@internetarchive/bookreader 5.0.0-88-alpha.11 → 5.0.0-89

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 (243) hide show
  1. package/BookReader/BookReader.css +17 -3
  2. package/BookReader/BookReader.js +1 -1
  3. package/BookReader/BookReader.js.map +1 -1
  4. package/BookReader/ia-bookreader-bundle.js +87 -108
  5. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  6. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  7. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  8. package/BookReader/plugins/plugin.autoplay.js +1 -1
  9. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  10. package/BookReader/plugins/plugin.iiif.js +1 -1
  11. package/BookReader/plugins/plugin.iiif.js.map +1 -1
  12. package/BookReader/plugins/plugin.resume.js +1 -1
  13. package/BookReader/plugins/plugin.resume.js.map +1 -1
  14. package/BookReader/plugins/plugin.search.js +1 -1
  15. package/BookReader/plugins/plugin.search.js.map +1 -1
  16. package/BookReader/plugins/plugin.text_selection.js +1 -1
  17. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  18. package/BookReader/plugins/plugin.tts.js +1 -1
  19. package/BookReader/plugins/plugin.tts.js.map +1 -1
  20. package/BookReader/plugins/plugin.url.js +1 -1
  21. package/BookReader/plugins/plugin.url.js.map +1 -1
  22. package/CHANGELOG.md +14 -0
  23. package/babel.config.js +12 -30
  24. package/codecov.yml +1 -1
  25. package/jsconfig.json +1 -3
  26. package/package.json +14 -16
  27. package/src/BookNavigator/search/search-results.js +1 -1
  28. package/src/BookReader/ImageCache.js +48 -15
  29. package/src/BookReader/Mode1UpLit.js +56 -86
  30. package/src/BookReader/Mode2UpLit.js +5 -5
  31. package/src/BookReader/Navbar/Navbar.js +53 -11
  32. package/src/BookReader/PageContainer.js +41 -22
  33. package/src/BookReader/options.js +27 -3
  34. package/src/BookReader/utils.js +10 -0
  35. package/src/BookReader.js +120 -21
  36. package/src/BookReaderPlugin.js +44 -0
  37. package/src/css/_BRnav.scss +0 -3
  38. package/src/css/_BRpages.scss +21 -2
  39. package/src/css/_controls.scss +4 -0
  40. package/src/plugins/plugin.archive_analytics.js +84 -78
  41. package/src/plugins/plugin.autoplay.js +98 -102
  42. package/src/plugins/plugin.chapters.js +17 -22
  43. package/src/plugins/plugin.iiif.js +16 -30
  44. package/src/plugins/plugin.resume.js +54 -51
  45. package/src/plugins/plugin.text_selection.js +68 -76
  46. package/src/plugins/tts/AbstractTTSEngine.js +2 -4
  47. package/src/plugins/tts/PageChunk.js +5 -9
  48. package/src/plugins/tts/PageChunkIterator.js +3 -5
  49. package/src/plugins/tts/plugin.tts.js +309 -329
  50. package/src/plugins/url/plugin.url.js +1 -1
  51. package/src/util/strings.js +1 -0
  52. package/tests/e2e/autoplay.test.js +8 -5
  53. package/tests/e2e/helpers/base.js +2 -2
  54. package/tests/e2e/helpers/mockSearch.js +6 -9
  55. package/tests/jest/BookReader/Navbar/Navbar.test.js +16 -3
  56. package/tests/jest/BookReader/PageContainer.test.js +96 -55
  57. package/tests/jest/BookReader/utils.test.js +21 -0
  58. package/tests/jest/BookReader.test.js +13 -12
  59. package/tests/jest/plugins/plugin.archive_analytics.test.js +8 -11
  60. package/tests/jest/plugins/plugin.autoplay.test.js +9 -22
  61. package/tests/jest/plugins/plugin.resume.test.js +19 -32
  62. package/tests/jest/plugins/plugin.text_selection.test.js +23 -24
  63. package/dist/esm/BookNavigator/assets/bookmark-colors.js +0 -4
  64. package/dist/esm/BookNavigator/assets/button-base.js +0 -4
  65. package/dist/esm/BookNavigator/assets/ia-logo.js +0 -4
  66. package/dist/esm/BookNavigator/assets/icon_checkmark.js +0 -8
  67. package/dist/esm/BookNavigator/assets/icon_close.js +0 -4
  68. package/dist/esm/BookNavigator/book-navigator.js +0 -612
  69. package/dist/esm/BookNavigator/bookmarks/bookmark-button.js +0 -35
  70. package/dist/esm/BookNavigator/bookmarks/bookmark-edit.js +0 -78
  71. package/dist/esm/BookNavigator/bookmarks/bookmarks-list.js +0 -160
  72. package/dist/esm/BookNavigator/bookmarks/bookmarks-loginCTA.js +0 -24
  73. package/dist/esm/BookNavigator/bookmarks/bookmarks-provider.js +0 -55
  74. package/dist/esm/BookNavigator/bookmarks/ia-bookmarks.js +0 -521
  75. package/dist/esm/BookNavigator/delete-modal-actions.js +0 -29
  76. package/dist/esm/BookNavigator/downloads/downloads-provider.js +0 -84
  77. package/dist/esm/BookNavigator/downloads/downloads.js +0 -69
  78. package/dist/esm/BookNavigator/search/search-provider.js +0 -238
  79. package/dist/esm/BookNavigator/search/search-results.js +0 -161
  80. package/dist/esm/BookNavigator/sharing.js +0 -26
  81. package/dist/esm/BookNavigator/viewable-files.js +0 -94
  82. package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments-provider.js +0 -83
  83. package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments.js +0 -131
  84. package/dist/esm/BookReader/BookModel.js +0 -575
  85. package/dist/esm/BookReader/DragScrollable.js +0 -224
  86. package/dist/esm/BookReader/ImageCache.js +0 -122
  87. package/dist/esm/BookReader/Mode1Up.js +0 -114
  88. package/dist/esm/BookReader/Mode1UpLit.js +0 -579
  89. package/dist/esm/BookReader/Mode2Up.js +0 -106
  90. package/dist/esm/BookReader/Mode2UpLit.js +0 -1020
  91. package/dist/esm/BookReader/ModeCoordinateSpace.js +0 -28
  92. package/dist/esm/BookReader/ModeSmoothZoom.js +0 -318
  93. package/dist/esm/BookReader/ModeThumb.js +0 -366
  94. package/dist/esm/BookReader/Navbar/Navbar.js +0 -253
  95. package/dist/esm/BookReader/PageContainer.js +0 -165
  96. package/dist/esm/BookReader/ReduceSet.js +0 -27
  97. package/dist/esm/BookReader/Toolbar/Toolbar.js +0 -242
  98. package/dist/esm/BookReader/events.js +0 -20
  99. package/dist/esm/BookReader/options.js +0 -331
  100. package/dist/esm/BookReader/utils/HTMLDimensionsCacher.js +0 -48
  101. package/dist/esm/BookReader/utils/ScrollClassAdder.js +0 -31
  102. package/dist/esm/BookReader/utils/SelectionObserver.js +0 -42
  103. package/dist/esm/BookReader/utils/classes.js +0 -37
  104. package/dist/esm/BookReader/utils.js +0 -315
  105. package/dist/esm/BookReader.js +0 -1828
  106. package/dist/esm/assets/icons/1up.svg +0 -12
  107. package/dist/esm/assets/icons/2up.svg +0 -15
  108. package/dist/esm/assets/icons/advance.svg +0 -26
  109. package/dist/esm/assets/icons/chevron-right.svg +0 -1
  110. package/dist/esm/assets/icons/close-circle-dark.svg +0 -1
  111. package/dist/esm/assets/icons/close-circle.svg +0 -1
  112. package/dist/esm/assets/icons/fullscreen.svg +0 -17
  113. package/dist/esm/assets/icons/fullscreen_exit.svg +0 -17
  114. package/dist/esm/assets/icons/hamburger.svg +0 -15
  115. package/dist/esm/assets/icons/left-arrow.svg +0 -12
  116. package/dist/esm/assets/icons/magnify-minus.svg +0 -12
  117. package/dist/esm/assets/icons/magnify-plus.svg +0 -13
  118. package/dist/esm/assets/icons/magnify.svg +0 -15
  119. package/dist/esm/assets/icons/pause.svg +0 -23
  120. package/dist/esm/assets/icons/play.svg +0 -22
  121. package/dist/esm/assets/icons/playback-speed.svg +0 -34
  122. package/dist/esm/assets/icons/read-aloud.svg +0 -22
  123. package/dist/esm/assets/icons/review.svg +0 -22
  124. package/dist/esm/assets/icons/thumbnails.svg +0 -17
  125. package/dist/esm/assets/icons/voice.svg +0 -1
  126. package/dist/esm/assets/icons/volume-full.svg +0 -22
  127. package/dist/esm/assets/images/BRicons.png +0 -0
  128. package/dist/esm/assets/images/BRicons.svg +0 -94
  129. package/dist/esm/assets/images/BRicons_ia.png +0 -0
  130. package/dist/esm/assets/images/back_pages.png +0 -0
  131. package/dist/esm/assets/images/book_bottom_icon.png +0 -0
  132. package/dist/esm/assets/images/book_down_icon.png +0 -0
  133. package/dist/esm/assets/images/book_left_icon.png +0 -0
  134. package/dist/esm/assets/images/book_leftmost_icon.png +0 -0
  135. package/dist/esm/assets/images/book_right_icon.png +0 -0
  136. package/dist/esm/assets/images/book_rightmost_icon.png +0 -0
  137. package/dist/esm/assets/images/book_top_icon.png +0 -0
  138. package/dist/esm/assets/images/book_up_icon.png +0 -0
  139. package/dist/esm/assets/images/books_graphic.svg +0 -177
  140. package/dist/esm/assets/images/booksplit.png +0 -0
  141. package/dist/esm/assets/images/control_pause_icon.png +0 -0
  142. package/dist/esm/assets/images/control_play_icon.png +0 -0
  143. package/dist/esm/assets/images/embed_icon.png +0 -0
  144. package/dist/esm/assets/images/icon-home-ia.png +0 -0
  145. package/dist/esm/assets/images/icon_OL-logo-xs.png +0 -0
  146. package/dist/esm/assets/images/icon_alert-xs.png +0 -0
  147. package/dist/esm/assets/images/icon_book.svg +0 -12
  148. package/dist/esm/assets/images/icon_bookmark.svg +0 -12
  149. package/dist/esm/assets/images/icon_close-pop.png +0 -0
  150. package/dist/esm/assets/images/icon_download.png +0 -0
  151. package/dist/esm/assets/images/icon_gear.svg +0 -14
  152. package/dist/esm/assets/images/icon_hamburger.svg +0 -20
  153. package/dist/esm/assets/images/icon_home.png +0 -0
  154. package/dist/esm/assets/images/icon_home.svg +0 -21
  155. package/dist/esm/assets/images/icon_home_ia.png +0 -0
  156. package/dist/esm/assets/images/icon_indicator.png +0 -0
  157. package/dist/esm/assets/images/icon_info.svg +0 -11
  158. package/dist/esm/assets/images/icon_one_page.svg +0 -8
  159. package/dist/esm/assets/images/icon_pause.svg +0 -1
  160. package/dist/esm/assets/images/icon_play.svg +0 -1
  161. package/dist/esm/assets/images/icon_playback-rate.svg +0 -15
  162. package/dist/esm/assets/images/icon_return.png +0 -0
  163. package/dist/esm/assets/images/icon_search_button.svg +0 -8
  164. package/dist/esm/assets/images/icon_share.svg +0 -9
  165. package/dist/esm/assets/images/icon_skip-ahead.svg +0 -6
  166. package/dist/esm/assets/images/icon_skip-back.svg +0 -13
  167. package/dist/esm/assets/images/icon_speaker.svg +0 -18
  168. package/dist/esm/assets/images/icon_speaker_open.svg +0 -10
  169. package/dist/esm/assets/images/icon_thumbnails.svg +0 -12
  170. package/dist/esm/assets/images/icon_toc.svg +0 -5
  171. package/dist/esm/assets/images/icon_two_pages.svg +0 -9
  172. package/dist/esm/assets/images/icon_zoomer.png +0 -0
  173. package/dist/esm/assets/images/loading.gif +0 -0
  174. package/dist/esm/assets/images/logo_icon.png +0 -0
  175. package/dist/esm/assets/images/marker_chap-off.png +0 -0
  176. package/dist/esm/assets/images/marker_chap-off.svg +0 -11
  177. package/dist/esm/assets/images/marker_chap-off_ia.png +0 -0
  178. package/dist/esm/assets/images/marker_chap-on.png +0 -0
  179. package/dist/esm/assets/images/marker_chap-on.svg +0 -11
  180. package/dist/esm/assets/images/marker_srch-on.svg +0 -11
  181. package/dist/esm/assets/images/marker_srchchap-off.png +0 -0
  182. package/dist/esm/assets/images/marker_srchchap-on.png +0 -0
  183. package/dist/esm/assets/images/nav_control-dn.png +0 -0
  184. package/dist/esm/assets/images/nav_control-dn_ia.png +0 -0
  185. package/dist/esm/assets/images/nav_control-up.png +0 -0
  186. package/dist/esm/assets/images/nav_control-up_ia.png +0 -0
  187. package/dist/esm/assets/images/nav_control.png +0 -0
  188. package/dist/esm/assets/images/one_page_mode_icon.png +0 -0
  189. package/dist/esm/assets/images/paper-badge.png +0 -0
  190. package/dist/esm/assets/images/print_icon.png +0 -0
  191. package/dist/esm/assets/images/progressbar.gif +0 -0
  192. package/dist/esm/assets/images/right_edges.png +0 -0
  193. package/dist/esm/assets/images/slider.png +0 -0
  194. package/dist/esm/assets/images/slider_ia.png +0 -0
  195. package/dist/esm/assets/images/thumbnail_mode_icon.png +0 -0
  196. package/dist/esm/assets/images/transparent.png +0 -0
  197. package/dist/esm/assets/images/two_page_mode_icon.png +0 -0
  198. package/dist/esm/assets/images/unviewable_page.png +0 -0
  199. package/dist/esm/assets/images/zoom_in_icon.png +0 -0
  200. package/dist/esm/assets/images/zoom_out_icon.png +0 -0
  201. package/dist/esm/css/BookReader.scss +0 -85
  202. package/dist/esm/css/_BRBookmarks.scss +0 -29
  203. package/dist/esm/css/_BRComponent.scss +0 -13
  204. package/dist/esm/css/_BRfloat.scss +0 -197
  205. package/dist/esm/css/_BRicon.scss +0 -54
  206. package/dist/esm/css/_BRmain.scss +0 -262
  207. package/dist/esm/css/_BRnav.scss +0 -354
  208. package/dist/esm/css/_BRpages.scss +0 -213
  209. package/dist/esm/css/_BRsearch.scss +0 -268
  210. package/dist/esm/css/_BRtoolbar.scss +0 -84
  211. package/dist/esm/css/_BRvendor.scss +0 -5
  212. package/dist/esm/css/_TextSelection.scss +0 -108
  213. package/dist/esm/css/_colorbox.scss +0 -52
  214. package/dist/esm/css/_controls.scss +0 -257
  215. package/dist/esm/css/_icons.scss +0 -121
  216. package/dist/esm/ia-bookreader/ia-bookreader.js +0 -141
  217. package/dist/esm/jquery-wrapper.js +0 -3
  218. package/dist/esm/plugins/plugin.archive_analytics.js +0 -72
  219. package/dist/esm/plugins/plugin.autoplay.js +0 -119
  220. package/dist/esm/plugins/plugin.chapters.js +0 -288
  221. package/dist/esm/plugins/plugin.iframe.js +0 -44
  222. package/dist/esm/plugins/plugin.iiif.js +0 -146
  223. package/dist/esm/plugins/plugin.resume.js +0 -66
  224. package/dist/esm/plugins/plugin.text_selection.js +0 -621
  225. package/dist/esm/plugins/plugin.vendor-fullscreen.js +0 -227
  226. package/dist/esm/plugins/search/plugin.search.js +0 -499
  227. package/dist/esm/plugins/search/utils.js +0 -42
  228. package/dist/esm/plugins/search/view.js +0 -360
  229. package/dist/esm/plugins/tts/AbstractTTSEngine.js +0 -282
  230. package/dist/esm/plugins/tts/FestivalTTSEngine.js +0 -192
  231. package/dist/esm/plugins/tts/PageChunk.js +0 -105
  232. package/dist/esm/plugins/tts/PageChunkIterator.js +0 -155
  233. package/dist/esm/plugins/tts/WebTTSEngine.js +0 -364
  234. package/dist/esm/plugins/tts/plugin.tts.js +0 -315
  235. package/dist/esm/plugins/tts/tooltip_dict.js +0 -14
  236. package/dist/esm/plugins/tts/utils.js +0 -79
  237. package/dist/esm/plugins/url/UrlPlugin.js +0 -197
  238. package/dist/esm/plugins/url/plugin.url.js +0 -212
  239. package/dist/esm/util/browserSniffing.js +0 -56
  240. package/dist/esm/util/debouncer.js +0 -25
  241. package/dist/esm/util/docCookies.js +0 -75
  242. package/dist/esm/util/strings.js +0 -34
  243. package/index.js +0 -2
