@internetarchive/bookreader 5.0.0-26 → 5.0.0-29

Sign up to get free protection for your applications and to get access to all the features.
Files changed (236) hide show
  1. package/.husky/_/husky.sh +30 -0
  2. package/BookReader/BookReader.css +1 -1
  3. package/BookReader/BookReader.js +1 -1
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/bookreader-component-bundle.js +570 -542
  6. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +23 -0
  7. package/BookReader/bookreader-component-bundle.js.map +1 -1
  8. package/BookReader/plugins/plugin.search.js +1 -1
  9. package/BookReader/plugins/plugin.search.js.map +1 -1
  10. package/BookReader/plugins/plugin.tts.js.map +1 -1
  11. package/BookReader/plugins/plugin.url.js +1 -1
  12. package/BookReader/plugins/plugin.url.js.map +1 -1
  13. package/BookReaderDemo/BookReaderDemo.css +14 -1
  14. package/BookReaderDemo/IADemoBr.js +104 -0
  15. package/BookReaderDemo/demo-internetarchive.html +65 -98
  16. package/CHANGELOG.md +10 -0
  17. package/package.json +9 -6
  18. package/src/BookNavigator/assets/ia-logo.js +17 -0
  19. package/src/BookNavigator/book-navigator.js +521 -0
  20. package/src/BookNavigator/bookmarks/bookmark-button.js +2 -1
  21. package/src/BookNavigator/bookmarks/bookmarks-provider.js +20 -8
  22. package/src/BookNavigator/bookmarks/ia-bookmarks.js +84 -51
  23. package/src/BookNavigator/downloads/downloads-provider.js +5 -9
  24. package/src/BookNavigator/downloads/downloads.js +1 -0
  25. package/src/BookNavigator/search/search-provider.js +15 -8
  26. package/src/BookNavigator/sharing.js +27 -0
  27. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +9 -8
  28. package/src/BookNavigator/volumes/volumes-provider.js +44 -13
  29. package/src/BookNavigator/volumes/volumes.js +14 -3
  30. package/src/BookReader/options.js +6 -0
  31. package/src/BookReader.js +20 -8
  32. package/src/BookReaderComponent/BookReaderComponent.js +53 -32
  33. package/src/css/_BRComponent.scss +1 -1
  34. package/src/plugins/search/plugin.search.js +10 -9
  35. package/src/plugins/tts/FestivalTTSEngine.js +1 -1
  36. package/src/plugins/url/UrlPlugin.js +184 -0
  37. package/src/plugins/url/plugin.url.js +220 -0
  38. package/{src → stat}/BookNavigator/BookModel.js +0 -0
  39. package/{src → stat}/BookNavigator/BookNavigator.js +109 -102
  40. package/stat/BookNavigator/assets/bookmark-colors.js +15 -0
  41. package/stat/BookNavigator/assets/button-base.js +61 -0
  42. package/stat/BookNavigator/assets/ia-logo.js +17 -0
  43. package/stat/BookNavigator/assets/icon_checkmark.js +6 -0
  44. package/stat/BookNavigator/assets/icon_close.js +3 -0
  45. package/stat/BookNavigator/assets/icon_sort_asc.js +5 -0
  46. package/stat/BookNavigator/assets/icon_sort_desc.js +5 -0
  47. package/stat/BookNavigator/assets/icon_sort_neutral.js +5 -0
  48. package/stat/BookNavigator/assets/icon_volumes.js +11 -0
  49. package/stat/BookNavigator/bookmarks/bookmark-button.js +64 -0
  50. package/stat/BookNavigator/bookmarks/bookmark-edit.js +215 -0
  51. package/stat/BookNavigator/bookmarks/bookmarks-list.js +285 -0
  52. package/stat/BookNavigator/bookmarks/bookmarks-loginCTA.js +28 -0
  53. package/stat/BookNavigator/bookmarks/bookmarks-provider.js +56 -0
  54. package/stat/BookNavigator/bookmarks/ia-bookmarks.js +523 -0
  55. package/{src → stat}/BookNavigator/br-fullscreen-mgr.js +1 -2
  56. package/stat/BookNavigator/delete-modal-actions.js +49 -0
  57. package/stat/BookNavigator/downloads/downloads-provider.js +72 -0
  58. package/stat/BookNavigator/downloads/downloads.js +139 -0
  59. package/stat/BookNavigator/provider-config.js +0 -0
  60. package/stat/BookNavigator/search/a-search-result.js +55 -0
  61. package/stat/BookNavigator/search/search-provider.js +180 -0
  62. package/stat/BookNavigator/search/search-results.js +360 -0
  63. package/{src/ItemNavigator/providers → stat/BookNavigator}/sharing.js +3 -5
  64. package/stat/BookNavigator/visual-adjustments/visual-adjustments-provider.js +94 -0
  65. package/stat/BookNavigator/visual-adjustments/visual-adjustments.js +280 -0
  66. package/stat/BookNavigator/volumes/volumes-provider.js +83 -0
  67. package/stat/BookNavigator/volumes/volumes.js +178 -0
  68. package/stat/BookReader/BookModel.js +518 -0
  69. package/stat/BookReader/DebugConsole.js +54 -0
  70. package/stat/BookReader/DragScrollable.js +233 -0
  71. package/stat/BookReader/ImageCache.js +116 -0
  72. package/stat/BookReader/Mode1Up.js +102 -0
  73. package/stat/BookReader/Mode1UpLit.js +434 -0
  74. package/stat/BookReader/Mode2Up.js +1372 -0
  75. package/stat/BookReader/ModeSmoothZoom.js +177 -0
  76. package/stat/BookReader/ModeThumb.js +344 -0
  77. package/stat/BookReader/Navbar/Navbar.js +310 -0
  78. package/stat/BookReader/PageContainer.js +120 -0
  79. package/stat/BookReader/ReduceSet.js +26 -0
  80. package/stat/BookReader/Toolbar/Toolbar.js +384 -0
  81. package/stat/BookReader/events.js +20 -0
  82. package/stat/BookReader/options.js +324 -0
  83. package/stat/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  84. package/stat/BookReader/utils/classes.js +36 -0
  85. package/stat/BookReader/utils.js +240 -0
  86. package/stat/BookReader.js +2550 -0
  87. package/stat/BookReaderComponent/BookReaderComponent.js +117 -0
  88. package/stat/assets/icons/1up.svg +12 -0
  89. package/stat/assets/icons/2up.svg +15 -0
  90. package/stat/assets/icons/advance.svg +26 -0
  91. package/stat/assets/icons/chevron-right.svg +1 -0
  92. package/stat/assets/icons/close-circle-dark.svg +1 -0
  93. package/stat/assets/icons/close-circle.svg +1 -0
  94. package/stat/assets/icons/fullscreen.svg +17 -0
  95. package/stat/assets/icons/fullscreen_exit.svg +17 -0
  96. package/stat/assets/icons/hamburger.svg +15 -0
  97. package/stat/assets/icons/left-arrow.svg +12 -0
  98. package/stat/assets/icons/magnify-minus.svg +16 -0
  99. package/stat/assets/icons/magnify-plus.svg +17 -0
  100. package/stat/assets/icons/magnify.svg +15 -0
  101. package/stat/assets/icons/pause.svg +23 -0
  102. package/stat/assets/icons/play.svg +22 -0
  103. package/stat/assets/icons/playback-speed.svg +34 -0
  104. package/stat/assets/icons/read-aloud.svg +22 -0
  105. package/stat/assets/icons/review.svg +22 -0
  106. package/stat/assets/icons/thumbnails.svg +17 -0
  107. package/stat/assets/icons/voice.svg +1 -0
  108. package/stat/assets/icons/volume-full.svg +22 -0
  109. package/stat/assets/images/BRicons.png +0 -0
  110. package/stat/assets/images/BRicons.svg +94 -0
  111. package/stat/assets/images/BRicons_ia.png +0 -0
  112. package/stat/assets/images/back_pages.png +0 -0
  113. package/stat/assets/images/book_bottom_icon.png +0 -0
  114. package/stat/assets/images/book_down_icon.png +0 -0
  115. package/stat/assets/images/book_left_icon.png +0 -0
  116. package/stat/assets/images/book_leftmost_icon.png +0 -0
  117. package/stat/assets/images/book_right_icon.png +0 -0
  118. package/stat/assets/images/book_rightmost_icon.png +0 -0
  119. package/stat/assets/images/book_top_icon.png +0 -0
  120. package/stat/assets/images/book_up_icon.png +0 -0
  121. package/stat/assets/images/books_graphic.svg +177 -0
  122. package/stat/assets/images/booksplit.png +0 -0
  123. package/stat/assets/images/control_pause_icon.png +0 -0
  124. package/stat/assets/images/control_play_icon.png +0 -0
  125. package/stat/assets/images/embed_icon.png +0 -0
  126. package/stat/assets/images/icon-home-ia.png +0 -0
  127. package/stat/assets/images/icon_OL-logo-xs.png +0 -0
  128. package/stat/assets/images/icon_alert-xs.png +0 -0
  129. package/stat/assets/images/icon_book.svg +12 -0
  130. package/stat/assets/images/icon_bookmark.svg +12 -0
  131. package/stat/assets/images/icon_close-pop.png +0 -0
  132. package/stat/assets/images/icon_download.png +0 -0
  133. package/stat/assets/images/icon_gear.svg +14 -0
  134. package/stat/assets/images/icon_hamburger.svg +20 -0
  135. package/stat/assets/images/icon_home.png +0 -0
  136. package/stat/assets/images/icon_home.svg +21 -0
  137. package/stat/assets/images/icon_home_ia.png +0 -0
  138. package/stat/assets/images/icon_indicator.png +0 -0
  139. package/stat/assets/images/icon_info.svg +11 -0
  140. package/stat/assets/images/icon_one_page.svg +8 -0
  141. package/stat/assets/images/icon_pause.svg +1 -0
  142. package/stat/assets/images/icon_play.svg +1 -0
  143. package/stat/assets/images/icon_playback-rate.svg +15 -0
  144. package/stat/assets/images/icon_return.png +0 -0
  145. package/stat/assets/images/icon_search_button.svg +8 -0
  146. package/stat/assets/images/icon_share.svg +9 -0
  147. package/stat/assets/images/icon_skip-ahead.svg +6 -0
  148. package/stat/assets/images/icon_skip-back.svg +13 -0
  149. package/stat/assets/images/icon_speaker.svg +18 -0
  150. package/stat/assets/images/icon_speaker_open.svg +10 -0
  151. package/stat/assets/images/icon_thumbnails.svg +12 -0
  152. package/stat/assets/images/icon_toc.svg +5 -0
  153. package/stat/assets/images/icon_two_pages.svg +9 -0
  154. package/stat/assets/images/icon_zoomer.png +0 -0
  155. package/stat/assets/images/loading.gif +0 -0
  156. package/stat/assets/images/logo_icon.png +0 -0
  157. package/stat/assets/images/marker_chap-off.png +0 -0
  158. package/stat/assets/images/marker_chap-off.svg +11 -0
  159. package/stat/assets/images/marker_chap-off_ia.png +0 -0
  160. package/stat/assets/images/marker_chap-on.png +0 -0
  161. package/stat/assets/images/marker_chap-on.svg +11 -0
  162. package/stat/assets/images/marker_srch-on.svg +11 -0
  163. package/stat/assets/images/marker_srchchap-off.png +0 -0
  164. package/stat/assets/images/marker_srchchap-on.png +0 -0
  165. package/stat/assets/images/nav_control-dn.png +0 -0
  166. package/stat/assets/images/nav_control-dn_ia.png +0 -0
  167. package/stat/assets/images/nav_control-up.png +0 -0
  168. package/stat/assets/images/nav_control-up_ia.png +0 -0
  169. package/stat/assets/images/nav_control.png +0 -0
  170. package/stat/assets/images/one_page_mode_icon.png +0 -0
  171. package/stat/assets/images/paper-badge.png +0 -0
  172. package/stat/assets/images/print_icon.png +0 -0
  173. package/stat/assets/images/progressbar.gif +0 -0
  174. package/stat/assets/images/right_edges.png +0 -0
  175. package/stat/assets/images/slider.png +0 -0
  176. package/stat/assets/images/slider_ia.png +0 -0
  177. package/stat/assets/images/thumbnail_mode_icon.png +0 -0
  178. package/stat/assets/images/transparent.png +0 -0
  179. package/stat/assets/images/two_page_mode_icon.png +0 -0
  180. package/stat/assets/images/zoom_in_icon.png +0 -0
  181. package/stat/assets/images/zoom_out_icon.png +0 -0
  182. package/stat/css/BookReader.scss +89 -0
  183. package/stat/css/_BRBookmarks.scss +29 -0
  184. package/stat/css/_BRComponent.scss +13 -0
  185. package/stat/css/_BRfloat.scss +197 -0
  186. package/stat/css/_BRicon.scss +48 -0
  187. package/stat/css/_BRmain.scss +251 -0
  188. package/stat/css/_BRnav.scss +359 -0
  189. package/stat/css/_BRpages.scss +139 -0
  190. package/stat/css/_BRsearch.scss +226 -0
  191. package/stat/css/_BRtoolbar.scss +84 -0
  192. package/stat/css/_BRvendor.scss +5 -0
  193. package/stat/css/_MobileNav.scss +194 -0
  194. package/stat/css/_TextSelection.scss +32 -0
  195. package/stat/css/_colorbox.scss +52 -0
  196. package/stat/css/_controls.scss +253 -0
  197. package/stat/css/_icons.scss +121 -0
  198. package/stat/jquery-wrapper.js +4 -0
  199. package/stat/plugins/plugin.archive_analytics.js +86 -0
  200. package/stat/plugins/plugin.autoplay.js +129 -0
  201. package/stat/plugins/plugin.chapters.js +248 -0
  202. package/stat/plugins/plugin.iframe.js +48 -0
  203. package/stat/plugins/plugin.mobile_nav.js +288 -0
  204. package/stat/plugins/plugin.resume.js +68 -0
  205. package/stat/plugins/plugin.text_selection.js +291 -0
  206. package/{src → stat}/plugins/plugin.url.js +0 -0
  207. package/stat/plugins/plugin.vendor-fullscreen.js +247 -0
  208. package/stat/plugins/search/plugin.search.js +439 -0
  209. package/stat/plugins/search/view.js +439 -0
  210. package/stat/plugins/tts/AbstractTTSEngine.js +249 -0
  211. package/stat/plugins/tts/FestivalTTSEngine.js +169 -0
  212. package/stat/plugins/tts/PageChunk.js +107 -0
  213. package/stat/plugins/tts/PageChunkIterator.js +163 -0
  214. package/stat/plugins/tts/WebTTSEngine.js +357 -0
  215. package/stat/plugins/tts/plugin.tts.js +357 -0
  216. package/stat/plugins/tts/tooltip_dict.js +15 -0
  217. package/stat/plugins/tts/utils.js +91 -0
  218. package/stat/util/browserSniffing.js +30 -0
  219. package/stat/util/debouncer.js +26 -0
  220. package/stat/util/docCookies.js +67 -0
  221. package/stat/util/strings.js +34 -0
  222. package/tests/e2e/viewmode.test.js +30 -30
  223. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +64 -52
  224. package/tests/jest/BookReader.test.js +1 -1
  225. package/tests/jest/plugins/url/UrlPlugin.test.js +175 -0
  226. package/tests/jest/plugins/{plugin.url.test.js → url/plugin.url.test.js} +3 -2
  227. package/tests/karma/BookNavigator/book-navigator.test.js +413 -108
  228. package/tests/karma/BookNavigator/bookmarks/bookmark-button.test.js +44 -0
  229. package/tests/karma/BookNavigator/downloads/downloads-provider.test.js +6 -3
  230. package/tests/karma/BookNavigator/search/search-provider.test.js +106 -6
  231. package/tests/karma/BookNavigator/search/search-results.test.js +0 -2
  232. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +29 -20
  233. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +46 -22
  234. package/webpack.config.js +1 -1
  235. package/src/BookNavigator/assets/book-loader.js +0 -27
  236. package/src/ItemNavigator/ItemNavigator.js +0 -377
