@internetarchive/bookreader 5.0.0-4 → 5.0.0-40-a1

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 (228) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +75 -4
  3. package/.github/workflows/npm-publish.yml +2 -16
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +83 -323
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +24 -0
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1623 -0
  10. package/BookReader/{bookreader-component-bundle.js.LICENSE.txt → ia-bookreader-bundle.js.LICENSE.txt} +14 -10
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/close-circle-dark.svg +1 -0
  13. package/BookReader/icons/magnify-minus.svg +1 -1
  14. package/BookReader/icons/magnify-plus.svg +1 -1
  15. package/BookReader/icons/voice.svg +1 -0
  16. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  17. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  18. package/BookReader/plugins/plugin.autoplay.js +1 -1
  19. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  20. package/BookReader/plugins/plugin.chapters.js +1 -1
  21. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  22. package/BookReader/plugins/plugin.iframe.js +1 -1
  23. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  24. package/BookReader/plugins/plugin.mobile_nav.js +1 -1
  25. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  26. package/BookReader/plugins/plugin.resume.js +1 -1
  27. package/BookReader/plugins/plugin.resume.js.map +1 -1
  28. package/BookReader/plugins/plugin.search.js +1 -1
  29. package/BookReader/plugins/plugin.search.js.map +1 -1
  30. package/BookReader/plugins/plugin.text_selection.js +1 -1
  31. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  32. package/BookReader/plugins/plugin.tts.js +1 -1
  33. package/BookReader/plugins/plugin.tts.js.map +1 -1
  34. package/BookReader/plugins/plugin.url.js +1 -1
  35. package/BookReader/plugins/plugin.url.js.map +1 -1
  36. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  37. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  38. package/BookReader/webcomponents-bundle.js +3 -0
  39. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  40. package/BookReader/webcomponents-bundle.js.map +1 -0
  41. package/BookReaderDemo/BookReaderDemo.css +14 -1
  42. package/BookReaderDemo/IADemoBr.js +120 -0
  43. package/BookReaderDemo/demo-advanced.html +1 -1
  44. package/BookReaderDemo/demo-autoplay.html +1 -0
  45. package/BookReaderDemo/demo-embed-iframe-src.html +1 -0
  46. package/BookReaderDemo/demo-fullscreen-mobile.html +1 -0
  47. package/BookReaderDemo/demo-fullscreen.html +1 -0
  48. package/BookReaderDemo/demo-iiif.html +1 -0
  49. package/BookReaderDemo/demo-internetarchive.html +74 -17
  50. package/BookReaderDemo/demo-multiple.html +1 -0
  51. package/BookReaderDemo/demo-preview-pages.html +1 -0
  52. package/BookReaderDemo/demo-simple.html +1 -0
  53. package/BookReaderDemo/demo-vendor-fullscreen.html +1 -0
  54. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  55. package/BookReaderDemo/immersion-1up.html +1 -0
  56. package/BookReaderDemo/immersion-mode.html +1 -0
  57. package/BookReaderDemo/toggle_controls.html +1 -0
  58. package/BookReaderDemo/view_mode.html +1 -0
  59. package/BookReaderDemo/viewmode-cycle.html +1 -2
  60. package/CHANGELOG.md +166 -0
  61. package/README.md +14 -1
  62. package/babel.config.js +18 -0
  63. package/codecov.yml +6 -0
  64. package/index.html +3 -0
  65. package/jsconfig.json +19 -0
  66. package/package.json +62 -47
  67. package/renovate.json +43 -0
  68. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  69. package/src/BookNavigator/assets/button-base.js +9 -2
  70. package/src/BookNavigator/assets/ia-logo.js +17 -0
  71. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  72. package/src/BookNavigator/assets/icon_close.js +1 -1
  73. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  74. package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
  75. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  76. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  77. package/src/BookNavigator/book-navigator.js +556 -0
  78. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  79. package/src/BookNavigator/bookmarks/bookmark-edit.js +4 -4
  80. package/src/BookNavigator/bookmarks/bookmarks-list.js +3 -3
  81. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  82. package/src/BookNavigator/bookmarks/bookmarks-provider.js +23 -12
  83. package/src/BookNavigator/bookmarks/ia-bookmarks.js +98 -62
  84. package/src/BookNavigator/delete-modal-actions.js +1 -1
  85. package/src/BookNavigator/downloads/downloads-provider.js +23 -17
  86. package/src/BookNavigator/downloads/downloads.js +17 -25
  87. package/src/BookNavigator/search/a-search-result.js +3 -3
  88. package/src/BookNavigator/search/search-provider.js +57 -24
  89. package/src/BookNavigator/search/search-results.js +8 -20
  90. package/src/BookNavigator/sharing.js +27 -0
  91. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -13
  92. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +4 -3
  93. package/src/BookNavigator/volumes/volumes-provider.js +114 -0
  94. package/src/BookNavigator/volumes/volumes.js +188 -0
  95. package/src/BookReader/DebugConsole.js +3 -3
  96. package/src/BookReader/DragScrollable.js +233 -0
  97. package/src/BookReader/Mode1Up.js +51 -351
  98. package/src/BookReader/Mode1UpLit.js +441 -0
  99. package/src/BookReader/Mode2Up.js +104 -71
  100. package/src/BookReader/ModeSmoothZoom.js +179 -0
  101. package/src/BookReader/ModeThumb.js +16 -8
  102. package/src/BookReader/Navbar/Navbar.js +2 -31
  103. package/src/BookReader/PageContainer.js +57 -6
  104. package/src/BookReader/ReduceSet.js +1 -1
  105. package/src/BookReader/Toolbar/Toolbar.js +7 -7
  106. package/src/BookReader/options.js +10 -0
  107. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  108. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  109. package/src/BookReader/utils.js +68 -13
  110. package/src/BookReader.js +375 -289
  111. package/src/assets/icons/close-circle-dark.svg +1 -0
  112. package/src/assets/icons/magnify-minus.svg +3 -7
  113. package/src/assets/icons/magnify-plus.svg +3 -7
  114. package/src/assets/icons/voice.svg +1 -0
  115. package/src/css/BookReader.scss +0 -12
  116. package/src/css/_BRComponent.scss +1 -1
  117. package/src/css/_BRmain.scss +19 -24
  118. package/src/css/_BRnav.scss +4 -26
  119. package/src/css/_BRpages.scss +35 -0
  120. package/src/css/_BRsearch.scss +11 -215
  121. package/src/css/_TextSelection.scss +14 -17
  122. package/src/css/_colorbox.scss +2 -2
  123. package/src/css/_controls.scss +16 -3
  124. package/src/css/_icons.scss +6 -0
  125. package/src/ia-bookreader/ia-bookreader.js +224 -0
  126. package/src/plugins/plugin.chapters.js +26 -33
  127. package/src/plugins/plugin.mobile_nav.js +11 -10
  128. package/src/plugins/plugin.resume.js +3 -3
  129. package/src/plugins/plugin.text_selection.js +26 -39
  130. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  131. package/src/plugins/search/plugin.search.js +106 -107
  132. package/src/plugins/search/view.js +50 -163
  133. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  134. package/src/plugins/tts/FestivalTTSEngine.js +12 -13
  135. package/src/plugins/tts/PageChunk.js +15 -21
  136. package/src/plugins/tts/PageChunkIterator.js +8 -12
  137. package/src/plugins/tts/WebTTSEngine.js +64 -68
  138. package/src/plugins/tts/plugin.tts.js +79 -108
  139. package/src/plugins/url/UrlPlugin.js +184 -0
  140. package/src/plugins/{plugin.url.js → url/plugin.url.js} +28 -6
  141. package/tests/e2e/README.md +37 -0
  142. package/tests/e2e/autoplay.test.js +2 -2
  143. package/tests/e2e/base.test.js +7 -7
  144. package/tests/e2e/helpers/base.js +8 -3
  145. package/tests/e2e/helpers/debug.js +1 -1
  146. package/tests/e2e/helpers/desktopSearch.js +14 -13
  147. package/tests/e2e/helpers/mobileSearch.js +3 -3
  148. package/tests/e2e/helpers/params.js +17 -0
  149. package/tests/e2e/models/Navigation.js +12 -3
  150. package/tests/e2e/rightToLeft.test.js +4 -5
  151. package/tests/e2e/viewmode.test.js +38 -33
  152. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +3 -3
  153. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +176 -0
  154. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +1 -1
  155. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  156. package/tests/jest/BookReader/Mode1UpLit.test.js +88 -0
  157. package/tests/{BookReader → jest/BookReader}/Mode2Up.test.js +5 -7
  158. package/tests/jest/BookReader/ModeSmoothZoom.test.js +149 -0
  159. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  160. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +7 -7
  161. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +79 -6
  162. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  163. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  164. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  165. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  166. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  167. package/tests/jest/BookReader/utils.test.js +136 -0
  168. package/tests/jest/BookReader.keyboard.test.js +190 -0
  169. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  170. package/tests/{BookReader.test.js → jest/BookReader.test.js} +20 -4
  171. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  172. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +2 -2
  173. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +8 -8
  174. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  175. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +5 -5
  176. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  177. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +39 -47
  178. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  179. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +24 -25
  180. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +6 -6
  181. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +6 -6
  182. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  183. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  184. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  185. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +1 -1
  186. package/tests/{plugins → jest/plugins}/tts/utils.test.js +3 -3
  187. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  188. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +33 -14
  189. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  190. package/tests/{util → jest/util}/docCookies.test.js +1 -1
  191. package/tests/{util → jest/util}/strings.test.js +1 -1
  192. package/tests/{utils.js → jest/utils.js} +38 -0
  193. package/tests/karma/BookNavigator/book-navigator.test.js +501 -0
  194. package/tests/karma/BookNavigator/bookmarks/bookmark-button.test.js +44 -0
  195. package/tests/karma/BookNavigator/bookmarks/bookmark-edit.test.js +1 -3
  196. package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +3 -4
  197. package/tests/karma/BookNavigator/bookmarks/ia-bookmarks.test.js +57 -0
  198. package/tests/karma/BookNavigator/downloads/downloads-provider.test.js +67 -0
  199. package/tests/karma/BookNavigator/downloads/downloads.test.js +54 -0
  200. package/tests/karma/BookNavigator/search/search-provider.test.js +123 -0
  201. package/tests/karma/BookNavigator/{search-results.test.js → search/search-results.test.js} +1 -4
  202. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +49 -0
  203. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -2
  204. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +184 -0
  205. package/tests/karma/BookNavigator/volumes/volumes.test.js +98 -0
  206. package/webpack.config.js +10 -4
  207. package/.babelrc +0 -12
  208. package/.dependabot/config.yml +0 -6
  209. package/.testcaferc.json +0 -5
  210. package/BookReader/bookreader-component-bundle.js +0 -1450
  211. package/BookReader/bookreader-component-bundle.js.map +0 -1
  212. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  213. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  214. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  215. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  216. package/src/BookNavigator/BookModel.js +0 -14
  217. package/src/BookNavigator/BookNavigator.js +0 -435
  218. package/src/BookNavigator/assets/book-loader.js +0 -27
  219. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  220. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  221. package/src/ItemNavigator/ItemNavigator.js +0 -372
  222. package/src/ItemNavigator/providers/sharing.js +0 -29
  223. package/src/dragscrollable-br.js +0 -261
  224. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  225. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  226. package/tests/BookReader/Mode1Up.test.js +0 -164
  227. package/tests/BookReader/utils.test.js +0 -109
  228. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
