@internetarchive/bookreader 5.0.0-9 → 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 (324) hide show
  1. package/.eslintrc.js +21 -19
  2. package/.github/workflows/node.js.yml +76 -11
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +404 -1125
  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/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 -1
  61. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  62. package/BookReader/plugins/plugin.autoplay.js +1 -1
  63. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  64. package/BookReader/plugins/plugin.chapters.js +25 -1
  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 -1
  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 -1
  72. package/BookReader/plugins/plugin.resume.js.map +1 -1
  73. package/BookReader/plugins/plugin.search.js +2 -1
  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 -1
  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 +1 -1
  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 -1
  83. package/BookReader/plugins/plugin.url.js.map +1 -1
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  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 +584 -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 +5 -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 +50 -28
  135. package/src/BookNavigator/search/search-results.js +24 -10
  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 +544 -1078
  162. package/src/BookReaderPlugin.js +44 -0
  163. package/src/assets/icons/magnify-minus.svg +3 -7
  164. package/src/assets/icons/magnify-plus.svg +3 -7
  165. package/src/assets/icons/voice.svg +1 -0
  166. package/src/assets/images/unviewable_page.png +0 -0
  167. package/src/css/BookReader.scss +1 -5
  168. package/src/css/_BRBookmarks.scss +1 -1
  169. package/src/css/_BRComponent.scss +1 -1
  170. package/src/css/_BRicon.scss +8 -2
  171. package/src/css/_BRmain.scss +16 -3
  172. package/src/css/_BRnav.scss +12 -42
  173. package/src/css/_BRpages.scss +170 -42
  174. package/src/css/_BRsearch.scss +68 -25
  175. package/src/css/_BRtoolbar.scss +5 -5
  176. package/src/css/_TextSelection.scss +87 -27
  177. package/src/css/_colorbox.scss +2 -2
  178. package/src/css/_controls.scss +24 -7
  179. package/src/css/_icons.scss +1 -1
  180. package/src/ia-bookreader/ia-bookreader.js +224 -0
  181. package/src/plugins/plugin.archive_analytics.js +84 -78
  182. package/src/plugins/plugin.autoplay.js +99 -104
  183. package/src/plugins/plugin.chapters.js +237 -191
  184. package/src/plugins/plugin.iframe.js +1 -1
  185. package/src/plugins/plugin.iiif.js +141 -0
  186. package/src/plugins/plugin.resume.js +53 -50
  187. package/src/plugins/plugin.text_selection.js +503 -175
  188. package/src/plugins/plugin.vendor-fullscreen.js +7 -7
  189. package/src/plugins/search/plugin.search.js +151 -127
  190. package/src/plugins/search/utils.js +43 -0
  191. package/src/plugins/search/view.js +37 -59
  192. package/src/plugins/tts/AbstractTTSEngine.js +75 -45
  193. package/src/plugins/tts/FestivalTTSEngine.js +21 -31
  194. package/src/plugins/tts/PageChunk.js +16 -23
  195. package/src/plugins/tts/PageChunkIterator.js +11 -17
  196. package/src/plugins/tts/WebTTSEngine.js +88 -72
  197. package/src/plugins/tts/plugin.tts.js +310 -350
  198. package/src/plugins/tts/utils.js +16 -26
  199. package/src/plugins/url/UrlPlugin.js +191 -0
  200. package/src/plugins/{plugin.url.js → url/plugin.url.js} +47 -18
  201. package/src/util/browserSniffing.js +22 -0
  202. package/src/util/docCookies.js +21 -2
  203. package/src/util/strings.js +1 -0
  204. package/tests/e2e/README.md +37 -0
  205. package/tests/e2e/autoplay.test.js +9 -6
  206. package/tests/e2e/base.test.js +8 -16
  207. package/tests/e2e/helpers/base.js +55 -50
  208. package/tests/e2e/helpers/debug.js +1 -1
  209. package/tests/e2e/helpers/mockSearch.js +19 -22
  210. package/tests/e2e/helpers/params.js +17 -0
  211. package/tests/e2e/helpers/rightToLeft.js +8 -14
  212. package/tests/e2e/helpers/search.js +73 -0
  213. package/tests/e2e/models/Navigation.js +20 -37
  214. package/tests/e2e/rightToLeft.test.js +4 -5
  215. package/tests/e2e/viewmode.test.js +40 -33
  216. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  217. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  218. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  219. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  220. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  221. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  222. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  223. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  224. package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
  225. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  226. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  227. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  228. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  229. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  230. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  231. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  232. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  233. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  234. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  235. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  236. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  237. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +42 -29
  238. package/tests/jest/BookReader/PageContainer.test.js +238 -0
  239. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  240. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +3 -3
  241. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  242. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  243. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  244. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  245. package/tests/jest/BookReader/utils.test.js +250 -0
  246. package/tests/jest/BookReader.keyboard.test.js +190 -0
  247. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +10 -2
  248. package/tests/{BookReader.test.js → jest/BookReader.test.js} +43 -53
  249. package/tests/jest/plugins/plugin.archive_analytics.test.js +20 -0
  250. package/tests/jest/plugins/plugin.autoplay.test.js +35 -0
  251. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  252. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +4 -4
  253. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +22 -35
  254. package/tests/jest/plugins/plugin.text_selection.test.js +316 -0
  255. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  256. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +19 -47
  257. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +42 -9
  258. package/tests/jest/plugins/search/utils.js +25 -0
  259. package/tests/jest/plugins/search/utils.test.js +29 -0
  260. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +30 -10
  261. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  262. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  263. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  264. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  265. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  266. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  267. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +57 -18
  268. package/tests/jest/setup.js +3 -0
  269. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  270. package/tests/jest/util/docCookies.test.js +24 -0
  271. package/tests/{util → jest/util}/strings.test.js +1 -1
  272. package/tests/{utils.js → jest/utils.js} +38 -0
  273. package/webpack.config.js +16 -10
  274. package/.babelrc +0 -12
  275. package/.dependabot/config.yml +0 -6
  276. package/.testcaferc.json +0 -5
  277. package/BookReader/bookreader-component-bundle.js +0 -1450
  278. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  279. package/BookReader/bookreader-component-bundle.js.map +0 -1
  280. package/BookReader/jquery-1.10.1.js +0 -2
  281. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  282. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  283. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  284. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  285. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  286. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  287. package/BookReaderDemo/IIIFBookReader.js +0 -207
  288. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  289. package/BookReaderDemo/demo-autoplay.html +0 -38
  290. package/BookReaderDemo/demo-iiif.js +0 -26
  291. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  292. package/karma.conf.js +0 -23
  293. package/src/BookNavigator/BookModel.js +0 -14
  294. package/src/BookNavigator/BookNavigator.js +0 -446
  295. package/src/BookNavigator/assets/book-loader.js +0 -27
  296. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  297. package/src/BookNavigator/search/a-search-result.js +0 -55
  298. package/src/BookReader/DebugConsole.js +0 -54
  299. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  300. package/src/ItemNavigator/ItemNavigator.js +0 -376
  301. package/src/ItemNavigator/providers/sharing.js +0 -29
  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/PageContainer.test.js +0 -115
  311. package/tests/BookReader/utils.test.js +0 -109
  312. package/tests/e2e/helpers/desktopSearch.js +0 -72
  313. package/tests/e2e/helpers/mobileSearch.js +0 -85
  314. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  315. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  316. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  317. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  318. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  319. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  320. package/tests/plugins/plugin.autoplay.test.js +0 -52
  321. package/tests/plugins/plugin.chapters.test.js +0 -130
  322. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  323. package/tests/plugins/plugin.text_selection.test.js +0 -203
  324. 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);