@@ -0,0 +1,357 @@
1
+ /* global BookReader */
2
+ /**
3
+ * Plugin for Text to Speech in BookReader
4
+ */
5
+ import FestivalTTSEngine from './FestivalTTSEngine.js';
6
+ import WebTTSEngine from './WebTTSEngine.js';
7
+ import { toISO6391, approximateWordCount } from './utils.js';
8
+ import { en as tooltips } from './tooltip_dict.js';
9
+ import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
10
+ /** @typedef {import('./PageChunk.js').default} PageChunk */
11
+ /** @typedef {import("./AbstractTTSEngine.js").default} AbstractTTSEngine */
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
+ }
37
+
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
+ }
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
+
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));
78
+ }
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) {
88
+ const pageIndex = pageContainer.page.index;
89
+ renderBoxesInPageContainerLayer('ttsHiliteLayer', this._ttsBoxesByIndex[pageIndex], pageContainer.page, pageContainer.$container[0]);
90
+ }
91
+ return pageContainer;
92
+ };
93
+ })(BookReader.prototype._createPageContainer);
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($(`
101
+ <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 renderVoicesMenu = (voicesMenu) => {
162
+ voicesMenu.empty();
163
+ if (this.ttsEngine.getVoices().length > 1) {
164
+ voicesMenu.append(this.ttsEngine.getVoices().map(
165
+ voice =>
166
+ $(`<option value="${voice.voiceURI}">${voice.lang} - ${voice.name}</option>`)));
167
+ voicesMenu.val(this.ttsEngine.voice.voiceURI);
168
+ voicesMenu.show();
169
+ } else {
170
+ voicesMenu.hide();
171
+ }
172
+ };
173
+ const voicesMenu = this.refs.$BRReadAloudToolbar.find('[name=playback-voice]');
174
+ renderVoicesMenu(voicesMenu);
175
+ voicesMenu.on("change", ev => this.ttsEngine.setVoice(voicesMenu.val()));
176
+ this.ttsEngine.events.on('pause resume start', () => this.ttsUpdateState());
177
+ this.ttsEngine.events.on('voiceschanged', () => renderVoicesMenu(voicesMenu));
178
+ this.refs.$BRReadAloudToolbar.find('[name=play]').on("click", this.ttsPlayPause.bind(this));
179
+ this.refs.$BRReadAloudToolbar.find('[name=advance]').on("click", this.ttsJumpForward.bind(this));
180
+ this.refs.$BRReadAloudToolbar.find('[name=review]').on("click", this.ttsJumpBackward.bind(this));
181
+ const $rateSelector = this.refs.$BRReadAloudToolbar.find('select[name="playback-speed"]');
182
+ $rateSelector.on("change", ev => this.ttsEngine.setPlaybackRate(parseFloat($rateSelector.val())));
183
+ $(`<li>
184
+ <button class="BRicon read js-tooltip" title="${tooltips.readAloud}">
185
+ <div class="icon icon-read-aloud"></div>
186
+ <span class="tooltip">${tooltips.readAloud}</span>
187
+ </button>
188
+ </li>`).insertBefore($el.find('.BRcontrols .BRicon.zoom_out').closest('li'));
189
+ }
190
+ return $el;
191
+ };
192
+ })(BookReader.prototype.initNavbar);
193
+
194
+ // ttsToggle()
195
+ //______________________________________________________________________________
196
+ BookReader.prototype.ttsToggle = function () {
197
+ if (this.autoStop) this.autoStop();
198
+ if (this.ttsEngine.playing) {
199
+ this.ttsStop();
200
+ } else {
201
+ this.ttsStart();
202
+ }
203
+ };
204
+
205
+ // ttsStart(
206
+ //______________________________________________________________________________
207
+ BookReader.prototype.ttsStart = function (startTTSEngine = true) {
208
+ if (this.constModeThumb == this.mode)
209
+ this.switchMode(this.constMode1up);
210
+
211
+ this.refs.$BRReadAloudToolbar.addClass('visible');
212
+ this.$('.BRicon.read').addClass('unread active');
213
+ this.ttsSendAnalyticsEvent('Start');
214
+ if (startTTSEngine)
215
+ this.ttsEngine.start(this.currentIndex(), this.getNumLeafs());
216
+ };
217
+
218
+ BookReader.prototype.ttsJumpForward = function () {
219
+ if (this.ttsEngine.paused) {
220
+ this.ttsEngine.resume();
221
+ }
222
+ this.ttsEngine.jumpForward();
223
+ };
224
+
225
+ BookReader.prototype.ttsJumpBackward = function () {
226
+ if (this.ttsEngine.paused) {
227
+ this.ttsEngine.resume();
228
+ }
229
+ this.ttsEngine.jumpBackward();
230
+ };
231
+
232
+ BookReader.prototype.ttsUpdateState = function() {
233
+ const isPlaying = !(this.ttsEngine.paused || !this.ttsEngine.playing);
234
+ this.$('.read-aloud [name=play]').toggleClass('playing', isPlaying);
235
+ };
236
+
237
+ BookReader.prototype.ttsPlayPause = function() {
238
+ if (!this.ttsEngine.playing) {
239
+ this.ttsToggle();
240
+ } else {
241
+ this.ttsEngine.togglePlayPause();
242
+ this.ttsUpdateState(this.ttsEngine.paused);
243
+ }
244
+ };
245
+
246
+ // ttsStop()
247
+ //______________________________________________________________________________
248
+ BookReader.prototype.ttsStop = function () {
249
+ this.refs.$BRReadAloudToolbar.removeClass('visible');
250
+ this.$('.BRicon.read').removeClass('unread active');
251
+ this.ttsSendAnalyticsEvent('Stop');
252
+ this.ttsEngine.stop();
253
+ this.ttsRemoveHilites();
254
+ this.removeProgressPopup();
255
+ };
256
+
257
+ /**
258
+ * @param {PageChunk} chunk
259
+ * @return {PromiseLike<void>} returns once the flip is done
260
+ */
261
+ BookReader.prototype.ttsBeforeChunkPlay = async function(chunk) {
262
+ await this.ttsMaybeFlipToIndex(chunk.leafIndex);
263
+ this.ttsHighlightChunk(chunk);
264
+ // This appears not to work; ttsMaybeFlipToIndex causes a scroll to the top of
265
+ // the active page :/ Disabling cause the extra scroll just adds an odd jitter.
266
+ // this.ttsScrollToChunk(chunk);
267
+ };
268
+
269
+ /**
270
+ * @param {PageChunk} chunk
271
+ */
272
+ BookReader.prototype.ttsSendChunkFinishedAnalyticsEvent = function(chunk) {
273
+ this.ttsSendAnalyticsEvent('ChunkFinished-Words', approximateWordCount(chunk.text));
274
+ };
275
+
276
+ /**
277
+ * Flip the page if the provided leaf index is not visible
278
+ * @param {Number} leafIndex
279
+ * @return {PromiseLike<void>} resolves once the flip animation has completed
280
+ */
281
+ BookReader.prototype.ttsMaybeFlipToIndex = function (leafIndex) {
282
+ const in2PageMode = this.constMode2up == this.mode;
283
+ let resolve = null;
284
+ const promise = new Promise(res => resolve = res);
285
+
286
+ if (!in2PageMode) {
287
+ this.jumpToIndex(leafIndex);
288
+ resolve();
289
+ } else {
290
+ const leafVisible = leafIndex == this.twoPage.currentIndexR || leafIndex == this.twoPage.currentIndexL;
291
+ if (leafVisible) {
292
+ resolve();
293
+ } else {
294
+ this.animationFinishedCallback = resolve;
295
+ const mustGoNext = leafIndex > Math.max(this.twoPage.currentIndexR, this.twoPage.currentIndexL);
296
+ if (mustGoNext) this.next();
297
+ else this.prev();
298
+ promise.then(this.ttsMaybeFlipToIndex.bind(this, leafIndex));
299
+ }
300
+ }
301
+
302
+ return promise;
303
+ };
304
+
305
+ /**
306
+ * @param {PageChunk} chunk
307
+ */
308
+ BookReader.prototype.ttsHighlightChunk = function(chunk) {
309
+ // The poorly-named variable leafIndex
310
+ const pageIndex = chunk.leafIndex;
311
+
312
+ this.ttsRemoveHilites();
313
+
314
+ // group by index; currently only possible to have chunks on one page :/
315
+ this._ttsBoxesByIndex = {
316
+ [pageIndex]: chunk.lineRects.map(([l, b, r, t]) => ({l, r, b, t}))
317
+ };
318
+
319
+ // update any already created pages
320
+ for (const [pageIndexString, boxes] of Object.entries(this._ttsBoxesByIndex)) {
321
+ const pageIndex = parseFloat(pageIndexString);
322
+ const page = this._models.book.getPage(pageIndex);
323
+ const pageContainers = this.getActivePageContainerElementsForIndex(pageIndex);
324
+ pageContainers.forEach(container => renderBoxesInPageContainerLayer('ttsHiliteLayer', boxes, page, container));
325
+ }
326
+ };
327
+
328
+ /**
329
+ * @param {PageChunk} chunk
330
+ */
331
+ BookReader.prototype.ttsScrollToChunk = function(chunk) {
332
+ if (this.constMode1up != this.mode) return;
333
+
334
+ $(`.pagediv${chunk.leafIndex} .ttsHiliteLayer rect`)[0]?.scrollIntoView();
335
+ };
336
+
337
+ // ttsRemoveHilites()
338
+ //______________________________________________________________________________
339
+ BookReader.prototype.ttsRemoveHilites = function () {
340
+ $(this.getActivePageContainerElements()).find('.ttsHiliteLayer').remove();
341
+ this._ttsBoxesByIndex = {};
342
+ };
343
+
344
+ /**
345
+ * @private
346
+ * Send an analytics event with an optional value. Also attaches the book's language.
347
+ * @param {string} action
348
+ * @param {number} [value]
349
+ */
350
+ BookReader.prototype.ttsSendAnalyticsEvent = function(action, value) {
351
+ if (this.archiveAnalyticsSendEvent) {
352
+ const extraValues = {};
353
+ const mediaLanguage = this.ttsEngine.opts.bookLanguage;
354
+ if (mediaLanguage) extraValues.mediaLanguage = mediaLanguage;
355
+ this.archiveAnalyticsSendEvent('BRReadAloud', action, value, extraValues);
356
+ }
357
+ };
@@ -0,0 +1,15 @@
1
+ export const en = {
2
+ advance: 'Advance 10 seconds',
3
+ play: 'Play',
4
+ playbackSpeed: 'Playback speed',
5
+ readAloud: 'Read this book aloud',
6
+ review: 'Review 10 seconds',
7
+ };
8
+
9
+ export const es = {
10
+ advance: 'Avance 10 segundos',
11
+ play: 'Jugar',
12
+ playbackSpeed: 'Velocidad de reproducción',
13
+ readAloud: 'Lee este libro en voz alta',
14
+ review: 'Revisar 10 segundos',
15
+ };
@@ -0,0 +1,91 @@
1
+ import langs from 'iso-language-codes/js/data.js';
2
+
3
+ /**
4
+ * Convert a EventTarget style event into a promise
5
+ * @param {EventTarget} target
6
+ * @param {string} eventType
7
+ * @return {Promise<Event>}
8
+ */
9
+ export function promisifyEvent(target, eventType) {
10
+ return new Promise(res => {
11
+ const resolver = ev => {
12
+ target.removeEventListener(eventType, resolver);
13
+ res(ev);
14
+ };
15
+ target.addEventListener(eventType, resolver);
16
+ });
17
+ }
18
+
19
+ /**
20
+ * Use regex to approximate word count in a string
21
+ * @param {string} text
22
+ * @return {number}
23
+ */
24
+ export function approximateWordCount(text) {
25
+ const m = text.match(/\S+/g);
26
+ return m ? m.length : 0;
27
+ }
28
+
29
+ /**
30
+ * Waits the provided number of ms and then resolves with a promise
31
+ * @param {number} ms
32
+ * @return {Promise}
33
+ */
34
+ export function sleep(ms) {
35
+ return new Promise(res => setTimeout(res, ms));
36
+ }
37
+
38
+ /**
39
+ * Checks whether the current browser is on android
40
+ * @param {string} [userAgent]
41
+ * @return {boolean}
42
+ */
43
+ export function isAndroid(userAgent = navigator.userAgent) {
44
+ return /android/i.test(userAgent);
45
+ }
46
+
47
+ /**
48
+ * @typedef {string} ISO6391
49
+ * Language code in ISO 639-1 format. e.g. en, fr, zh
50
+ **/
51
+
52
+ /** Each lang is an array, with each index mapping to a different property */
53
+ const COLUMN_TO_LANG_INDEX = {
54
+ 'Name': 0,
55
+ 'Endonym': 1,
56
+ 'ISO 639-1': 2,
57
+ 'ISO 639-2/T': 3,
58
+ 'ISO 639-2/B': 4
59
+ };
60
+
61
+ /**
62
+ * @param {string} language in some format
63
+ * @return {ISO6391?}
64
+ */
65
+ export function toISO6391(language) {
66
+ if (!language) return null;
67
+ language = language.toLowerCase();
68
+
69
+ return searchForISO6391(language, ['ISO 639-1']) ||
70
+ searchForISO6391(language, ['ISO 639-2/B']) ||
71
+ searchForISO6391(language, ['ISO 639-2/T', 'Endonym', 'Name']);
72
+ }
73
+
74
+ /**
75
+ * Searches for the given long in the given columns.
76
+ * @param {string} language
77
+ * @param {Array<keyof COLUMN_TO_LANG_INDEX>} columnsToSearch
78
+ * @return {ISO6391?}
79
+ */
80
+ function searchForISO6391(language, columnsToSearch) {
81
+ for (let i = 0; i < langs.length; i++) {
82
+ for (let colI = 0; colI < columnsToSearch.length; colI++) {
83
+ const column = columnsToSearch[colI];
84
+ const columnValue = langs[i][COLUMN_TO_LANG_INDEX[column]];
85
+ if (columnValue.split(', ').map(x => x.toLowerCase()).indexOf(language) != -1) {
86
+ return langs[i][COLUMN_TO_LANG_INDEX['ISO 639-1']];
87
+ }
88
+ }
89
+ }
90
+ return null;
91
+ }
@@ -0,0 +1,30 @@
1
+
2
+ /**
3
+ * Checks whether the current browser is a Chrome/Chromium browser
4
+ * Code from https://stackoverflow.com/a/4565120/2317712
5
+ * @param {string} [userAgent]
6
+ * @param {string} [vendor]
7
+ * @return {boolean}
8
+ */
9
+ export function isChrome(userAgent = navigator.userAgent, vendor = navigator.vendor) {
10
+ return /chrome/i.test(userAgent) && /google inc/i.test(vendor);
11
+ }
12
+
13
+ /**
14
+ * Checks whether the current browser is firefox
15
+ * @param {string} [userAgent]
16
+ * @return {boolean}
17
+ */
18
+ export function isFirefox(userAgent = navigator.userAgent) {
19
+ return /firefox/i.test(userAgent);
20
+ }
21
+
22
+ /**
23
+ * Checks whether the current browser is safari
24
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#Browser_Name
25
+ * @param {string} [userAgent]
26
+ * @return {boolean}
27
+ */
28
+ export function isSafari(userAgent = navigator.userAgent) {
29
+ return /safari/i.test(userAgent) && !/chrome|chromium/i.test(userAgent);
30
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Wait until some time has passed before executing a callback.
3
+ *
4
+ * @param {Function} callback
5
+ * @param {Number} threshhold - in milliseconds
6
+ * @param {*} context - will be bound to callback as its "this" value
7
+ */
8
+ class Debouncer {
9
+ constructor(callback, threshhold = 250, context = undefined) {
10
+ this.callback = callback;
11
+ this.threshhold = threshhold;
12
+ this.context = context;
13
+ this.deferTimeout = undefined;
14
+ }
15
+
16
+ execute() {
17
+ clearTimeout(this.deferTimeout);
18
+ this.deferTimeout = setTimeout(this.executeCallback.bind(this), this.threshhold);
19
+ }
20
+
21
+ executeCallback() {
22
+ this.callback.apply(this.context);
23
+ }
24
+ }
25
+
26
+ export { Debouncer as default };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Helper module use to get, set and remove item from cookie
3
+ *
4
+ * See more:
5
+ * https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
6
+ * https://developer.mozilla.org/User:fusionchess
7
+ * https://github.com/madmurphy/cookies.js
8
+ * This framework is released under the GNU Public License, version 3 or later.
9
+ * http://www.gnu.org/licenses/gpl-3.0-standalone.html
10
+ */
11
+
12
+ /**
13
+ * Get specific key's value stored in cookie
14
+ *
15
+ * @param {string} sKey
16
+ *
17
+ * @returns {string|null}
18
+ */
19
+ export function getItem(sKey) {
20
+ if (!sKey) return null;
21
+
22
+ return decodeURIComponent(
23
+ // eslint-disable-next-line no-useless-escape
24
+ document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null;
25
+ }
26
+
27
+ /**
28
+ * Set specific key's value in cookie
29
+ *
30
+ * @param {string} sKey cookie name
31
+ * @param {string} sValue cookie value
32
+ * @param {string} [vEnd] expire|max-age
33
+ * @param {string} [sPath] path of current item
34
+ * @param {string} [sDomain] domain name
35
+ * @param {boolean} [bSecure]
36
+ *
37
+ * @returns {true}
38
+ */
39
+ export function setItem(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
40
+ document.cookie = encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue)
41
+ + (vEnd ? `; expires=${vEnd.toUTCString()}` : '')
42
+ + (sDomain ? `; domain=${sDomain}` : '')
43
+ + (sPath ? `; path=${sPath}` : '')
44
+ + (bSecure ? `; secure` : '');
45
+
46
+ return true;
47
+ }
48
+
49
+ /**
50
+ * BROKEN Remove specific key's value from cookie
51
+ * @fixme hasItem isn't even implemented! This will always error.
52
+ * @param {string} sKey cookie name
53
+ * @param {string} [sPath] path of current item
54
+ * @param {string} [sDomain]
55
+ *
56
+ * @returns {boolean}
57
+ */
58
+ export function removeItem(sKey, sPath, sDomain) {
59
+ // eslint-disable-next-line
60
+ if (!hasItem(sKey)) return false;
61
+
62
+ document.cookie = encodeURIComponent(sKey) + `=; expires=Thu, 01 Jan 1970 00:00:00 GMT`
63
+ + (sDomain ? `; domain=${sDomain}` : '')
64
+ + (sPath ? `; path=${sPath}` : '');
65
+
66
+ return true;
67
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @typedef {String} StringWithVars
3
+ * A template string with {{foo}} style variables
4
+ * Also supports filters, like {{bookPath|urlencode}} (See APPLY_FILTERS for the
5
+ * supported list of filters)
6
+ **/
7
+
8
+ /**
9
+ * @param {StringWithVars|String} template
10
+ * @param { {[varName: string]: { toString: () => string} } } vars
11
+ * @param { {[varName: string]: { toString: () => string} } } [overrides]
12
+ */
13
+ export function applyVariables(template, vars, overrides = {}, possibleFilters = APPLY_FILTERS) {
14
+ return template?.replace(/\{\{([^}]*?)\}\}/g, ($0, $1) => {
15
+ if (!$1) return $0;
16
+ /** @type {string} */
17
+ const expression = $1;
18
+ const [varName, ...filterNames] = expression.split('|').map(x => x.trim());
19
+ const defined = varName in overrides || varName in vars;
20
+
21
+ // If it's not defined, don't expand it at all
22
+ if (!defined) return $0;
23
+
24
+ const value = varName in overrides ? overrides[varName]
25
+ : varName in vars ? vars[varName] : null;
26
+ const filters = filterNames.map(n => possibleFilters[n]);
27
+ return filters.reduce((acc, cur) => cur(acc), value && value.toString());
28
+ });
29
+ }
30
+
31
+ /** @type { {[filterName: String]:( string => string)} } */
32
+ export const APPLY_FILTERS = {
33
+ urlencode: encodeURIComponent,
34
+ };