@internetarchive/bookreader 5.0.0-9-multiple-files → 5.0.0-90

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 (333) hide show
  1. package/.eslintrc.js +21 -19
  2. package/.github/workflows/node.js.yml +81 -7
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +505 -1442
  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/images/unviewable_page.png +0 -0
  58. package/BookReader/jquery-3.js +2 -0
  59. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  60. package/BookReader/plugins/plugin.archive_analytics.js +1 -172
  61. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  62. package/BookReader/plugins/plugin.autoplay.js +1 -165
  63. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  64. package/BookReader/plugins/plugin.chapters.js +22 -301
  65. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  66. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  67. package/BookReader/plugins/plugin.iframe.js +1 -74
  68. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  69. package/BookReader/plugins/plugin.iiif.js +2 -0
  70. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  71. package/BookReader/plugins/plugin.resume.js +1 -368
  72. package/BookReader/plugins/plugin.resume.js.map +1 -1
  73. package/BookReader/plugins/plugin.search.js +2 -1420
  74. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  75. package/BookReader/plugins/plugin.search.js.map +1 -1
  76. package/BookReader/plugins/plugin.text_selection.js +2 -1080
  77. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  78. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  79. package/BookReader/plugins/plugin.tts.js +2 -9193
  80. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  81. package/BookReader/plugins/plugin.tts.js.map +1 -1
  82. package/BookReader/plugins/plugin.url.js +1 -269
  83. package/BookReader/plugins/plugin.url.js.map +1 -1
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -379
  85. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  86. package/BookReader/webcomponents-bundle.js +3 -0
  87. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  88. package/BookReader/webcomponents-bundle.js.map +1 -0
  89. package/BookReaderDemo/BookReaderDemo.css +18 -19
  90. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  91. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  92. package/BookReaderDemo/IADemoBr.js +144 -0
  93. package/BookReaderDemo/demo-advanced.html +2 -2
  94. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  95. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  96. package/BookReaderDemo/demo-fullscreen.html +2 -4
  97. package/BookReaderDemo/demo-iiif.html +99 -12
  98. package/BookReaderDemo/demo-internetarchive.html +214 -18
  99. package/BookReaderDemo/demo-multiple.html +2 -1
  100. package/BookReaderDemo/demo-preview-pages.html +526 -525
  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 +595 -33
  110. package/README.md +14 -1
  111. package/babel.config.js +20 -0
  112. package/codecov.yml +6 -0
  113. package/index.html +5 -2
  114. package/jsconfig.json +19 -0
  115. package/netlify.toml +9 -0
  116. package/package.json +70 -62
  117. package/renovate.json +52 -0
  118. package/scripts/preversion.js +0 -1
  119. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  120. package/src/BookNavigator/assets/button-base.js +10 -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 +590 -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 +4 -9
  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 +29 -25
  134. package/src/BookNavigator/search/search-provider.js +80 -28
  135. package/src/BookNavigator/search/search-results.js +29 -26
  136. package/src/BookNavigator/sharing.js +27 -0
  137. package/src/BookNavigator/viewable-files.js +95 -0
  138. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +13 -12
  139. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +7 -7
  140. package/src/BookReader/BookModel.js +76 -41
  141. package/src/BookReader/DragScrollable.js +233 -0
  142. package/src/BookReader/ImageCache.js +48 -15
  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 +777 -0
  147. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  148. package/src/BookReader/ModeSmoothZoom.js +312 -0
  149. package/src/BookReader/ModeThumb.js +19 -13
  150. package/src/BookReader/Navbar/Navbar.js +70 -54
  151. package/src/BookReader/PageContainer.js +116 -22
  152. package/src/BookReader/ReduceSet.js +3 -3
  153. package/src/BookReader/Toolbar/Toolbar.js +14 -41
  154. package/src/BookReader/events.js +2 -3
  155. package/src/BookReader/options.js +73 -15
  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/classes.js +1 -1
  160. package/src/BookReader/utils.js +128 -13
  161. package/src/BookReader.js +562 -1078
  162. package/src/BookReaderPlugin.js +44 -0
  163. package/src/assets/icons/close-circle-dark.svg +1 -0
  164. package/src/assets/icons/magnify-minus.svg +3 -7
  165. package/src/assets/icons/magnify-plus.svg +3 -7
  166. package/src/assets/icons/voice.svg +1 -0
  167. package/src/assets/images/unviewable_page.png +0 -0
  168. package/src/css/BookReader.scss +1 -17
  169. package/src/css/_BRBookmarks.scss +1 -1
  170. package/src/css/_BRComponent.scss +1 -1
  171. package/src/css/_BRicon.scss +8 -2
  172. package/src/css/_BRmain.scss +33 -27
  173. package/src/css/_BRnav.scss +12 -42
  174. package/src/css/_BRpages.scss +170 -42
  175. package/src/css/_BRsearch.scss +68 -230
  176. package/src/css/_BRtoolbar.scss +5 -5
  177. package/src/css/_TextSelection.scss +87 -27
  178. package/src/css/_colorbox.scss +2 -2
  179. package/src/css/_controls.scss +24 -7
  180. package/src/css/_icons.scss +7 -1
  181. package/src/ia-bookreader/ia-bookreader.js +224 -0
  182. package/src/plugins/plugin.archive_analytics.js +84 -78
  183. package/src/plugins/plugin.autoplay.js +99 -104
  184. package/src/plugins/plugin.chapters.js +237 -191
  185. package/src/plugins/plugin.iframe.js +1 -1
  186. package/src/plugins/plugin.iiif.js +141 -0
  187. package/src/plugins/plugin.resume.js +53 -50
  188. package/src/plugins/plugin.text_selection.js +503 -175
  189. package/src/plugins/plugin.vendor-fullscreen.js +7 -7
  190. package/src/plugins/search/plugin.search.js +183 -121
  191. package/src/plugins/search/utils.js +43 -0
  192. package/src/plugins/search/view.js +67 -202
  193. package/src/plugins/tts/AbstractTTSEngine.js +75 -45
  194. package/src/plugins/tts/FestivalTTSEngine.js +21 -31
  195. package/src/plugins/tts/PageChunk.js +16 -23
  196. package/src/plugins/tts/PageChunkIterator.js +11 -17
  197. package/src/plugins/tts/WebTTSEngine.js +88 -72
  198. package/src/plugins/tts/plugin.tts.js +310 -350
  199. package/src/plugins/tts/utils.js +16 -26
  200. package/src/plugins/url/UrlPlugin.js +191 -0
  201. package/src/plugins/{plugin.url.js → url/plugin.url.js} +47 -18
  202. package/src/util/browserSniffing.js +22 -0
  203. package/src/util/docCookies.js +21 -2
  204. package/src/util/strings.js +1 -0
  205. package/tests/e2e/README.md +37 -0
  206. package/tests/e2e/autoplay.test.js +9 -6
  207. package/tests/e2e/base.test.js +8 -16
  208. package/tests/e2e/helpers/base.js +55 -50
  209. package/tests/e2e/helpers/debug.js +1 -1
  210. package/tests/e2e/helpers/mockSearch.js +19 -22
  211. package/tests/e2e/helpers/params.js +17 -0
  212. package/tests/e2e/helpers/rightToLeft.js +8 -14
  213. package/tests/e2e/helpers/search.js +73 -0
  214. package/tests/e2e/models/Navigation.js +20 -37
  215. package/tests/e2e/rightToLeft.test.js +4 -5
  216. package/tests/e2e/viewmode.test.js +40 -33
  217. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  218. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  219. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  220. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  221. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  222. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  223. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  224. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  225. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +109 -60
  226. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  227. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  228. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  229. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  230. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  231. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  232. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  233. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  234. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  235. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  236. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  237. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  238. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +42 -29
  239. package/tests/jest/BookReader/PageContainer.test.js +238 -0
  240. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  241. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +3 -3
  242. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  243. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  244. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  245. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  246. package/tests/jest/BookReader/utils.test.js +250 -0
  247. package/tests/jest/BookReader.keyboard.test.js +190 -0
  248. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +10 -2
  249. package/tests/{BookReader.test.js → jest/BookReader.test.js} +43 -53
  250. package/tests/jest/plugins/plugin.archive_analytics.test.js +20 -0
  251. package/tests/jest/plugins/plugin.autoplay.test.js +35 -0
  252. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  253. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +4 -4
  254. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +22 -35
  255. package/tests/jest/plugins/plugin.text_selection.test.js +316 -0
  256. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  257. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +26 -47
  258. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +42 -9
  259. package/tests/jest/plugins/search/utils.js +25 -0
  260. package/tests/jest/plugins/search/utils.test.js +29 -0
  261. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +30 -10
  262. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  263. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  264. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  265. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  266. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  267. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  268. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +57 -18
  269. package/tests/jest/setup.js +3 -0
  270. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  271. package/tests/jest/util/docCookies.test.js +24 -0
  272. package/tests/{util → jest/util}/strings.test.js +1 -1
  273. package/tests/{utils.js → jest/utils.js} +38 -0
  274. package/webpack.config.js +16 -10
  275. package/.babelrc +0 -12
  276. package/.dependabot/config.yml +0 -6
  277. package/.testcaferc.json +0 -5
  278. package/BookReader/bookreader-component-bundle.js +0 -14330
  279. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  280. package/BookReader/bookreader-component-bundle.js.map +0 -1
  281. package/BookReader/icons/sort-ascending.svg +0 -1
  282. package/BookReader/icons/sort-descending.svg +0 -1
  283. package/BookReader/jquery-1.10.1.js +0 -108
  284. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  285. package/BookReader/plugins/plugin.menu_toggle.js +0 -369
  286. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  287. package/BookReader/plugins/plugin.mobile_nav.js +0 -335
  288. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  289. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  290. package/BookReaderDemo/IIIFBookReader.js +0 -207
  291. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  292. package/BookReaderDemo/demo-autoplay.html +0 -38
  293. package/BookReaderDemo/demo-iiif.js +0 -26
  294. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  295. package/karma.conf.js +0 -23
  296. package/src/BookNavigator/BookModel.js +0 -14
  297. package/src/BookNavigator/BookNavigator.js +0 -452
  298. package/src/BookNavigator/assets/book-loader.js +0 -27
  299. package/src/BookNavigator/assets/icon_sort_ascending.js +0 -5
  300. package/src/BookNavigator/assets/icon_sort_descending.js +0 -5
  301. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  302. package/src/BookNavigator/search/a-search-result.js +0 -55
  303. package/src/BookNavigator/volumes/volumes-provider.js +0 -108
  304. package/src/BookNavigator/volumes/volumes.js +0 -162
  305. package/src/BookReader/DebugConsole.js +0 -54
  306. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  307. package/src/ItemNavigator/ItemNavigator.js +0 -372
  308. package/src/ItemNavigator/providers/sharing.js +0 -29
  309. package/src/assets/icons/sort-ascending.svg +0 -1
  310. package/src/assets/icons/sort-descending.svg +0 -1
  311. package/src/css/_MobileNav.scss +0 -194
  312. package/src/dragscrollable-br.js +0 -261
  313. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  314. package/src/plugins/plugin.mobile_nav.js +0 -287
  315. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  316. package/tests/BookReader/DebugConsole.test.js +0 -25
  317. package/tests/BookReader/Mode1Up.test.js +0 -164
  318. package/tests/BookReader/Mode2Up.test.js +0 -247
  319. package/tests/BookReader/PageContainer.test.js +0 -115
  320. package/tests/BookReader/utils.test.js +0 -109
  321. package/tests/e2e/helpers/desktopSearch.js +0 -72
  322. package/tests/e2e/helpers/mobileSearch.js +0 -85
  323. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  324. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  325. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  326. package/tests/karma/BookNavigator/volumes.test.js +0 -133
  327. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  328. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  329. package/tests/plugins/plugin.autoplay.test.js +0 -52
  330. package/tests/plugins/plugin.chapters.test.js +0 -130
  331. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  332. package/tests/plugins/plugin.text_selection.test.js +0 -203
  333. package/tests/util/docCookies.test.js +0 -15