@@ -1,361 +1,341 @@
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
7
  import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
8
+ import { BookReaderPlugin } from '../../BookReaderPlugin.js';
9
+ import { applyVariables } from '../../util/strings.js';
10
10
  /** @typedef {import('./PageChunk.js').default} PageChunk */
11
11
  /** @typedef {import("./AbstractTTSEngine.js").default} AbstractTTSEngine */
12
12
 
13
- // Default options for TTS
14
- jQuery.extend(BookReader.defaultOptions, {
15
- server: 'ia600609.us.archive.org',
16
- bookPath: '',
17
- enableTtsPlugin: true,
18
- });
19
-
20
- // Extend the constructor to add TTS properties
21
- BookReader.prototype.setup = (function (super_) {
22
- return function (options) {
23
- super_.call(this, options);
24
-
25
- if (this.options.enableTtsPlugin) {
26
- /** @type { {[pageIndex: number]: Array<{ l: number, r: number, t: number, b: number }>} } */
27
- this._ttsBoxesByIndex = {};
28
-
29
- let TTSEngine = WebTTSEngine.isSupported() ? WebTTSEngine :
30
- FestivalTTSEngine.isSupported() ? FestivalTTSEngine :
31
- null;
32
-
33
- if (/_forceTTSEngine=(festival|web)/.test(location.toString())) {
34
- const engineName = location.toString().match(/_forceTTSEngine=(festival|web)/)[1];
35
- TTSEngine = { festival: FestivalTTSEngine, web: WebTTSEngine }[engineName];
36
- }
13
+ const BookReader = /** @type {typeof import('../../BookReader').default} */(window.BookReader);
37
14
 
38
- if (TTSEngine) {
39
- /** @type {AbstractTTSEngine} */
40
- this.ttsEngine = new TTSEngine({
41
- server: options.server,
42
- bookPath: options.bookPath,
43
- bookLanguage: toISO6391(options.bookLanguage),
44
- onLoadingStart: this.showProgressPopup.bind(this, 'Loading audio...'),
45
- onLoadingComplete: this.removeProgressPopup.bind(this),
46
- onDone: this.ttsStop.bind(this),
47
- beforeChunkPlay: this.ttsBeforeChunkPlay.bind(this),
48
- afterChunkPlay: this.ttsSendChunkFinishedAnalyticsEvent.bind(this),
49
- });
50
- }
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
+
28
+ /**
29
+ * @override
30
+ * @param {Partial<TtsPlugin['options']>} options
31
+ **/
32
+ setup(options) {
33
+ super.setup(Object.assign({
34
+ // @deprecated support options specified in global options
35
+ server: this.br.options.server,
36
+ bookPath: this.br.options.bookPath,
37
+ }, options));
38
+
39
+ if (!this.options.enabled) return;
40
+
41
+ /** @type { {[pageIndex: number]: Array<{ l: number, r: number, t: number, b: number }>} } */
42
+ this._ttsBoxesByIndex = {};
43
+
44
+ let TTSEngine = WebTTSEngine.isSupported() ? WebTTSEngine :
45
+ FestivalTTSEngine.isSupported() ? FestivalTTSEngine :
46
+ null;
47
+
48
+ if (/_forceTTSEngine=(festival|web)/.test(location.toString())) {
49
+ const engineName = location.toString().match(/_forceTTSEngine=(festival|web)/)[1];
50
+ TTSEngine = { festival: FestivalTTSEngine, web: WebTTSEngine }[engineName];
51
51
  }
52
- };
53
- })(BookReader.prototype.setup);
54
-
55
- BookReader.prototype.init = (function(super_) {
56
- return function() {
57
- if (this.options.enableTtsPlugin) {
58
- // Bind to events
59
-
60
- this.bind(BookReader.eventNames.PostInit, () => {
61
- this.$('.BRicon.read').click(() => {
62
- this.ttsToggle();
63
- return false;
64
- });
65
- if (this.ttsEngine) {
66
- this.ttsEngine.init();
67
- if (/[?&]_autoReadAloud=show/.test(location.toString())) {
68
- this.ttsStart(false); // false flag is to initiate read aloud controls
69
- }
70
- }
71
- });
72
52
 
73
- // This is fired when the hash changes by one of the other plugins!
74
- // i.e. it will fire every time the page changes -_-
75
- // this.bind(BookReader.eventNames.stop, function(e, br) {
76
- // this.ttsStop();
77
- // }.bind(this));
53
+ if (TTSEngine) {
54
+ /** @type {AbstractTTSEngine} */
55
+ this.ttsEngine = new TTSEngine({
56
+ pageChunkUrl: applyVariables(this.options.pageChunkUrl, this.br.options.vars),
57
+ bookLanguage: toISO6391(this.br.options.bookLanguage),
58
+ onLoadingStart: this.br.showProgressPopup.bind(this.br, 'Loading audio...'),
59
+ onLoadingComplete: this.br.removeProgressPopup.bind(this.br),
60
+ onDone: this.stop.bind(this),
61
+ beforeChunkPlay: this.beforeChunkPlay.bind(this),
62
+ afterChunkPlay: this.sendChunkFinishedAnalyticsEvent.bind(this),
63
+ });
78
64
  }
79
- super_.call(this);
80
- };
81
- })(BookReader.prototype.init);
82
-
83
- /** @override */
84
- BookReader.prototype._createPageContainer = (function (super_) {
85
- return function (index) {
86
- const pageContainer = super_.call(this, index);
87
- if (this.options.enableTtsPlugin && pageContainer.page && index in this._ttsBoxesByIndex) {
65
+ }
66
+
67
+ /** @override */
68
+ init() {
69
+ if (!this.options.enabled) return;
70
+
71
+ this.br.bind(BookReader.eventNames.PostInit, () => {
72
+ this.br.$('.BRicon.read').click(() => {
73
+ this.toggle();
74
+ return false;
75
+ });
76
+ if (this.ttsEngine) {
77
+ this.ttsEngine.init();
78
+ if (/[?&]_autoReadAloud=show/.test(location.toString())) {
79
+ this.start(false); // false flag is to initiate read aloud controls
80
+ }
81
+ }
82
+ });
83
+
84
+ // This is fired when the hash changes by one of the other plugins!
85
+ // i.e. it will fire every time the page changes -_-
86
+ // this.br.bind(BookReader.eventNames.stop, function(e, br) {
87
+ // this.ttsStop();
88
+ // }.bind(this));
89
+ }
90
+
91
+ /**
92
+ * @override
93
+ * @param {import ("@/src/BookReader/PageContainer.js").PageContainer} pageContainer
94
+ */
95
+ _configurePageContainer(pageContainer) {
96
+ if (this.options.enabled && pageContainer.page && pageContainer.page.index in this._ttsBoxesByIndex) {
88
97
  const pageIndex = pageContainer.page.index;
89
98
  renderBoxesInPageContainerLayer('ttsHiliteLayer', this._ttsBoxesByIndex[pageIndex], pageContainer.page, pageContainer.$container[0]);
90
99
  }
91
- return pageContainer;
92
- };
93
- })(BookReader.prototype._createPageContainer);
94
-
95
- // Extend buildMobileDrawerElement
96
- BookReader.prototype.buildMobileDrawerElement = (function (super_) {
97
- return function () {
98
- const $el = super_.call(this);
99
- if (this.options.enableTtsPlugin && this.ttsEngine) {
100
- $el.find('.BRmobileMenu__moreInfoRow').after($(`
100
+ }
101
+
102
+ /**
103
+ * @override
104
+ * @param {JQuery<HTMLElement>} $navBar
105
+ */
106
+ extendNavBar($navBar) {
107
+ if (!this.options.enabled || !this.ttsEngine) return;
108
+
109
+ this.br.refs.$BRReadAloudToolbar = $(`
110
+ <ul class="read-aloud">
101
111
  <li>
102
- <span>
103
- <span class="DrawerIconWrapper"><img class="DrawerIcon" src="${this.imagesBaseURL}icon_speaker_open.svg" alt="info-speaker"/></span>
104
- Read Aloud
105
- </span>
106
- <div>
107
- <span class="larger">Press to toggle read aloud</span>
108
- <br/>
109
- <button class="BRicon read"></button>
110
- </div>
111
- </li>`));
112
- }
113
- return $el;
114
- };
115
- })(BookReader.prototype.buildMobileDrawerElement);
116
-
117
- // Extend initNavbar
118
- BookReader.prototype.initNavbar = (function (super_) {
119
- return function () {
120
- const $el = super_.call(this);
121
- if (this.options.enableTtsPlugin && this.ttsEngine) {
122
- this.refs.$BRReadAloudToolbar = $(`
123
- <ul class="read-aloud">
124
- <li>
125
- <select class="playback-speed" name="playback-speed" title="${tooltips.playbackSpeed}">
126
- <option value="0.25">0.25x</option>
127
- <option value="0.5">0.5x</option>
128
- <option value="0.75">0.75x</option>
129
- <option value="1.0" selected>1.0x</option>
130
- <option value="1.25">1.25x</option>
131
- <option value="1.5">1.5x</option>
132
- <option value="1.75">1.75x</option>
133
- <option value="2">2x</option>
134
- </select>
135
- </li>
136
- <li>
137
- <button type="button" name="review" title="${tooltips.review}">
138
- <div class="icon icon-review"></div>
139
- </button>
140
- </li>
141
- <li>
142
- <button type="button" name="play" title="${tooltips.play}">
143
- <div class="icon icon-play"></div>
144
- <div class="icon icon-pause"></div>
145
- </button>
146
- </li>
147
- <li>
148
- <button type="button" name="advance" title="${tooltips.advance}">
149
- <div class="icon icon-advance"></div>
150
- </button>
151
- </li>
152
- <li>
153
- <select class="playback-voices" name="playback-voice" style="display: none" title="Change read aloud voices">
154
- </select>
155
- </li>
156
- </ul>
157
- `);
158
-
159
- $el.find('.BRcontrols').prepend(this.refs.$BRReadAloudToolbar);
160
-
161
- const renderVoiceOption = (voices) => {
162
- return voices.map(voice =>
163
- `<option value="${voice.voiceURI}">${voice.lang} - ${voice.name}</option>`).join('');
164
- };
165
-
166
- const voiceSortOrder = (a,b) => `${a.lang} - ${a.name}`.localeCompare(`${b.lang} - ${b.name}`);
167
-
168
- const renderVoicesMenu = (voicesMenu) => {
169
- voicesMenu.empty();
170
- const bookLanguage = this.ttsEngine.opts.bookLanguage;
171
- const bookLanguages = this.ttsEngine.getVoices().filter(v => v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
172
- const otherLanguages = this.ttsEngine.getVoices().filter(v => !v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
173
-
174
- if (this.ttsEngine.getVoices().length > 1) {
175
- voicesMenu.append($(`<optgroup label="Book Language (${bookLanguage})"> ${renderVoiceOption(bookLanguages)} </optgroup>`));
176
- voicesMenu.append($(`<optgroup label="Other Languages"> ${renderVoiceOption(otherLanguages)} </optgroup>`));
177
-
178
- voicesMenu.val(this.ttsEngine.voice.voiceURI);
179
- voicesMenu.show();
180
- } else {
181
- voicesMenu.hide();
182
- }
183
- };
184
-
185
- const voicesMenu = this.refs.$BRReadAloudToolbar.find('[name=playback-voice]');
186
- renderVoicesMenu(voicesMenu);
187
- voicesMenu.on("change", ev => this.ttsEngine.setVoice(voicesMenu.val()));
188
- this.ttsEngine.events.on('pause resume start', () => this.ttsUpdateState());
189
- this.ttsEngine.events.on('voiceschanged', () => renderVoicesMenu(voicesMenu));
190
- this.refs.$BRReadAloudToolbar.find('[name=play]').on("click", this.ttsPlayPause.bind(this));
191
- this.refs.$BRReadAloudToolbar.find('[name=advance]').on("click", this.ttsJumpForward.bind(this));
192
- this.refs.$BRReadAloudToolbar.find('[name=review]').on("click", this.ttsJumpBackward.bind(this));
193
- const $rateSelector = this.refs.$BRReadAloudToolbar.find('select[name="playback-speed"]');
194
- $rateSelector.on("change", ev => this.ttsEngine.setPlaybackRate(parseFloat($rateSelector.val())));
195
- $(`<li>
196
- <button class="BRicon read js-tooltip" title="${tooltips.readAloud}">
197
- <div class="icon icon-read-aloud"></div>
198
- <span class="BRtooltip">${tooltips.readAloud}</span>
112
+ <select class="playback-speed" name="playback-speed" title="${tooltips.playbackSpeed}">
113
+ <option value="0.25">0.25x</option>
114
+ <option value="0.5">0.5x</option>
115
+ <option value="0.75">0.75x</option>
116
+ <option value="1.0" selected>1.0x</option>
117
+ <option value="1.25">1.25x</option>
118
+ <option value="1.5">1.5x</option>
119
+ <option value="1.75">1.75x</option>
120
+ <option value="2">2x</option>
121
+ </select>
122
+ </li>
123
+ <li>
124
+ <button type="button" name="review" title="${tooltips.review}">
125
+ <div class="icon icon-review"></div>
199
126
  </button>
200
- </li>`).insertBefore($el.find('.BRcontrols .BRicon.zoom_out').closest('li'));
127
+ </li>
128
+ <li>
129
+ <button type="button" name="play" title="${tooltips.play}">
130
+ <div class="icon icon-play"></div>
131
+ <div class="icon icon-pause"></div>
132
+ </button>
133
+ </li>
134
+ <li>
135
+ <button type="button" name="advance" title="${tooltips.advance}">
136
+ <div class="icon icon-advance"></div>
137
+ </button>
138
+ </li>
139
+ <li>
140
+ <select class="playback-voices" name="playback-voice" style="display: none" title="Change read aloud voices">
141
+ </select>
142
+ </li>
143
+ </ul>
144
+ `);
145
+
146
+ $navBar.find('.BRcontrols').prepend(this.br.refs.$BRReadAloudToolbar);
147
+
148
+ const renderVoiceOption = (voices) => {
149
+ return voices.map(voice =>
150
+ `<option value="${voice.voiceURI}">${voice.lang} - ${voice.name}</option>`).join('');
151
+ };
152
+
153
+ const voiceSortOrder = (a,b) => `${a.lang} - ${a.name}`.localeCompare(`${b.lang} - ${b.name}`);
154
+
155
+ const renderVoicesMenu = (voicesMenu) => {
156
+ voicesMenu.empty();
157
+ const bookLanguage = this.ttsEngine.opts.bookLanguage;
158
+ const bookLanguages = this.ttsEngine.getVoices().filter(v => v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
159
+ const otherLanguages = this.ttsEngine.getVoices().filter(v => !v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
160
+
161
+ if (this.ttsEngine.getVoices().length > 1) {
162
+ voicesMenu.append($(`<optgroup label="Book Language (${bookLanguage})"> ${renderVoiceOption(bookLanguages)} </optgroup>`));
163
+ voicesMenu.append($(`<optgroup label="Other Languages"> ${renderVoiceOption(otherLanguages)} </optgroup>`));
164
+
165
+ voicesMenu.val(this.ttsEngine.voice.voiceURI);
166
+ voicesMenu.show();
167
+ } else {
168
+ voicesMenu.hide();
169
+ }
170
+ };
171
+
172
+ const voicesMenu = this.br.refs.$BRReadAloudToolbar.find('[name=playback-voice]');
173
+ renderVoicesMenu(voicesMenu);
174
+ voicesMenu.on("change", ev => this.ttsEngine.setVoice(voicesMenu.val()));
175
+ this.ttsEngine.events.on('pause resume start', () => this.updateState());
176
+ this.ttsEngine.events.on('voiceschanged', () => renderVoicesMenu(voicesMenu));
177
+ this.br.refs.$BRReadAloudToolbar.find('[name=play]').on("click", this.playPause.bind(this));
178
+ this.br.refs.$BRReadAloudToolbar.find('[name=advance]').on("click", this.jumpForward.bind(this));
179
+ this.br.refs.$BRReadAloudToolbar.find('[name=review]').on("click", this.jumpBackward.bind(this));
180
+ const $rateSelector = this.br.refs.$BRReadAloudToolbar.find('select[name="playback-speed"]');
181
+ $rateSelector.on("change", ev => this.ttsEngine.setPlaybackRate(parseFloat($rateSelector.val())));
182
+ $(`<li>
183
+ <button class="BRicon read js-tooltip" title="${tooltips.readAloud}">
184
+ <div class="icon icon-read-aloud"></div>
185
+ <span class="BRtooltip">${tooltips.readAloud}</span>
186
+ </button>
187
+ </li>`).insertBefore($navBar.find('.BRcontrols .BRicon.zoom_out').closest('li'));
188
+ }
189
+
190
+ toggle() {
191
+ this.br._plugins.autoplay?.stop();
192
+ if (this.ttsEngine.playing) {
193
+ this.stop();
194
+ } else {
195
+ this.start();
201
196
  }
202
- return $el;
203
- };
204
- })(BookReader.prototype.initNavbar);
205
-
206
- // ttsToggle()
207
- //______________________________________________________________________________
208
- BookReader.prototype.ttsToggle = function () {
209
- if (this.autoStop) this.autoStop();
210
- if (this.ttsEngine.playing) {
211
- this.ttsStop();
212
- } else {
213
- this.ttsStart();
214
197
  }
215
- };
216
-
217
- // ttsStart(
218
- //______________________________________________________________________________
219
- BookReader.prototype.ttsStart = function (startTTSEngine = true) {
220
- if (this.constModeThumb == this.mode)
221
- this.switchMode(this.constMode1up);
222
-
223
- this.refs.$BRReadAloudToolbar.addClass('visible');
224
- this.$('.BRicon.read').addClass('unread active');
225
- this.ttsSendAnalyticsEvent('Start');
226
- if (startTTSEngine)
227
- this.ttsEngine.start(this.currentIndex(), this.book.getNumLeafs());
228
- };
229
-
230
- BookReader.prototype.ttsJumpForward = function () {
231
- if (this.ttsEngine.paused) {
232
- this.ttsEngine.resume();
198
+
199
+ start(startTTSEngine = true) {
200
+ if (this.br.constModeThumb == this.br.mode)
201
+ this.br.switchMode(this.br.constMode1up);
202
+
203
+ this.br.refs.$BRReadAloudToolbar.addClass('visible');
204
+ this.br.$('.BRicon.read').addClass('unread active');
205
+ this.sendAnalyticsEvent('Start');
206
+ if (startTTSEngine)
207
+ this.ttsEngine.start(this.br.currentIndex(), this.br.book.getNumLeafs());
233
208
  }
234
- this.ttsEngine.jumpForward();
235
- };
236
209
 
237
- BookReader.prototype.ttsJumpBackward = function () {
238
- if (this.ttsEngine.paused) {
239
- this.ttsEngine.resume();
210
+ jumpForward() {
211
+ if (this.ttsEngine.paused) {
212
+ this.ttsEngine.resume();
213
+ }
214
+ this.ttsEngine.jumpForward();
240
215
  }
241
- this.ttsEngine.jumpBackward();
242
- };
243
-
244
- BookReader.prototype.ttsUpdateState = function() {
245
- const isPlaying = !(this.ttsEngine.paused || !this.ttsEngine.playing);
246
- this.$('.read-aloud [name=play]').toggleClass('playing', isPlaying);
247
- };
248
-
249
- BookReader.prototype.ttsPlayPause = function() {
250
- if (!this.ttsEngine.playing) {
251
- this.ttsToggle();
252
- } else {
253
- this.ttsEngine.togglePlayPause();
254
- this.ttsUpdateState();
216
+
217
+ jumpBackward() {
218
+ if (this.ttsEngine.paused) {
219
+ this.ttsEngine.resume();
220
+ }
221
+ this.ttsEngine.jumpBackward();
255
222
  }
256
- };
257
-
258
- // ttsStop()
259
- //______________________________________________________________________________
260
- BookReader.prototype.ttsStop = function () {
261
- this.refs.$BRReadAloudToolbar.removeClass('visible');
262
- this.$('.BRicon.read').removeClass('unread active');
263
- this.ttsSendAnalyticsEvent('Stop');
264
- this.ttsEngine.stop();
265
- this.ttsRemoveHilites();
266
- this.removeProgressPopup();
267
- };
268
223
 
269
- /**
270
- * @param {PageChunk} chunk
271
- * @return {PromiseLike<void>} returns once the flip is done
272
- */
273
- BookReader.prototype.ttsBeforeChunkPlay = async function(chunk) {
274
- await this.ttsMaybeFlipToIndex(chunk.leafIndex);
275
- this.ttsHighlightChunk(chunk);
276
- this.ttsScrollToChunk(chunk);
277
- };
224
+ updateState() {
225
+ const isPlaying = !(this.ttsEngine.paused || !this.ttsEngine.playing);
226
+ this.br.$('.read-aloud [name=play]').toggleClass('playing', isPlaying);
227
+ }
278
228
 
279
- /**
280
- * @param {PageChunk} chunk
281
- */
282
- BookReader.prototype.ttsSendChunkFinishedAnalyticsEvent = function(chunk) {
283
- this.ttsSendAnalyticsEvent('ChunkFinished-Words', approximateWordCount(chunk.text));
284
- };
229
+ playPause() {
230
+ if (!this.ttsEngine.playing) {
231
+ this.toggle();
232
+ } else {
233
+ this.ttsEngine.togglePlayPause();
234
+ this.updateState();
235
+ }
236
+ }
285
237
 
286
- /**
287
- * Flip the page if the provided leaf index is not visible
288
- * @param {Number} leafIndex
289
- */
290
- BookReader.prototype.ttsMaybeFlipToIndex = async function (leafIndex) {
291
- if (this.constMode2up != this.mode) {
292
- this.jumpToIndex(leafIndex);
293
- } else {
294
- await this._modes.mode2Up.mode2UpLit.jumpToIndex(leafIndex);
238
+
239
+ stop() {
240
+ this.br.refs.$BRReadAloudToolbar.removeClass('visible');
241
+ this.br.$('.BRicon.read').removeClass('unread active');
242
+ this.sendAnalyticsEvent('Stop');
243
+ this.ttsEngine.stop();
244
+ this.removeHilites();
245
+ this.br.removeProgressPopup();
295
246
  }
296
- };
297
247
 
298
- /**
299
- * @param {PageChunk} chunk
300
- */
301
- BookReader.prototype.ttsHighlightChunk = function(chunk) {
302
- // The poorly-named variable leafIndex
303
- const pageIndex = chunk.leafIndex;
304
-
305
- this.ttsRemoveHilites();
306
-
307
- // group by index; currently only possible to have chunks on one page :/
308
- this._ttsBoxesByIndex = {
309
- [pageIndex]: chunk.lineRects.map(([l, b, r, t]) => ({l, r, b, t})),
310
- };
311
-
312
- // update any already created pages
313
- for (const [pageIndexString, boxes] of Object.entries(this._ttsBoxesByIndex)) {
314
- const pageIndex = parseFloat(pageIndexString);
315
- const page = this.book.getPage(pageIndex);
316
- const pageContainers = this.getActivePageContainerElementsForIndex(pageIndex);
317
- pageContainers.forEach(container => renderBoxesInPageContainerLayer('ttsHiliteLayer', boxes, page, container));
248
+ /**
249
+ * @param {PageChunk} chunk
250
+ * Returns once the flip is done
251
+ */
252
+ async beforeChunkPlay(chunk) {
253
+ await this.maybeFlipToIndex(chunk.leafIndex);
254
+ this.highlightChunk(chunk);
255
+ this.scrollToChunk(chunk);
318
256
  }
319
- };
320
257
 
321
- /**
322
- * @param {PageChunk} chunk
323
- */
324
- BookReader.prototype.ttsScrollToChunk = function(chunk) {
325
- // It behaves weird if used in thumb mode
326
- if (this.constModeThumb == this.mode) return;
327
-
328
- $(`.pagediv${chunk.leafIndex} .ttsHiliteLayer rect`).last()?.[0]?.scrollIntoView({
329
- // Only vertically center the highlight if we're in 1up or in full screen. In
330
- // 2up, if we're not fullscreen, the whole body gets scrolled around to try to
331
- // center the highlight 🙄 See:
332
- // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move/11041376
333
- // Note: nearest doesn't quite work great, because the ReadAloud toolbar is now
334
- // full-width, and covers up the last line of the highlight.
335
- block: this.constMode1up == this.mode || this.isFullscreenActive ? 'center' : 'nearest',
336
- inline: 'center',
337
- behavior: 'smooth',
338
- });
339
- };
340
-
341
- // ttsRemoveHilites()
342
- //______________________________________________________________________________
343
- BookReader.prototype.ttsRemoveHilites = function () {
344
- $(this.getActivePageContainerElements()).find('.ttsHiliteLayer').remove();
345
- this._ttsBoxesByIndex = {};
346
- };
258
+ /**
259
+ * @param {PageChunk} chunk
260
+ */
261
+ sendChunkFinishedAnalyticsEvent(chunk) {
262
+ this.sendAnalyticsEvent('ChunkFinished-Words', approximateWordCount(chunk.text));
263
+ }
347
264
 
348
- /**
349
- * @private
350
- * Send an analytics event with an optional value. Also attaches the book's language.
351
- * @param {string} action
352
- * @param {number} [value]
353
- */
354
- BookReader.prototype.ttsSendAnalyticsEvent = function(action, value) {
355
- if (this.archiveAnalyticsSendEvent) {
356
- const extraValues = {};
357
- const mediaLanguage = this.ttsEngine.opts.bookLanguage;
358
- if (mediaLanguage) extraValues.mediaLanguage = mediaLanguage;
359
- this.archiveAnalyticsSendEvent('BRReadAloud', action, value, extraValues);
265
+ /**
266
+ * Flip the page if the provided leaf index is not visible
267
+ * @param {Number} leafIndex
268
+ */
269
+ async maybeFlipToIndex(leafIndex) {
270
+ if (this.br.constMode2up != this.br.mode) {
271
+ this.br.jumpToIndex(leafIndex);
272
+ } else {
273
+ await this.br._modes.mode2Up.mode2UpLit.jumpToIndex(leafIndex);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * @param {PageChunk} chunk
279
+ */
280
+ highlightChunk(chunk) {
281
+ // The poorly-named variable leafIndex
282
+ const pageIndex = chunk.leafIndex;
283
+
284
+ this.removeHilites();
285
+
286
+ // group by index; currently only possible to have chunks on one page :/
287
+ this._ttsBoxesByIndex = {
288
+ [pageIndex]: chunk.lineRects.map(([l, b, r, t]) => ({l, r, b, t})),
289
+ };
290
+
291
+ // update any already created pages
292
+ for (const [pageIndexString, boxes] of Object.entries(this._ttsBoxesByIndex)) {
293
+ const pageIndex = parseFloat(pageIndexString);
294
+ const page = this.br.book.getPage(pageIndex);
295
+ const pageContainers = this.br.getActivePageContainerElementsForIndex(pageIndex);
296
+ pageContainers.forEach(container => renderBoxesInPageContainerLayer('ttsHiliteLayer', boxes, page, container));
297
+ }
360
298
  }
361
- };
299
+
300
+ /**
301
+ * @param {PageChunk} chunk
302
+ */
303
+ scrollToChunk(chunk) {
304
+ // It behaves weird if used in thumb mode
305
+ if (this.br.constModeThumb == this.br.mode) return;
306
+
307
+ $(`.pagediv${chunk.leafIndex} .ttsHiliteLayer rect`).last()?.[0]?.scrollIntoView({
308
+ // Only vertically center the highlight if we're in 1up or in full screen. In
309
+ // 2up, if we're not fullscreen, the whole body gets scrolled around to try to
310
+ // center the highlight 🙄 See:
311
+ // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move/11041376
312
+ // Note: nearest doesn't quite work great, because the ReadAloud toolbar is now
313
+ // full-width, and covers up the last line of the highlight.
314
+ block: this.br.constMode1up == this.br.mode || this.br.isFullscreenActive ? 'center' : 'nearest',
315
+ inline: 'center',
316
+ behavior: 'smooth',
317
+ });
318
+ }
319
+
320
+ removeHilites() {
321
+ $(this.br.getActivePageContainerElements()).find('.ttsHiliteLayer').remove();
322
+ this._ttsBoxesByIndex = {};
323
+ }
324
+
325
+ /**
326
+ * @private
327
+ * Send an analytics event with an optional value. Also attaches the book's language.
328
+ * @param {string} action
329
+ * @param {number} [value]
330
+ */
331
+ sendAnalyticsEvent(action, value) {
332
+ if (this.br._plugins.archiveAnalytics) {
333
+ const extraValues = {};
334
+ const mediaLanguage = this.ttsEngine.opts.bookLanguage;
335
+ if (mediaLanguage) extraValues.mediaLanguage = mediaLanguage;
336
+ this.br._plugins.archiveAnalytics.sendEvent('BRReadAloud', action, value, extraValues);
337
+ }
338
+ }
339
+ }
340
+
341
+ BookReader?.registerPlugin('tts', TtsPlugin);