@@ -28,6 +28,7 @@ import PageChunkIterator from './PageChunkIterator.js';
28
28
  * @property {() => void} resume
29
29
  * @property {() => void} finish force the sound to 'finish'
30
30
  * @property {number => void} setPlaybackRate
31
+ * @property {SpeechSynthesisVoice => void} setVoice
31
32
  **/
32
33
 
33
34
  /** Handling bookreader's text-to-speech */
@@ -50,9 +51,7 @@ export default class AbstractTTSEngine {
50
51
  /** @type {SpeechSynthesisVoice} */
51
52
  this.voice = null;
52
53
  // Listen for voice changes (fired by subclasses)
53
- this.events.on('voiceschanged', () => {
54
- this.voice = AbstractTTSEngine.getBestBookVoice(this.getVoices(), this.opts.bookLanguage);
55
- });
54
+ this.events.on('voiceschanged', this.updateBestVoice);
56
55
  this.events.trigger('voiceschanged');
57
56
  }
58
57
 
@@ -71,6 +70,10 @@ export default class AbstractTTSEngine {
71
70
  /** @abstract */
72
71
  init() { return null; }
73
72
 
73
+ updateBestVoice = () => {
74
+ this.voice = AbstractTTSEngine.getBestBookVoice(this.getVoices(), this.opts.bookLanguage);
75
+ }
76
+
74
77
  /**
75
78
  * @param {number} leafIndex
76
79
  * @param {number} numLeafs total number of leafs in the current book
@@ -124,13 +127,22 @@ export default class AbstractTTSEngine {
124
127
  }
125
128
 
126
129
  /** @public */
127
- jumpBackward() {
128
- Promise.all([
130
+ async jumpBackward() {
131
+ await Promise.all([
129
132
  this.activeSound.stop(),
130
133
  this._chunkIterator.decrement()
131
134
  .then(() => this._chunkIterator.decrement())
132
- ])
133
- .then(() => this.step());
135
+ ]);
136
+ this.step();
137
+ }
138
+
139
+ /** @param {string} voiceURI */
140
+ setVoice(voiceURI) {
141
+ // if the user actively selects a voice, don't re-choose best voice anymore
142
+ // MS Edge fires voices changed randomly very often
143
+ this.events.off('voiceschanged', this.updateBestVoice);
144
+ this.voice = this.getVoices().find(voice => voice.voiceURI === voiceURI);
145
+ if (this.activeSound) this.activeSound.setVoice(this.voice);
134
146
  }
135
147
 
136
148
  /** @param {number} newRate */
@@ -140,36 +152,33 @@ export default class AbstractTTSEngine {
140
152
  }
141
153
 
142
154
  /** @private */
143
- step() {
144
- this._chunkIterator.next()
145
- .then(chunk => {
146
- if (chunk == PageChunkIterator.AT_END) {
147
- this.stop();
148
- this.opts.onDone();
149
- return;
150
- }
151
-
152
- this.opts.onLoadingStart();
153
- const sound = this.createSound(chunk);
154
- sound.chunk = chunk;
155
- sound.rate = this.playbackRate;
156
- sound.voice = this.voice;
157
- sound.load(() => this.opts.onLoadingComplete());
158
-
159
- this.opts.onLoadingComplete();
160
- return this.opts.beforeChunkPlay(chunk).then(() => sound);
161
- })
162
- .then(sound => {
163
- if (!this.playing) return;
164
-
165
- const playPromise = this.playSound(sound)
166
- .then(() => this.opts.afterChunkPlay(sound.chunk));
167
- if (this.paused) this.pause();
168
- return playPromise;
169
- })
170
- .then(() => {
171
- if (this.playing) return this.step();
172
- });
155
+ async step() {
156
+ const chunk = await this._chunkIterator.next();
157
+ if (chunk == PageChunkIterator.AT_END) {
158
+ this.stop();
159
+ this.opts.onDone();
160
+ return;
161
+ }
162
+ this.opts.onLoadingStart();
163
+ const sound = this.createSound(chunk);
164
+ sound.chunk = chunk;
165
+ sound.rate = this.playbackRate;
166
+ sound.voice = this.voice;
167
+ sound.load(() => this.opts.onLoadingComplete());
168
+
169
+ this.opts.onLoadingComplete();
170
+
171
+ await this.opts.beforeChunkPlay(chunk);
172
+
173
+ if (!this.playing) return;
174
+
175
+ const playPromise = await this.playSound(sound)
176
+ .then(()=> this.opts.afterChunkPlay(sound.chunk));
177
+
178
+ if (this.paused) this.pause();
179
+ await playPromise;
180
+
181
+ if (this.playing) return this.step();
173
182
  }
174
183
 
175
184
  /**
@@ -23,7 +23,7 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
23
23
  // $.browsers is sometimes undefined on some Android browsers :/
24
24
  // Likely related to when $.browser was moved to npm
25
25
  /** @type {'mp3' | 'ogg'} format of audio to get */
26
- this.audioFormat = $.browser?.mozilla ? 'ogg' : 'mp3';
26
+ this.audioFormat = $.browser?.mozilla ? 'ogg' : 'mp3'; //eslint-disable-line no-jquery/no-browser
27
27
  }
28
28
 
29
29
  /** @override */
@@ -91,10 +91,10 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
91
91
  * See https://stackoverflow.com/questions/12206631/html5-audio-cant-play-through-javascript-unless-triggered-manually-once
92
92
  * @return {PromiseLike}
93
93
  */
94
- iOSCaptureUserIntentHack() {
94
+ async iOSCaptureUserIntentHack() {
95
95
  const sound = soundManager.createSound({ url: SILENCE_1MS[this.audioFormat] });
96
- return new Promise(res => sound.play({onfinish: res}))
97
- .then(() => sound.destruct());
96
+ await new Promise(res => sound.play({onfinish: res}));
97
+ sound.destruct();
98
98
  }
99
99
  }
100
100
 
@@ -107,7 +107,7 @@ class FestivalTTSSound {
107
107
  this.sound = null;
108
108
  this.rate = 1;
109
109
  /** @type {function} calling this resolves the "play" promise */
110
- this._finishResolver = null
110
+ this._finishResolver = null;
111
111
  }
112
112
 
113
113
  get loaded() {
@@ -122,21 +122,20 @@ class FestivalTTSSound {
122
122
  if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
123
123
  onload();
124
124
  },
125
- onresume: () => {
126
- sleep(25).then(() => {
127
- if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
128
- });
125
+ onresume: async () => {
126
+ await sleep(25);
127
+ if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
129
128
  }
130
129
  });
131
130
  return this.sound.load();
132
131
  }
133
132
 
134
- play() {
135
- return new Promise(res => {
133
+ async play() {
134
+ await new Promise(res => {
136
135
  this._finishResolver = res;
137
136
  this.sound.play({ onfinish: res });
138
- })
139
- .then(() => this.sound.destruct());
137
+ });
138
+ this.sound.destruct();
140
139
  }
141
140
 
142
141
  /** @override */
@@ -21,27 +21,18 @@ export default class PageChunk {
21
21
  * @param {number} leafIndex
22
22
  * @return {Promise<PageChunk[]>}
23
23
  */
24
- static fetch(server, bookPath, leafIndex) {
25
- // jquery's ajax "PromiseLike" implementation is inconsistent with
26
- // modern Promises, so convert it to a full promise (it doesn't forward
27
- // a returned promise to the next handler in the chain, which kind of
28
- // defeats the entire point of using promises to avoid "callback hell")
29
- return new Promise((res, rej) => {
30
- $.ajax({
31
- type: 'GET',
32
- url: `https://${server}/BookReader/BookReaderGetTextWrapper.php`,
33
- dataType:'jsonp',
34
- cache: true,
35
- data: {
36
- path: `${bookPath}_djvu.xml`,
37
- page: leafIndex
38
- },
39
- error: rej,
40
- })
41
- .then(chunks => {
42
- res(PageChunk._fromTextWrapperResponse(leafIndex, chunks));
43
- });
24
+ static async fetch(server, bookPath, leafIndex) {
25
+ const chunks = await $.ajax({
26
+ type: 'GET',
27
+ url: `https://${server}/BookReader/BookReaderGetTextWrapper.php`,
28
+ dataType:'jsonp',
29
+ cache: true,
30
+ data: {
31
+ path: `${bookPath}_djvu.xml`,
32
+ page: leafIndex
33
+ }
44
34
  });
35
+ return PageChunk._fromTextWrapperResponse(leafIndex, chunks);
45
36
  }
46
37
 
47
38
  /**
@@ -97,7 +88,10 @@ export default class PageChunk {
97
88
  * @return {string}
98
89
  */
99
90
  static _removeDanglingHyphens(text) {
100
- return text.replace(/-\s+/g, '');
91
+ // Some books mis-OCR a dangling hyphen as a ¬ (mathematical not sign) . Since in math
92
+ // the not sign should not appear followed by a space, we think we can safely assume
93
+ // this should be replaced.
94
+ return text.replace(/[-¬]\s+/g, '');
101
95
  }
102
96
  }
103
97
 
@@ -53,22 +53,18 @@ export default class PageChunkIterator {
53
53
  * in the correct order.
54
54
  * @return {PromiseLike<"__PageChunkIterator.AT_END__" | PageChunk>}
55
55
  */
56
- _nextUncontrolled() {
56
+ async _nextUncontrolled() {
57
57
  if (this._cursor.page == this.pageCount) {
58
58
  return Promise.resolve(PageChunkIterator.AT_END);
59
59
  }
60
-
61
60
  this._recenterBuffer(this._cursor.page);
62
-
63
- return this._fetchPageChunks(this._cursor.page)
64
- .then(chunks => {
65
- if (this._cursor.chunk == chunks.length) {
66
- this._cursor.page++;
67
- this._cursor.chunk = 0;
68
- return this._nextUncontrolled();
69
- }
70
- return chunks[this._cursor.chunk++];
71
- });
61
+ const chunks = await this._fetchPageChunks(this._cursor.page);
62
+ if (this._cursor.chunk == chunks.length) {
63
+ this._cursor.page++;
64
+ this._cursor.chunk = 0;
65
+ return this._nextUncontrolled();
66
+ }
67
+ return chunks[this._cursor.chunk++];
72
68
  }
73
69
 
74
70
  /**
@@ -167,7 +167,7 @@ export class WebTTSSound {
167
167
  * left off.
168
168
  * @return {Promise<void>}
169
169
  */
170
- reload() {
170
+ async reload() {
171
171
  // We'll restore the pause state, so copy it here
172
172
  const wasPaused = this.paused;
173
173
  // Use recent event to determine where we'll restart from
@@ -179,14 +179,12 @@ export class WebTTSSound {
179
179
  }
180
180
 
181
181
  // We can't modify the utterance object, so we have to make a new one
182
- return this.stop()
183
- .then(() => {
184
- this.load();
185
- // Instead of playing and immediately pausing, we don't start playing. Note
186
- // this is a requirement because pause doesn't work consistently across
187
- // browsers.
188
- if (!wasPaused) this.play();
189
- });
182
+ await this.stop();
183
+ this.load();
184
+ // Instead of playing and immediately pausing, we don't start playing. Note
185
+ // this is a requirement because pause doesn't work consistently across
186
+ // browsers.
187
+ if (!wasPaused) this.play();
190
188
  }
191
189
 
192
190
  play() {
@@ -222,15 +220,16 @@ export class WebTTSSound {
222
220
  return endPromise;
223
221
  }
224
222
 
225
- finish() {
226
- this.stop().then(() => this.utterance.dispatchEvent(new Event('finish')));
223
+ async finish() {
224
+ await this.stop();
225
+ this.utterance.dispatchEvent(new Event('finish'));
227
226
  }
228
227
 
229
228
  /**
230
229
  * @override
231
230
  * Will fire a pause event unless already paused
232
231
  **/
233
- pause() {
232
+ async pause() {
234
233
  if (this.paused) return;
235
234
 
236
235
  const pausePromise = promisifyEvent(this.utterance, 'pause');
@@ -246,19 +245,18 @@ export class WebTTSSound {
246
245
  if (pauseMightNotFire) {
247
246
  // wait for it just in case
248
247
  const timeoutPromise = sleep(100).then(() => 'timeout');
249
- Promise.race([pausePromise, timeoutPromise])
250
- .then(result => {
251
- // We got our pause event; nothing to do!
252
- if (result != 'timeout') return;
253
-
254
- this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
255
- // if pause might not work, then we'll stop entirely and restart later
256
- if (pauseMightNotWork) this.stop();
257
- });
248
+ const result = await Promise.race([pausePromise, timeoutPromise]);
249
+ // We got our pause event; nothing to do!
250
+ if (result != 'timeout') return;
251
+
252
+ this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
253
+
254
+ // if pause might not work, then we'll stop entirely and restart later
255
+ if (pauseMightNotWork) this.stop();
258
256
  }
259
257
  }
260
258
 
261
- resume() {
259
+ async resume() {
262
260
  if (!this.started) {
263
261
  this.play();
264
262
  return;
@@ -278,16 +276,15 @@ export class WebTTSSound {
278
276
  speechSynthesis.resume();
279
277
 
280
278
  if (resumeMightNotFire) {
281
- Promise.race([resumePromise, sleep(100).then(() => 'timeout')])
282
- .then(result => {
283
- if (result != 'timeout') return;
284
-
285
- this.utterance.dispatchEvent(new CustomEvent('resume', {}));
286
- if (resumeMightNotWork) {
287
- const reloadPromise = this.reload();
288
- reloadPromise.then(() => this.play());
289
- }
290
- });
279
+ const result = await Promise.race([resumePromise, sleep(100).then(() => 'timeout')]);
280
+
281
+ if (result != 'timeout') return;
282
+
283
+ this.utterance.dispatchEvent(new CustomEvent('resume', {}));
284
+ if (resumeMightNotWork) {
285
+ await this.reload();
286
+ this.play();
287
+ }
291
288
  }
292
289
  }
293
290
 
@@ -296,6 +293,11 @@ export class WebTTSSound {
296
293
  this.reload();
297
294
  }
298
295
 
296
+ /** @param {SpeechSynthesisVoice} voice */
297
+ setVoice(voice) {
298
+ this.voice = voice;
299
+ this.reload();
300
+ }
299
301
  /**
300
302
  * @private
301
303
  * Chrome has a bug where it only plays 15 seconds of TTS and then
@@ -303,45 +305,39 @@ export class WebTTSSound {
303
305
  * We avoid this (as described here: https://bugs.chromium.org/p/chromium/issues/detail?id=679437#c15 )
304
306
  * by pausing after 14 seconds and ~instantly resuming.
305
307
  */
306
- _chromePausingBugFix() {
308
+ async _chromePausingBugFix() {
307
309
  const timeoutPromise = sleep(14000).then(() => 'timeout');
308
310
  const pausePromise = promisifyEvent(this.utterance, 'pause').then(() => 'paused');
309
311
  const endPromise = promisifyEvent(this.utterance, 'end').then(() => 'ended');
310
- return Promise.race([timeoutPromise, pausePromise, endPromise])
311
- .then(result => {
312
- if (location.toString().indexOf('_debugReadAloud=true') != -1) {
313
- console.log(`CHROME-PAUSE-HACK: ${result}`);
314
- }
315
- switch (result) {
316
- case 'ended':
317
- // audio was stopped/finished; nothing to do
318
- break;
319
- case 'paused':
320
- // audio was paused; wait for resume
321
- // Chrome won't let you resume the audio if 14s have passed 🤷‍
322
- // We could do the same as before (but resume+pause instead of pause+resume),
323
- // but that means we'd _constantly_ be running in the background. So in that
324
- // case, let's just restart the chunk
325
- Promise.race([
326
- promisifyEvent(this.utterance, 'resume'),
327
- sleep(14000).then(() => 'timeout'),
328
- ])
329
- .then(result => {
330
- result == 'timeout' ? this.reload() : this._chromePausingBugFix();
331
- });
332
- break;
333
- case 'timeout':
334
- // We hit Chrome's secret cut off time. Pause/resume
335
- // to be able to keep TTS-ing
336
- speechSynthesis.pause();
337
- sleep(25)
338
- .then(() => {
339
- speechSynthesis.resume();
340
- this._chromePausingBugFix();
341
- });
342
- break;
343
- }
344
- });
312
+ const result = await Promise.race([timeoutPromise, pausePromise, endPromise]);
313
+ if (location.toString().indexOf('_debugReadAloud=true') != -1) {
314
+ console.log(`CHROME-PAUSE-HACK: ${result}`);
315
+ }
316
+ switch (result) {
317
+ case 'ended':
318
+ // audio was stopped/finished; nothing to do
319
+ break;
320
+ case 'paused':
321
+ // audio was paused; wait for resume
322
+ // Chrome won't let you resume the audio if 14s have passed 🤷‍
323
+ // We could do the same as before (but resume+pause instead of pause+resume),
324
+ // but that means we'd _constantly_ be running in the background. So in that
325
+ // case, let's just restart the chunk
326
+ await Promise.race([
327
+ promisifyEvent(this.utterance, 'resume'),
328
+ sleep(14000).then(() => 'timeout'),
329
+ ]);
330
+ result == 'timeout' ? this.reload() : this._chromePausingBugFix();
331
+ break;
332
+ case 'timeout':
333
+ // We hit Chrome's secret cut off time. Pause/resume
334
+ // to be able to keep TTS-ing
335
+ speechSynthesis.pause();
336
+ await sleep(25);
337
+ speechSynthesis.resume();
338
+ this._chromePausingBugFix();
339
+ break;
340
+ }
345
341
  }
346
342
  }
347
343