@@ -1,392 +1,352 @@
1
- /* global BookReader */
2
- /**
3
- * Plugin for Text to Speech in BookReader
4
- */
1
+ // @ts-check
2
+
5
3
  import FestivalTTSEngine from './FestivalTTSEngine.js';
6
4
  import WebTTSEngine from './WebTTSEngine.js';
7
5
  import { toISO6391, approximateWordCount } from './utils.js';
8
6
  import { en as tooltips } from './tooltip_dict.js';
9
- /** @typedef {import('./PageChunk.js')} PageChunk */
10
- /** @typedef {import("./AbstractTTSEngine.js")} AbstractTTSEngine */
11
-
12
- // Default options for TTS
13
- jQuery.extend(BookReader.defaultOptions, {
14
- server: 'ia600609.us.archive.org',
15
- bookPath: '',
16
- enableTtsPlugin: true,
17
- });
18
-
19
- // Extend the constructor to add TTS properties
20
- BookReader.prototype.setup = (function (super_) {
21
- return function (options) {
22
- super_.call(this, options);
23
-
24
- if (this.options.enableTtsPlugin) {
25
- this.ttsHilites = [];
26
- let TTSEngine = WebTTSEngine.isSupported() ? WebTTSEngine :
27
- FestivalTTSEngine.isSupported() ? FestivalTTSEngine :
28
- null;
29
-
30
- if (/_forceTTSEngine=(festival|web)/.test(location.toString())) {
31
- const engineName = location.toString().match(/_forceTTSEngine=(festival|web)/)[1];
32
- TTSEngine = { festival: FestivalTTSEngine, web: WebTTSEngine }[engineName];
33
- }
7
+ import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
8
+ import { BookReaderPlugin } from '../../BookReaderPlugin.js';
9
+ import { applyVariables } from '../../util/strings.js';
10
+ /** @typedef {import('./PageChunk.js').default} PageChunk */
11
+ /** @typedef {import("./AbstractTTSEngine.js").default} AbstractTTSEngine */
34
12
 
35
- if (TTSEngine) {
36
- /** @type {AbstractTTSEngine} */
37
- this.ttsEngine = new TTSEngine({
38
- server: options.server,
39
- bookPath: options.bookPath,
40
- bookLanguage: toISO6391(options.bookLanguage),
41
- onLoadingStart: this.showProgressPopup.bind(this, 'Loading audio...'),
42
- onLoadingComplete: this.removeProgressPopup.bind(this),
43
- onDone: this.ttsStop.bind(this),
44
- beforeChunkPlay: this.ttsBeforeChunkPlay.bind(this),
45
- afterChunkPlay: this.ttsSendChunkFinishedAnalyticsEvent.bind(this),
46
- });
47
- }
13
+ const BookReader = /** @type {typeof import('../../BookReader').default} */(window.BookReader);
14
+
15
+ /**
16
+ * Plugin for Text to Speech in BookReader
17
+ */
18
+ export class TtsPlugin extends BookReaderPlugin {
19
+ options = {
20
+ enabled: true,
21
+ /**
22
+ * @type {import('@/src/util/strings.js').StringWithVars}
23
+ * The URL where to get PageChunk objects for a given page. Expects a var `pageIndex`
24
+ **/
25
+ pageChunkUrl: 'https://{{server}}/BookReader/BookReaderGetTextWrapper.php?path={{bookPath|urlencode}}_djvu.xml&page={{pageIndex}}&callback=false',
26
+ /**
27
+ * @type {import('@/src/util/strings.js').StringWithVars}
28
+ * The URL for converting text to audio if the web SpeechSynthesis API is not supported.
29
+ * Provided variables:
30
+ * - `text` -- the text to convert to audio
31
+ * - `format` -- the format of the audio, 'mp3' or 'ogg';
32
+ */
33
+ remoteTtsUrl: 'https://{{server}}/BookReader/BookReaderGetTTS.php?string={{text|urlencode}}&format={{format}}',
34
+ }
35
+
36
+ /**
37
+ * @override
38
+ * @param {Partial<TtsPlugin['options']>} options
39
+ **/
40
+ setup(options) {
41
+ super.setup(Object.assign({
42
+ // @deprecated support options specified in global options
43
+ server: this.br.options.server,
44
+ bookPath: this.br.options.bookPath,
45
+ }, options));
46
+
47
+ if (!this.options.enabled) return;
48
+
49
+ /** @type { {[pageIndex: number]: Array<{ l: number, r: number, t: number, b: number }>} } */
50
+ this._ttsBoxesByIndex = {};
51
+
52
+ let TTSEngine = WebTTSEngine.isSupported() ? WebTTSEngine :
53
+ FestivalTTSEngine.isSupported() ? FestivalTTSEngine :
54
+ null;
55
+
56
+ if (/_forceTTSEngine=(festival|web)/.test(location.toString())) {
57
+ const engineName = location.toString().match(/_forceTTSEngine=(festival|web)/)[1];
58
+ TTSEngine = { festival: FestivalTTSEngine, web: WebTTSEngine }[engineName];
48
59
  }
49
- };
50
- })(BookReader.prototype.setup);
51
-
52
- BookReader.prototype.init = (function(super_) {
53
- return function() {
54
- if (this.options.enableTtsPlugin) {
55
- // Bind to events
56
-
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
- }
60
+
61
+ if (TTSEngine) {
62
+ /** @type {AbstractTTSEngine} */
63
+ this.ttsEngine = new TTSEngine({
64
+ pageChunkUrl: applyVariables(this.options.pageChunkUrl, this.br.options.vars),
65
+ bookLanguage: toISO6391(this.br.options.bookLanguage),
66
+ onLoadingStart: this.br.showProgressPopup.bind(this.br, 'Loading audio...'),
67
+ onLoadingComplete: this.br.removeProgressPopup.bind(this.br),
68
+ onDone: this.stop.bind(this),
69
+ beforeChunkPlay: this.beforeChunkPlay.bind(this),
70
+ afterChunkPlay: this.sendChunkFinishedAnalyticsEvent.bind(this),
71
+ ...(TTSEngine === FestivalTTSEngine ? {
72
+ festivalUrl: applyVariables(this.options.remoteTtsUrl, this.br.options.vars),
73
+ } : {}),
66
74
  });
75
+ }
76
+ }
67
77
 
68
- this.bind(BookReader.eventNames.PostInit, () => {
69
- this.$('.BRicon.read').click(() => {
70
- this.ttsToggle();
71
- return false;
72
- });
73
- if (this.ttsEngine) {
74
- this.ttsEngine.init();
75
- if (/[?&]_autoReadAloud=show/.test(location.toString())) {
76
- this.ttsStart(false); // false flag is to initiate read aloud controls
77
- }
78
- }
78
+ /** @override */
79
+ init() {
80
+ if (!this.options.enabled) return;
81
+
82
+ this.br.bind(BookReader.eventNames.PostInit, () => {
83
+ this.br.$('.BRicon.read').click(() => {
84
+ this.toggle();
85
+ return false;
79
86
  });
87
+ if (this.ttsEngine) {
88
+ this.ttsEngine.init();
89
+ if (/[?&]_autoReadAloud=show/.test(location.toString())) {
90
+ this.start(false); // false flag is to initiate read aloud controls
91
+ }
92
+ }
93
+ });
80
94
 
81
- // This is fired when the hash changes by one of the other plugins!
82
- // i.e. it will fire every time the page changes -_-
83
- // this.bind(BookReader.eventNames.stop, function(e, br) {
84
- // this.ttsStop();
85
- // }.bind(this));
95
+ // This is fired when the hash changes by one of the other plugins!
96
+ // i.e. it will fire every time the page changes -_-
97
+ // this.br.bind(BookReader.eventNames.stop, function(e, br) {
98
+ // this.ttsStop();
99
+ // }.bind(this));
100
+ }
101
+
102
+ /**
103
+ * @override
104
+ * @param {import ("@/src/BookReader/PageContainer.js").PageContainer} pageContainer
105
+ */
106
+ _configurePageContainer(pageContainer) {
107
+ if (this.options.enabled && pageContainer.page && pageContainer.page.index in this._ttsBoxesByIndex) {
108
+ const pageIndex = pageContainer.page.index;
109
+ renderBoxesInPageContainerLayer('ttsHiliteLayer', this._ttsBoxesByIndex[pageIndex], pageContainer.page, pageContainer.$container[0]);
86
110
  }
87
- super_.call(this);
88
- };
89
- })(BookReader.prototype.init);
111
+ }
90
112
 
113
+ /**
114
+ * @override
115
+ * @param {JQuery<HTMLElement>} $navBar
116
+ */
117
+ extendNavBar($navBar) {
118
+ if (!this.options.enabled || !this.ttsEngine) return;
91
119
 
92
- // Extend buildMobileDrawerElement
93
- BookReader.prototype.buildMobileDrawerElement = (function (super_) {
94
- return function () {
95
- const $el = super_.call(this);
96
- if (this.options.enableTtsPlugin && this.ttsEngine) {
97
- $el.find('.BRmobileMenu__moreInfoRow').after($(`
120
+ this.br.refs.$BRReadAloudToolbar = $(`
121
+ <ul class="read-aloud">
98
122
  <li>
99
- <span>
100
- <span class="DrawerIconWrapper"><img class="DrawerIcon" src="${this.imagesBaseURL}icon_speaker_open.svg" alt="info-speaker"/></span>
101
- Read Aloud
102
- </span>
103
- <div>
104
- <span class="larger">Press to toggle read aloud</span>
105
- <br/>
106
- <button class="BRicon read"></button>
107
- </div>
108
- </li>`));
109
- }
110
- return $el;
111
- };
112
- })(BookReader.prototype.buildMobileDrawerElement);
113
-
114
- // Extend initNavbar
115
- BookReader.prototype.initNavbar = (function (super_) {
116
- return function () {
117
- const $el = super_.call(this);
118
- if (this.options.enableTtsPlugin && this.ttsEngine) {
119
- this.refs.$BRReadAloudToolbar = $(`
120
- <ul class="read-aloud">
121
- <li>
122
- <select class="playback-speed" name="playback-speed" title="${tooltips.playbackSpeed}">
123
- <option value="0.25">0.25x</option>
124
- <option value="0.5">0.5x</option>
125
- <option value="0.75">0.75x</option>
126
- <option value="1.0" selected>1.0x</option>
127
- <option value="1.25">1.25x</option>
128
- <option value="1.5">1.5x</option>
129
- <option value="1.75">1.75x</option>
130
- <option value="2">2x</option>
131
- </select>
132
- </li>
133
- <li>
134
- <button type="button" name="review" title="${tooltips.review}">
135
- <div class="icon icon-review"></div>
136
- </button>
137
- </li>
138
- <li>
139
- <button type="button" name="play" title="${tooltips.play}">
140
- <div class="icon icon-play"></div>
141
- <div class="icon icon-pause"></div>
142
- </button>
143
- </li>
144
- <li>
145
- <button type="button" name="advance" title="${tooltips.advance}">
146
- <div class="icon icon-advance"></div>
147
- </button>
148
- </li>
149
- </ul>
150
- `);
151
- $el.find('.BRcontrols').prepend(this.refs.$BRReadAloudToolbar);
152
- 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));
156
- const $rateSelector = this.refs.$BRReadAloudToolbar.find('select[name="playback-speed"]');
157
- $rateSelector.change(ev => this.ttsEngine.setPlaybackRate(parseFloat($rateSelector.val())));
158
- $(`<li>
159
- <button class="BRicon read js-tooltip" title="${tooltips.readAloud}">
160
- <div class="icon icon-read-aloud"></div>
161
- <span class="tooltip">${tooltips.readAloud}</span>
123
+ <select class="playback-speed" name="playback-speed" title="${tooltips.playbackSpeed}">
124
+ <option value="0.25">0.25x</option>
125
+ <option value="0.5">0.5x</option>
126
+ <option value="0.75">0.75x</option>
127
+ <option value="1.0" selected>1.0x</option>
128
+ <option value="1.25">1.25x</option>
129
+ <option value="1.5">1.5x</option>
130
+ <option value="1.75">1.75x</option>
131
+ <option value="2">2x</option>
132
+ </select>
133
+ </li>
134
+ <li>
135
+ <button type="button" name="review" title="${tooltips.review}">
136
+ <div class="icon icon-review"></div>
162
137
  </button>
163
- </li>`).insertBefore($el.find('.BRcontrols .BRicon.zoom_out').closest('li'));
164
- }
165
- return $el;
166
- };
167
- })(BookReader.prototype.initNavbar);
168
-
169
- // ttsToggle()
170
- //______________________________________________________________________________
171
- BookReader.prototype.ttsToggle = function () {
172
- if (this.autoStop) this.autoStop();
173
- if (this.ttsEngine.playing) {
174
- this.ttsStop();
175
- } else {
176
- this.ttsStart();
138
+ </li>
139
+ <li>
140
+ <button type="button" name="play" title="${tooltips.play}">
141
+ <div class="icon icon-play"></div>
142
+ <div class="icon icon-pause"></div>
143
+ </button>
144
+ </li>
145
+ <li>
146
+ <button type="button" name="advance" title="${tooltips.advance}">
147
+ <div class="icon icon-advance"></div>
148
+ </button>
149
+ </li>
150
+ <li>
151
+ <select class="playback-voices" name="playback-voice" style="display: none" title="Change read aloud voices">
152
+ </select>
153
+ </li>
154
+ </ul>
155
+ `);
156
+
157
+ $navBar.find('.BRcontrols').prepend(this.br.refs.$BRReadAloudToolbar);
158
+
159
+ const renderVoiceOption = (voices) => {
160
+ return voices.map(voice =>
161
+ `<option value="${voice.voiceURI}">${voice.lang} - ${voice.name}</option>`).join('');
162
+ };
163
+
164
+ const voiceSortOrder = (a,b) => `${a.lang} - ${a.name}`.localeCompare(`${b.lang} - ${b.name}`);
165
+
166
+ const renderVoicesMenu = (voicesMenu) => {
167
+ voicesMenu.empty();
168
+ const bookLanguage = this.ttsEngine.opts.bookLanguage;
169
+ const bookLanguages = this.ttsEngine.getVoices().filter(v => v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
170
+ const otherLanguages = this.ttsEngine.getVoices().filter(v => !v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
171
+
172
+ if (this.ttsEngine.getVoices().length > 1) {
173
+ voicesMenu.append($(`<optgroup label="Book Language (${bookLanguage})"> ${renderVoiceOption(bookLanguages)} </optgroup>`));
174
+ voicesMenu.append($(`<optgroup label="Other Languages"> ${renderVoiceOption(otherLanguages)} </optgroup>`));
175
+
176
+ voicesMenu.val(this.ttsEngine.voice.voiceURI);
177
+ voicesMenu.show();
178
+ } else {
179
+ voicesMenu.hide();
180
+ }
181
+ };
182
+
183
+ const voicesMenu = this.br.refs.$BRReadAloudToolbar.find('[name=playback-voice]');
184
+ renderVoicesMenu(voicesMenu);
185
+ voicesMenu.on("change", ev => this.ttsEngine.setVoice(voicesMenu.val()));
186
+ this.ttsEngine.events.on('pause resume start', () => this.updateState());
187
+ this.ttsEngine.events.on('voiceschanged', () => renderVoicesMenu(voicesMenu));
188
+ this.br.refs.$BRReadAloudToolbar.find('[name=play]').on("click", this.playPause.bind(this));
189
+ this.br.refs.$BRReadAloudToolbar.find('[name=advance]').on("click", this.jumpForward.bind(this));
190
+ this.br.refs.$BRReadAloudToolbar.find('[name=review]').on("click", this.jumpBackward.bind(this));
191
+ const $rateSelector = this.br.refs.$BRReadAloudToolbar.find('select[name="playback-speed"]');
192
+ $rateSelector.on("change", ev => this.ttsEngine.setPlaybackRate(parseFloat($rateSelector.val())));
193
+ $(`<li>
194
+ <button class="BRicon read js-tooltip" title="${tooltips.readAloud}">
195
+ <div class="icon icon-read-aloud"></div>
196
+ <span class="BRtooltip">${tooltips.readAloud}</span>
197
+ </button>
198
+ </li>`).insertBefore($navBar.find('.BRcontrols .BRicon.zoom_out').closest('li'));
177
199
  }
178
- };
179
-
180
- // ttsStart(
181
- //______________________________________________________________________________
182
- BookReader.prototype.ttsStart = function (startTTSEngine = true) {
183
- if (this.constModeThumb == this.mode)
184
- this.switchMode(this.constMode1up);
185
-
186
- this.refs.$BRReadAloudToolbar.addClass('visible');
187
- this.$('.BRicon.read').addClass('unread active');
188
- this.ttsSendAnalyticsEvent('Start');
189
- if (startTTSEngine)
190
- this.ttsEngine.start(this.currentIndex(), this.getNumLeafs());
191
- };
192
-
193
- BookReader.prototype.ttsJumpForward = function () {
194
- if (this.ttsEngine.paused) {
195
- this.ttsEngine.resume();
200
+
201
+ toggle() {
202
+ this.br._plugins.autoplay?.stop();
203
+ if (this.ttsEngine.playing) {
204
+ this.stop();
205
+ } else {
206
+ this.start();
207
+ }
196
208
  }
197
- this.ttsEngine.jumpForward();
198
- };
199
209
 
200
- BookReader.prototype.ttsJumpBackward = function () {
201
- if (this.ttsEngine.paused) {
202
- this.ttsEngine.resume();
210
+ start(startTTSEngine = true) {
211
+ if (this.br.constModeThumb == this.br.mode)
212
+ this.br.switchMode(this.br.constMode1up);
213
+
214
+ this.br.refs.$BRReadAloudToolbar.addClass('visible');
215
+ this.br.$('.BRicon.read').addClass('unread active');
216
+ this.sendAnalyticsEvent('Start');
217
+ if (startTTSEngine)
218
+ this.ttsEngine.start(this.br.currentIndex(), this.br.book.getNumLeafs());
203
219
  }
204
- this.ttsEngine.jumpBackward();
205
- };
206
-
207
- BookReader.prototype.ttsUpdateState = function() {
208
- const isPlaying = !(this.ttsEngine.paused || !this.ttsEngine.playing);
209
- this.$('.read-aloud [name=play]').toggleClass('playing', isPlaying);
210
- };
211
-
212
- BookReader.prototype.ttsPlayPause = function() {
213
- if (!this.ttsEngine.playing) {
214
- this.ttsToggle();
215
- } else {
216
- this.ttsEngine.togglePlayPause();
217
- this.ttsUpdateState(this.ttsEngine.paused);
220
+
221
+ jumpForward() {
222
+ if (this.ttsEngine.paused) {
223
+ this.ttsEngine.resume();
224
+ }
225
+ this.ttsEngine.jumpForward();
218
226
  }
219
- };
220
-
221
- // ttsStop()
222
- //______________________________________________________________________________
223
- BookReader.prototype.ttsStop = function () {
224
- this.refs.$BRReadAloudToolbar.removeClass('visible');
225
- this.$('.BRicon.read').removeClass('unread active');
226
- this.ttsSendAnalyticsEvent('Stop');
227
- this.ttsEngine.stop();
228
- this.ttsRemoveHilites();
229
- this.removeProgressPopup();
230
- };
231
227
 
232
- /**
233
- * @param {PageChunk} chunk
234
- * @return {PromiseLike<void>} returns once the flip is done
235
- */
236
- BookReader.prototype.ttsBeforeChunkPlay = function(chunk) {
237
- return this.ttsMaybeFlipToIndex(chunk.leafIndex)
238
- .then(() => {
239
- this.ttsHighlightChunk(chunk);
240
- this.ttsScrollToChunk(chunk);
241
- });
242
- };
228
+ jumpBackward() {
229
+ if (this.ttsEngine.paused) {
230
+ this.ttsEngine.resume();
231
+ }
232
+ this.ttsEngine.jumpBackward();
233
+ }
243
234
 
244
- /**
245
- * @param {PageChunk} chunk
246
- */
247
- BookReader.prototype.ttsSendChunkFinishedAnalyticsEvent = function(chunk) {
248
- this.ttsSendAnalyticsEvent('ChunkFinished-Words', approximateWordCount(chunk.text));
249
- };
235
+ updateState() {
236
+ const isPlaying = !(this.ttsEngine.paused || !this.ttsEngine.playing);
237
+ this.br.$('.read-aloud [name=play]').toggleClass('playing', isPlaying);
238
+ }
250
239
 
251
- /**
252
- * Flip the page if the provided leaf index is not visible
253
- * @param {Number} leafIndex
254
- * @return {PromiseLike<void>} resolves once the flip animation has completed
255
- */
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) {
262
- this.jumpToIndex(leafIndex);
263
- resolve();
264
- } else {
265
- const leafVisible = leafIndex == this.twoPage.currentIndexR || leafIndex == this.twoPage.currentIndexL;
266
- if (leafVisible) {
267
- resolve();
240
+ playPause() {
241
+ if (!this.ttsEngine.playing) {
242
+ this.toggle();
268
243
  } 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));
244
+ this.ttsEngine.togglePlayPause();
245
+ this.updateState();
274
246
  }
275
247
  }
276
248
 
277
- return promise;
278
- }
279
249
 
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);
250
+ stop() {
251
+ this.br.refs.$BRReadAloudToolbar.removeClass('visible');
252
+ this.br.$('.BRicon.read').removeClass('unread active');
253
+ this.sendAnalyticsEvent('Stop');
254
+ this.ttsEngine.stop();
255
+ this.removeHilites();
256
+ this.br.removeProgressPopup();
290
257
  }
291
- };
292
258
 
293
- /**
294
- * @param {PageChunk} chunk
295
- */
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;
259
+ /**
260
+ * @param {PageChunk} chunk
261
+ * Returns once the flip is done
262
+ */
263
+ async beforeChunkPlay(chunk) {
264
+ await this.maybeFlipToIndex(chunk.leafIndex);
265
+ this.highlightChunk(chunk);
266
+ this.scrollToChunk(chunk);
305
267
  }
306
268
 
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);
269
+ /**
270
+ * @param {PageChunk} chunk
271
+ */
272
+ sendChunkFinishedAnalyticsEvent(chunk) {
273
+ this.sendAnalyticsEvent('ChunkFinished-Words', approximateWordCount(chunk.text));
274
+ }
314
275
 
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);
276
+ /**
277
+ * Flip the page if the provided leaf index is not visible
278
+ * @param {Number} leafIndex
279
+ */
280
+ async maybeFlipToIndex(leafIndex) {
281
+ if (this.br.constMode2up != this.br.mode) {
282
+ this.br.jumpToIndex(leafIndex);
283
+ } else {
284
+ await this.br._modes.mode2Up.mode2UpLit.jumpToIndex(leafIndex);
285
+ }
286
+ }
318
287
 
319
- if ((topOfFirstChunk < containerTop) || (botOfLastChunk > containerBot)) {
320
- this.refs.$brContainer.stop(true).animate({scrollTop: topOfFirstChunk},'fast');
288
+ /**
289
+ * @param {PageChunk} chunk
290
+ */
291
+ highlightChunk(chunk) {
292
+ // The poorly-named variable leafIndex
293
+ const pageIndex = chunk.leafIndex;
294
+
295
+ this.removeHilites();
296
+
297
+ // group by index; currently only possible to have chunks on one page :/
298
+ this._ttsBoxesByIndex = {
299
+ [pageIndex]: chunk.lineRects.map(([l, b, r, t]) => ({l, r, b, t})),
300
+ };
301
+
302
+ // update any already created pages
303
+ for (const [pageIndexString, boxes] of Object.entries(this._ttsBoxesByIndex)) {
304
+ const pageIndex = parseFloat(pageIndexString);
305
+ const page = this.br.book.getPage(pageIndex);
306
+ const pageContainers = this.br.getActivePageContainerElementsForIndex(pageIndex);
307
+ pageContainers.forEach(container => renderBoxesInPageContainerLayer('ttsHiliteLayer', boxes, page, container));
308
+ }
321
309
  }
322
- };
323
310
 
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'
311
+ /**
312
+ * @param {PageChunk} chunk
313
+ */
314
+ scrollToChunk(chunk) {
315
+ // It behaves weird if used in thumb mode
316
+ if (this.br.constModeThumb == this.br.mode) return;
317
+
318
+ $(`.pagediv${chunk.leafIndex} .ttsHiliteLayer rect`).last()?.[0]?.scrollIntoView({
319
+ // Only vertically center the highlight if we're in 1up or in full screen. In
320
+ // 2up, if we're not fullscreen, the whole body gets scrolled around to try to
321
+ // center the highlight 🙄 See:
322
+ // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move/11041376
323
+ // Note: nearest doesn't quite work great, because the ReadAloud toolbar is now
324
+ // full-width, and covers up the last line of the highlight.
325
+ block: this.br.constMode1up == this.br.mode || this.br.isFullscreenActive ? 'center' : 'nearest',
326
+ inline: 'center',
327
+ behavior: 'smooth',
346
328
  });
347
329
  }
348
330
 
349
- };
350
-
351
- /**
352
- * @param {PageChunk} chunk
353
- */
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);
331
+ removeHilites() {
332
+ $(this.br.getActivePageContainerElements()).find('.ttsHiliteLayer').remove();
333
+ this._ttsBoxesByIndex = {};
369
334
  }
370
- };
371
335
 
372
- // ttsRemoveHilites()
373
- //______________________________________________________________________________
374
- BookReader.prototype.ttsRemoveHilites = function () {
375
- $(this.ttsHilites).remove();
376
- this.ttsHilites = [];
377
- };
378
-
379
- /**
380
- * @private
381
- * Send an analytics event with an optional value. Also attaches the book's language.
382
- * @param {string} action
383
- * @param {number} [value]
384
- */
385
- BookReader.prototype.ttsSendAnalyticsEvent = function(action, value) {
386
- if (this.archiveAnalyticsSendEvent) {
387
- const extraValues = {};
388
- const mediaLanguage = this.ttsEngine.opts.bookLanguage;
389
- if (mediaLanguage) extraValues.mediaLanguage = mediaLanguage;
390
- this.archiveAnalyticsSendEvent('BRReadAloud', action, value, extraValues);
336
+ /**
337
+ * @private
338
+ * Send an analytics event with an optional value. Also attaches the book's language.
339
+ * @param {string} action
340
+ * @param {number} [value]
341
+ */
342
+ sendAnalyticsEvent(action, value) {
343
+ if (this.br._plugins.archiveAnalytics) {
344
+ const extraValues = {};
345
+ const mediaLanguage = this.ttsEngine.opts.bookLanguage;
346
+ if (mediaLanguage) extraValues.mediaLanguage = mediaLanguage;
347
+ this.br._plugins.archiveAnalytics.sendEvent('BRReadAloud', action, value, extraValues);
348
+ }
391
349
  }
392
- };
350
+ }
351
+
352
+ BookReader?.registerPlugin('tts', TtsPlugin);