@internetarchive/bookreader 5.0.0-9 → 5.0.0-90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/.eslintrc.js +21 -19
  2. package/.github/workflows/node.js.yml +76 -11
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +404 -1125
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1782 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +7 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/1up.svg +1 -1
  13. package/BookReader/icons/2up.svg +1 -1
  14. package/BookReader/icons/advance.svg +1 -1
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -1
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -1
  19. package/BookReader/icons/fullscreen_exit.svg +1 -1
  20. package/BookReader/icons/hamburger.svg +1 -1
  21. package/BookReader/icons/left-arrow.svg +1 -1
  22. package/BookReader/icons/magnify-minus.svg +1 -1
  23. package/BookReader/icons/magnify-plus.svg +1 -1
  24. package/BookReader/icons/magnify.svg +1 -1
  25. package/BookReader/icons/pause.svg +1 -1
  26. package/BookReader/icons/play.svg +1 -1
  27. package/BookReader/icons/playback-speed.svg +1 -1
  28. package/BookReader/icons/read-aloud.svg +1 -1
  29. package/BookReader/icons/review.svg +1 -1
  30. package/BookReader/icons/thumbnails.svg +1 -1
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -1
  33. package/BookReader/images/BRicons.svg +3 -3
  34. package/BookReader/images/books_graphic.svg +1 -1
  35. package/BookReader/images/icon_book.svg +1 -1
  36. package/BookReader/images/icon_bookmark.svg +1 -1
  37. package/BookReader/images/icon_gear.svg +1 -1
  38. package/BookReader/images/icon_hamburger.svg +1 -1
  39. package/BookReader/images/icon_home.svg +1 -1
  40. package/BookReader/images/icon_info.svg +1 -1
  41. package/BookReader/images/icon_one_page.svg +1 -1
  42. package/BookReader/images/icon_pause.svg +1 -1
  43. package/BookReader/images/icon_play.svg +1 -1
  44. package/BookReader/images/icon_playback-rate.svg +1 -1
  45. package/BookReader/images/icon_search_button.svg +1 -1
  46. package/BookReader/images/icon_share.svg +1 -1
  47. package/BookReader/images/icon_skip-ahead.svg +1 -1
  48. package/BookReader/images/icon_skip-back.svg +1 -1
  49. package/BookReader/images/icon_speaker.svg +1 -1
  50. package/BookReader/images/icon_speaker_open.svg +1 -1
  51. package/BookReader/images/icon_thumbnails.svg +1 -1
  52. package/BookReader/images/icon_toc.svg +1 -1
  53. package/BookReader/images/icon_two_pages.svg +1 -1
  54. package/BookReader/images/marker_chap-off.svg +1 -1
  55. package/BookReader/images/marker_chap-on.svg +1 -1
  56. package/BookReader/images/marker_srch-on.svg +1 -1
  57. package/BookReader/images/unviewable_page.png +0 -0
  58. package/BookReader/jquery-3.js +2 -0
  59. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  60. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  61. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  62. package/BookReader/plugins/plugin.autoplay.js +1 -1
  63. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  64. package/BookReader/plugins/plugin.chapters.js +25 -1
  65. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  66. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  67. package/BookReader/plugins/plugin.iframe.js +1 -1
  68. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  69. package/BookReader/plugins/plugin.iiif.js +2 -0
  70. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  71. package/BookReader/plugins/plugin.resume.js +1 -1
  72. package/BookReader/plugins/plugin.resume.js.map +1 -1
  73. package/BookReader/plugins/plugin.search.js +2 -1
  74. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  75. package/BookReader/plugins/plugin.search.js.map +1 -1
  76. package/BookReader/plugins/plugin.text_selection.js +2 -1
  77. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  78. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  79. package/BookReader/plugins/plugin.tts.js +1 -1
  80. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  81. package/BookReader/plugins/plugin.tts.js.map +1 -1
  82. package/BookReader/plugins/plugin.url.js +1 -1
  83. package/BookReader/plugins/plugin.url.js.map +1 -1
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  85. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  86. package/BookReader/webcomponents-bundle.js +3 -0
  87. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  88. package/BookReader/webcomponents-bundle.js.map +1 -0
  89. package/BookReaderDemo/BookReaderDemo.css +18 -19
  90. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  91. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  92. package/BookReaderDemo/IADemoBr.js +144 -0
  93. package/BookReaderDemo/demo-advanced.html +2 -2
  94. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  95. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  96. package/BookReaderDemo/demo-fullscreen.html +2 -4
  97. package/BookReaderDemo/demo-iiif.html +99 -12
  98. package/BookReaderDemo/demo-internetarchive.html +214 -18
  99. package/BookReaderDemo/demo-multiple.html +2 -1
  100. package/BookReaderDemo/demo-preview-pages.html +526 -525
  101. package/BookReaderDemo/demo-simple.html +2 -1
  102. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
  103. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  104. package/BookReaderDemo/immersion-1up.html +2 -2
  105. package/BookReaderDemo/immersion-mode.html +2 -4
  106. package/BookReaderDemo/toggle_controls.html +3 -2
  107. package/BookReaderDemo/view_mode.html +2 -1
  108. package/BookReaderDemo/viewmode-cycle.html +2 -3
  109. package/CHANGELOG.md +584 -33
  110. package/README.md +14 -1
  111. package/babel.config.js +20 -0
  112. package/codecov.yml +6 -0
  113. package/index.html +5 -2
  114. package/jsconfig.json +19 -0
  115. package/netlify.toml +9 -0
  116. package/package.json +70 -62
  117. package/renovate.json +52 -0
  118. package/scripts/preversion.js +0 -1
  119. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  120. package/src/BookNavigator/assets/button-base.js +5 -2
  121. package/src/BookNavigator/assets/ia-logo.js +17 -0
  122. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  123. package/src/BookNavigator/assets/icon_close.js +1 -1
  124. package/src/BookNavigator/book-navigator.js +590 -0
  125. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  126. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  127. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  128. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +4 -9
  129. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  130. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  131. package/src/BookNavigator/delete-modal-actions.js +1 -1
  132. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  133. package/src/BookNavigator/downloads/downloads.js +29 -25
  134. package/src/BookNavigator/search/search-provider.js +50 -28
  135. package/src/BookNavigator/search/search-results.js +24 -10
  136. package/src/BookNavigator/sharing.js +27 -0
  137. package/src/BookNavigator/viewable-files.js +95 -0
  138. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +13 -12
  139. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +7 -7
  140. package/src/BookReader/BookModel.js +76 -41
  141. package/src/BookReader/DragScrollable.js +233 -0
  142. package/src/BookReader/ImageCache.js +48 -15
  143. package/src/BookReader/Mode1Up.js +56 -351
  144. package/src/BookReader/Mode1UpLit.js +388 -0
  145. package/src/BookReader/Mode2Up.js +73 -1318
  146. package/src/BookReader/Mode2UpLit.js +777 -0
  147. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  148. package/src/BookReader/ModeSmoothZoom.js +312 -0
  149. package/src/BookReader/ModeThumb.js +19 -13
  150. package/src/BookReader/Navbar/Navbar.js +70 -54
  151. package/src/BookReader/PageContainer.js +116 -22
  152. package/src/BookReader/ReduceSet.js +3 -3
  153. package/src/BookReader/Toolbar/Toolbar.js +14 -41
  154. package/src/BookReader/events.js +2 -3
  155. package/src/BookReader/options.js +73 -15
  156. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  157. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  158. package/src/BookReader/utils/SelectionObserver.js +45 -0
  159. package/src/BookReader/utils/classes.js +1 -1
  160. package/src/BookReader/utils.js +128 -13
  161. package/src/BookReader.js +544 -1078
  162. package/src/BookReaderPlugin.js +44 -0
  163. package/src/assets/icons/magnify-minus.svg +3 -7
  164. package/src/assets/icons/magnify-plus.svg +3 -7
  165. package/src/assets/icons/voice.svg +1 -0
  166. package/src/assets/images/unviewable_page.png +0 -0
  167. package/src/css/BookReader.scss +1 -5
  168. package/src/css/_BRBookmarks.scss +1 -1
  169. package/src/css/_BRComponent.scss +1 -1
  170. package/src/css/_BRicon.scss +8 -2
  171. package/src/css/_BRmain.scss +16 -3
  172. package/src/css/_BRnav.scss +12 -42
  173. package/src/css/_BRpages.scss +170 -42
  174. package/src/css/_BRsearch.scss +68 -25
  175. package/src/css/_BRtoolbar.scss +5 -5
  176. package/src/css/_TextSelection.scss +87 -27
  177. package/src/css/_colorbox.scss +2 -2
  178. package/src/css/_controls.scss +24 -7
  179. package/src/css/_icons.scss +1 -1
  180. package/src/ia-bookreader/ia-bookreader.js +224 -0
  181. package/src/plugins/plugin.archive_analytics.js +84 -78
  182. package/src/plugins/plugin.autoplay.js +99 -104
  183. package/src/plugins/plugin.chapters.js +237 -191
  184. package/src/plugins/plugin.iframe.js +1 -1
  185. package/src/plugins/plugin.iiif.js +141 -0
  186. package/src/plugins/plugin.resume.js +53 -50
  187. package/src/plugins/plugin.text_selection.js +503 -175
  188. package/src/plugins/plugin.vendor-fullscreen.js +7 -7
  189. package/src/plugins/search/plugin.search.js +151 -127
  190. package/src/plugins/search/utils.js +43 -0
  191. package/src/plugins/search/view.js +37 -59
  192. package/src/plugins/tts/AbstractTTSEngine.js +75 -45
  193. package/src/plugins/tts/FestivalTTSEngine.js +21 -31
  194. package/src/plugins/tts/PageChunk.js +16 -23
  195. package/src/plugins/tts/PageChunkIterator.js +11 -17
  196. package/src/plugins/tts/WebTTSEngine.js +88 -72
  197. package/src/plugins/tts/plugin.tts.js +310 -350
  198. package/src/plugins/tts/utils.js +16 -26
  199. package/src/plugins/url/UrlPlugin.js +191 -0
  200. package/src/plugins/{plugin.url.js → url/plugin.url.js} +47 -18
  201. package/src/util/browserSniffing.js +22 -0
  202. package/src/util/docCookies.js +21 -2
  203. package/src/util/strings.js +1 -0
  204. package/tests/e2e/README.md +37 -0
  205. package/tests/e2e/autoplay.test.js +9 -6
  206. package/tests/e2e/base.test.js +8 -16
  207. package/tests/e2e/helpers/base.js +55 -50
  208. package/tests/e2e/helpers/debug.js +1 -1
  209. package/tests/e2e/helpers/mockSearch.js +19 -22
  210. package/tests/e2e/helpers/params.js +17 -0
  211. package/tests/e2e/helpers/rightToLeft.js +8 -14
  212. package/tests/e2e/helpers/search.js +73 -0
  213. package/tests/e2e/models/Navigation.js +20 -37
  214. package/tests/e2e/rightToLeft.test.js +4 -5
  215. package/tests/e2e/viewmode.test.js +40 -33
  216. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  217. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  218. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  219. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  220. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  221. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  222. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  223. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  224. package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
  225. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  226. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  227. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  228. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  229. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  230. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  231. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  232. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  233. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  234. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  235. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  236. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  237. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +42 -29
  238. package/tests/jest/BookReader/PageContainer.test.js +238 -0
  239. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  240. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +3 -3
  241. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  242. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  243. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  244. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  245. package/tests/jest/BookReader/utils.test.js +250 -0
  246. package/tests/jest/BookReader.keyboard.test.js +190 -0
  247. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +10 -2
  248. package/tests/{BookReader.test.js → jest/BookReader.test.js} +43 -53
  249. package/tests/jest/plugins/plugin.archive_analytics.test.js +20 -0
  250. package/tests/jest/plugins/plugin.autoplay.test.js +35 -0
  251. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  252. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +4 -4
  253. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +22 -35
  254. package/tests/jest/plugins/plugin.text_selection.test.js +316 -0
  255. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  256. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +19 -47
  257. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +42 -9
  258. package/tests/jest/plugins/search/utils.js +25 -0
  259. package/tests/jest/plugins/search/utils.test.js +29 -0
  260. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +30 -10
  261. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  262. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  263. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  264. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  265. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  266. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  267. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +57 -18
  268. package/tests/jest/setup.js +3 -0
  269. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  270. package/tests/jest/util/docCookies.test.js +24 -0
  271. package/tests/{util → jest/util}/strings.test.js +1 -1
  272. package/tests/{utils.js → jest/utils.js} +38 -0
  273. package/webpack.config.js +16 -10
  274. package/.babelrc +0 -12
  275. package/.dependabot/config.yml +0 -6
  276. package/.testcaferc.json +0 -5
  277. package/BookReader/bookreader-component-bundle.js +0 -1450
  278. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  279. package/BookReader/bookreader-component-bundle.js.map +0 -1
  280. package/BookReader/jquery-1.10.1.js +0 -2
  281. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  282. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  283. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  284. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  285. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  286. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  287. package/BookReaderDemo/IIIFBookReader.js +0 -207
  288. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  289. package/BookReaderDemo/demo-autoplay.html +0 -38
  290. package/BookReaderDemo/demo-iiif.js +0 -26
  291. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  292. package/karma.conf.js +0 -23
  293. package/src/BookNavigator/BookModel.js +0 -14
  294. package/src/BookNavigator/BookNavigator.js +0 -446
  295. package/src/BookNavigator/assets/book-loader.js +0 -27
  296. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  297. package/src/BookNavigator/search/a-search-result.js +0 -55
  298. package/src/BookReader/DebugConsole.js +0 -54
  299. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  300. package/src/ItemNavigator/ItemNavigator.js +0 -376
  301. package/src/ItemNavigator/providers/sharing.js +0 -29
  302. package/src/css/_MobileNav.scss +0 -194
  303. package/src/dragscrollable-br.js +0 -261
  304. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  305. package/src/plugins/plugin.mobile_nav.js +0 -287
  306. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  307. package/tests/BookReader/DebugConsole.test.js +0 -25
  308. package/tests/BookReader/Mode1Up.test.js +0 -164
  309. package/tests/BookReader/Mode2Up.test.js +0 -247
  310. package/tests/BookReader/PageContainer.test.js +0 -115
  311. package/tests/BookReader/utils.test.js +0 -109
  312. package/tests/e2e/helpers/desktopSearch.js +0 -72
  313. package/tests/e2e/helpers/mobileSearch.js +0 -85
  314. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  315. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  316. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  317. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  318. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  319. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  320. package/tests/plugins/plugin.autoplay.test.js +0 -52
  321. package/tests/plugins/plugin.chapters.test.js +0 -130
  322. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  323. package/tests/plugins/plugin.text_selection.test.js +0 -203
  324. package/tests/util/docCookies.test.js +0 -15
@@ -9,17 +9,15 @@ class SearchView {
9
9
  */
10
10
  constructor({ br, searchCancelledCallback = () => {} }) {
11
11
  this.br = br;
12
-
13
- // Search results are returned as a text blob with the hits wrapped in
14
- // triple mustaches. Hits occasionally include text beyond the search
15
- // term, so everything within the staches is captured and wrapped.
16
- this.matcher = new RegExp('{{{(.+?)}}}', 'g');
17
12
  this.matches = [];
18
13
  this.cacheDOMElements();
19
- this.bindEvents();
20
14
  this.cancelSearch = searchCancelledCallback;
21
15
  }
22
16
 
17
+ init() {
18
+ this.bindEvents();
19
+ }
20
+
23
21
  cacheDOMElements() {
24
22
  this.dom = {};
25
23
  // Search input within the top toolbar. Will be removed once the mobile menu is replaced.
@@ -142,17 +140,19 @@ class SearchView {
142
140
  const start = pool.slice(0, pool.length / 2);
143
141
  const end = pool.slice(pool.length / 2);
144
142
  return closestTo((comparisonFn(start, end, comparator) ? start : end), comparator);
145
- }
143
+ };
146
144
 
147
145
  const closestPage = closestTo(matchPages, currentPage);
148
146
  return this.matches.indexOf(this.matches.find((m) => m.par[0].page === closestPage));
149
147
  }
150
148
 
151
149
  updateResultsPosition() {
150
+ if (!this.dom.searchNavigation) return;
152
151
  this.dom.searchNavigation.find('[data-id=resultsCount]').text(this.resultsPosition());
153
152
  }
154
153
 
155
154
  updateSearchNavigationButtons() {
155
+ if (!this.dom.searchNavigation) return;
156
156
  this.dom.searchNavigation.find('.prev').attr('disabled', !this.currentMatchIndex);
157
157
  this.dom.searchNavigation.find('.next').attr('disabled', this.currentMatchIndex + 1 === this.matches.length);
158
158
  }
@@ -226,25 +226,20 @@ class SearchView {
226
226
  */
227
227
  renderPins(matches) {
228
228
  matches.forEach((match) => {
229
- const queryString = match.text;
230
- const pageIndex = this.br.leafNumToIndex(match.par[0].page);
231
- const pageNumber = this.br.getPageNum(pageIndex);
229
+ const pageIndex = this.br.book.leafNumToIndex(match.par[0].page);
232
230
  const uiStringSearch = "Search result"; // i18n
233
- const uiStringPage = "Page"; // i18n
234
-
235
- const percentThrough = this.br.constructor.util.cssPercentage(pageIndex, this.br.getNumLeafs() - 1);
236
-
237
- const queryStringWithB = queryString.replace(this.matcher, '<b>$1</b>');
238
-
239
- let queryStringWithBTruncated = '';
240
-
241
- if (queryString.length > 100) {
242
- queryStringWithBTruncated = queryString
243
- .replace(/^(.{100}[^\s]*).*/, "$1")
244
- .replace(this.matcher, '<b>$1</b>')
245
- + '...';
231
+ const percentThrough = this.br.constructor.util.cssPercentage(pageIndex, this.br.book.getNumLeafs() - 1);
232
+
233
+ let html = match.html;
234
+ if (html.length > 200) {
235
+ const start = Math.max(0, html.indexOf('<mark>') - 100);
236
+ if (start != 0) {
237
+ html = '' + match.html
238
+ .substring(start)
239
+ // Make sure at word boundary though
240
+ .replace(/^\S+/, '');
241
+ }
246
242
  }
247
-
248
243
  // draw marker
249
244
  $('<div>')
250
245
  .addClass('BRsearch')
@@ -254,35 +249,27 @@ class SearchView {
254
249
  .attr('title', uiStringSearch)
255
250
  .append(`
256
251
  <div class="BRquery">
257
- <div>${queryStringWithBTruncated || queryStringWithB}</div>
258
- <div>${uiStringPage} ${pageNumber}</div>
252
+ <main>${html}</main>
253
+ <footer>Page ${match.displayPageNumber}</footer>
259
254
  </div>
260
255
  `)
261
- .data({ pageIndex })
262
256
  .appendTo(this.br.$('.BRnavline'))
263
- .hover(
264
- (event) => {
265
- // remove from other markers then turn on just for this
266
- // XXX should be done when nav slider moves
267
- const marker = event.currentTarget;
268
- const tooltip = marker.querySelector('.BRquery');
269
- const tooltipOffset = tooltip.getBoundingClientRect();
270
- const targetOffset = marker.getBoundingClientRect();
271
- const boxSizeAdjust = parseInt(getComputedStyle(tooltip).paddingLeft) * 2;
272
- if (tooltipOffset.x - boxSizeAdjust < 0) {
273
- tooltip.style.setProperty('transform', `translateX(-${targetOffset.left - boxSizeAdjust}px)`);
274
- }
275
- $('.BRsearch,.BRchapter').removeClass('front');
276
- $(event.target).addClass('front');
277
- },
278
- (event) => $(event.target).removeClass('front'))
279
- .click(function (event) {
280
- // closures are nested and deep, using an arrow function breaks references.
281
- // Todo: update to arrow function & clean up closures
282
- // to remove `bind` dependency
283
- this.br._searchPluginGoToResult(+$(event.target).data('pageIndex'));
284
- this.br.updateSearchHilites();
285
- }.bind(this));
257
+ .on("mouseenter", (event) => {
258
+ // remove from other markers then turn on just for this
259
+ // XXX should be done when nav slider moves
260
+ const marker = event.currentTarget;
261
+ const tooltip = marker.querySelector('.BRquery');
262
+ const tooltipOffset = tooltip.getBoundingClientRect();
263
+ const targetOffset = marker.getBoundingClientRect();
264
+ const boxSizeAdjust = parseInt(getComputedStyle(tooltip).paddingLeft) * 2;
265
+ if (tooltipOffset.x - boxSizeAdjust < 0) {
266
+ tooltip.style.setProperty('transform', `translateX(-${targetOffset.left - boxSizeAdjust}px)`);
267
+ }
268
+ $('.BRsearch,.BRchapter').removeClass('front');
269
+ $(event.target).addClass('front');
270
+ })
271
+ .on("mouseleave", (event) => $(event.target).removeClass('front'))
272
+ .on("click", () => { this.br._searchPluginGoToResult(match.matchIndex); });
286
273
  });
287
274
  }
288
275
 
@@ -386,14 +373,6 @@ class SearchView {
386
373
  }
387
374
  }
388
375
 
389
- /**
390
- * @param {Event} e
391
- */
392
- handleNavToggledCallback(e) {
393
- const is_visible = this.br.navigationIsVisible();
394
- this.togglePinsFor(is_visible);
395
- }
396
-
397
376
  handleSearchStarted() {
398
377
  this.emptyMatches();
399
378
  this.br.removeSearchHilites();
@@ -428,7 +407,6 @@ class SearchView {
428
407
 
429
408
  window.addEventListener(`${namespace}SearchCallbackError`, this.handleSearchCallbackError.bind(this));
430
409
  $(document).on(`${namespace}SearchCallback`, this.handleSearchCallback.bind(this))
431
- .on(`${namespace}navToggled`, this.handleNavToggledCallback.bind(this))
432
410
  .on(`${namespace}SearchStarted`, this.handleSearchStarted.bind(this))
433
411
  .on(`${namespace}SearchCallbackBookNotIndexed`, this.handleSearchCallbackBookNotIndexed.bind(this))
434
412
  .on(`${namespace}SearchCallbackEmpty`, this.handleSearchCallbackEmpty.bind(this))
@@ -1,12 +1,12 @@
1
1
  import PageChunkIterator from './PageChunkIterator.js';
2
+ import { hasLocalStorage } from './utils.js';
2
3
  /** @typedef {import('./utils.js').ISO6391} ISO6391 */
3
4
  /** @typedef {import('./PageChunk.js')} PageChunk */
4
5
 
5
6
  /**
6
7
  * @export
7
8
  * @typedef {Object} TTSEngineOptions
8
- * @property {String} server
9
- * @property {String} bookPath
9
+ * @property {import('@/src/util/strings.js').StringWithVars} pageChunkUrl
10
10
  * @property {ISO6391} bookLanguage
11
11
  * @property {Function} onLoadingStart
12
12
  * @property {Function} onLoadingComplete
@@ -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 */
@@ -39,6 +40,7 @@ export default class AbstractTTSEngine {
39
40
  constructor(options) {
40
41
  this.playing = false;
41
42
  this.paused = false;
43
+ /** @type {TTSEngineOptions} */
42
44
  this.opts = options;
43
45
  /** @type {PageChunkIterator} */
44
46
  this._chunkIterator = null;
@@ -50,9 +52,7 @@ export default class AbstractTTSEngine {
50
52
  /** @type {SpeechSynthesisVoice} */
51
53
  this.voice = null;
52
54
  // Listen for voice changes (fired by subclasses)
53
- this.events.on('voiceschanged', () => {
54
- this.voice = AbstractTTSEngine.getBestBookVoice(this.getVoices(), this.opts.bookLanguage);
55
- });
55
+ this.events.on('voiceschanged', this.updateBestVoice);
56
56
  this.events.trigger('voiceschanged');
57
57
  }
58
58
 
@@ -71,17 +71,21 @@ export default class AbstractTTSEngine {
71
71
  /** @abstract */
72
72
  init() { return null; }
73
73
 
74
+ updateBestVoice = () => {
75
+ this.voice = AbstractTTSEngine.getBestBookVoice(this.getVoices(), this.opts.bookLanguage);
76
+ }
77
+
74
78
  /**
75
79
  * @param {number} leafIndex
76
80
  * @param {number} numLeafs total number of leafs in the current book
77
81
  */
78
82
  start(leafIndex, numLeafs) {
79
83
  this.playing = true;
84
+ this.paused = false;
80
85
  this.opts.onLoadingStart();
81
86
 
82
87
  this._chunkIterator = new PageChunkIterator(numLeafs, leafIndex, {
83
- server: this.opts.server,
84
- bookPath: this.opts.bookPath,
88
+ pageChunkUrl: this.opts.pageChunkUrl,
85
89
  pageBufferSize: 5,
86
90
  });
87
91
 
@@ -92,6 +96,7 @@ export default class AbstractTTSEngine {
92
96
  stop() {
93
97
  if (this.activeSound) this.activeSound.stop();
94
98
  this.playing = false;
99
+ this.paused = true;
95
100
  this._chunkIterator = null;
96
101
  this.activeSound = null;
97
102
  this.events.trigger('stop');
@@ -124,13 +129,26 @@ export default class AbstractTTSEngine {
124
129
  }
125
130
 
126
131
  /** @public */
127
- jumpBackward() {
128
- Promise.all([
132
+ async jumpBackward() {
133
+ await Promise.all([
129
134
  this.activeSound.stop(),
130
135
  this._chunkIterator.decrement()
131
- .then(() => this._chunkIterator.decrement())
132
- ])
133
- .then(() => this.step());
136
+ .then(() => this._chunkIterator.decrement()),
137
+ ]);
138
+ this.step();
139
+ }
140
+
141
+ /** @param {string} voiceURI */
142
+ setVoice(voiceURI) {
143
+ // if the user actively selects a voice, don't re-choose best voice anymore
144
+ // MS Edge fires voices changed randomly very often
145
+ this.events.off('voiceschanged', this.updateBestVoice);
146
+ this.voice = this.getVoices().find(voice => voice.voiceURI === voiceURI);
147
+ // if the current book has a language set, store the selected voice with the book language as a suffix
148
+ if (this.opts.bookLanguage && hasLocalStorage()) {
149
+ localStorage.setItem(`BRtts-voice-${this.opts.bookLanguage}`, this.voice.voiceURI);
150
+ }
151
+ if (this.activeSound) this.activeSound.setVoice(this.voice);
134
152
  }
135
153
 
136
154
  /** @param {number} newRate */
@@ -140,36 +158,33 @@ export default class AbstractTTSEngine {
140
158
  }
141
159
 
142
160
  /** @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
- });
161
+ async step() {
162
+ const chunk = await this._chunkIterator.next();
163
+ if (chunk == PageChunkIterator.AT_END) {
164
+ this.stop();
165
+ this.opts.onDone();
166
+ return;
167
+ }
168
+ this.opts.onLoadingStart();
169
+ const sound = this.createSound(chunk);
170
+ sound.chunk = chunk;
171
+ sound.rate = this.playbackRate;
172
+ sound.voice = this.voice;
173
+ sound.load(() => this.opts.onLoadingComplete());
174
+
175
+ this.opts.onLoadingComplete();
176
+
177
+ await this.opts.beforeChunkPlay(chunk);
178
+
179
+ if (!this.playing) return;
180
+
181
+ const playPromise = await this.playSound(sound)
182
+ .then(()=> this.opts.afterChunkPlay(sound.chunk));
183
+
184
+ if (this.paused) this.pause();
185
+ await playPromise;
186
+
187
+ if (this.playing) return this.step();
173
188
  }
174
189
 
175
190
  /**
@@ -212,10 +227,12 @@ export default class AbstractTTSEngine {
212
227
  // user languages that match the book language
213
228
  const matchingUserLangs = userLanguages.filter(lang => lang.startsWith(bookLanguage));
214
229
 
215
- // Try to find voices that intersect these two sets
216
- return AbstractTTSEngine.getMatchingVoice(matchingUserLangs, bookLangVoices) ||
230
+ // First try to find the last chosen voice from localStorage for the current book language
231
+ return AbstractTTSEngine.getMatchingStoredVoice(bookLangVoices, bookLanguage)
232
+ // Try to find voices that intersect these two sets
233
+ || AbstractTTSEngine.getMatchingVoice(matchingUserLangs, bookLangVoices)
217
234
  // no user languages match the books; let's return the best voice for the book language
218
- (bookLangVoices.find(v => v.default) || bookLangVoices[0])
235
+ || (bookLangVoices.find(v => v.default) || bookLangVoices[0])
219
236
  // No voices match the book language? let's find a voice in the user's language
220
237
  // and ignore book lang
221
238
  || AbstractTTSEngine.getMatchingVoice(userLanguages, voices)
@@ -223,6 +240,19 @@ export default class AbstractTTSEngine {
223
240
  || (voices.find(v => v.default) || voices[0]);
224
241
  }
225
242
 
243
+ /**
244
+ * @private
245
+ * Get the voice last selected by the user for the book language from localStorage.
246
+ * Returns undefined if no voice is stored or found.
247
+ * @param {SpeechSynthesisVoice[]} voices browser voices to choose from
248
+ * @param {ISO6391} bookLanguage book language to look for
249
+ * @return {SpeechSynthesisVoice | undefined}
250
+ */
251
+ static getMatchingStoredVoice(voices, bookLanguage) {
252
+ const storedVoice = hasLocalStorage() && localStorage.getItem(`BRtts-voice-${bookLanguage}`);
253
+ return (storedVoice ? voices.find(v => v.voiceURI === storedVoice) : undefined);
254
+ }
255
+
226
256
  /**
227
257
  * @private
228
258
  * Get the best voice that matches one of the BCP47 languages (order by preference)
@@ -1,5 +1,6 @@
1
1
  import AbstractTTSEngine from './AbstractTTSEngine.js';
2
- import { sleep } from './utils.js';
2
+ import { sleep } from '../../BookReader/utils.js';
3
+ import { applyVariables } from '../../util/strings.js';
3
4
  /* global soundManager */
4
5
  import 'soundmanager2';
5
6
  import 'jquery.browser';
@@ -17,19 +18,21 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
17
18
  return typeof(soundManager) !== 'undefined' && soundManager.supported();
18
19
  }
19
20
 
20
- /** @param {TTSEngineOptions} options */
21
+ /** @param {TTSEngineOptions | { festivalUrl: import('@/src/util/strings.js').StringWithVars }} options */
21
22
  constructor(options) {
22
23
  super(options);
23
24
  // $.browsers is sometimes undefined on some Android browsers :/
24
25
  // Likely related to when $.browser was moved to npm
25
26
  /** @type {'mp3' | 'ogg'} format of audio to get */
26
- this.audioFormat = $.browser?.mozilla ? 'ogg' : 'mp3';
27
+ this.audioFormat = $.browser?.mozilla ? 'ogg' : 'mp3'; //eslint-disable-line no-jquery/no-browser
28
+ /** @type {import('@/src/util/strings.js').StringWithVars} */
29
+ this.festivalUrl = options.festivalUrl;
27
30
  }
28
31
 
29
32
  /** @override */
30
33
  getVoices() {
31
34
  return [
32
- { default: true, lang: "en-US", localService: false, name: "Festival - English (US)", voiceURI: null }
35
+ { default: true, lang: "en-US", localService: false, name: "Festival - English (US)", voiceURI: null },
33
36
  ];
34
37
  }
35
38
 
@@ -45,7 +48,7 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
45
48
  url: '/bookreader/BookReader/soundmanager/swf',
46
49
  useHTML5Audio: true,
47
50
  //flash 8 version of swf is buggy when calling play() on a sound that is still loading
48
- flashVersion: 9
51
+ flashVersion: 9,
49
52
  });
50
53
  }
51
54
 
@@ -68,19 +71,7 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
68
71
 
69
72
  /** @override */
70
73
  createSound(chunk) {
71
- return new FestivalTTSSound(this.getSoundUrl(chunk.text));
72
- }
73
-
74
- /**
75
- * @private
76
- * Get URL for audio that says this text
77
- * @param {String} dataString the thing to say
78
- * @return {String} url
79
- */
80
- getSoundUrl(dataString) {
81
- return 'https://' + this.opts.server + '/BookReader/BookReaderGetTTS.php?string='
82
- + encodeURIComponent(dataString)
83
- + '&format=.' + this.audioFormat;
74
+ return new FestivalTTSSound(applyVariables(this.festivalUrl, { text: chunk.text, format: this.audioFormat }));
84
75
  }
85
76
 
86
77
  /**
@@ -91,10 +82,10 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
91
82
  * See https://stackoverflow.com/questions/12206631/html5-audio-cant-play-through-javascript-unless-triggered-manually-once
92
83
  * @return {PromiseLike}
93
84
  */
94
- iOSCaptureUserIntentHack() {
85
+ async iOSCaptureUserIntentHack() {
95
86
  const sound = soundManager.createSound({ url: SILENCE_1MS[this.audioFormat] });
96
- return new Promise(res => sound.play({onfinish: res}))
97
- .then(() => sound.destruct());
87
+ await new Promise(res => sound.play({onfinish: res}));
88
+ sound.destruct();
98
89
  }
99
90
  }
100
91
 
@@ -107,7 +98,7 @@ class FestivalTTSSound {
107
98
  this.sound = null;
108
99
  this.rate = 1;
109
100
  /** @type {function} calling this resolves the "play" promise */
110
- this._finishResolver = null
101
+ this._finishResolver = null;
111
102
  }
112
103
 
113
104
  get loaded() {
@@ -122,21 +113,20 @@ class FestivalTTSSound {
122
113
  if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
123
114
  onload();
124
115
  },
125
- onresume: () => {
126
- sleep(25).then(() => {
127
- if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
128
- });
129
- }
116
+ onresume: async () => {
117
+ await sleep(25);
118
+ if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
119
+ },
130
120
  });
131
121
  return this.sound.load();
132
122
  }
133
123
 
134
- play() {
135
- return new Promise(res => {
124
+ async play() {
125
+ await new Promise(res => {
136
126
  this._finishResolver = res;
137
127
  this.sound.play({ onfinish: res });
138
- })
139
- .then(() => this.sound.destruct());
128
+ });
129
+ this.sound.destruct();
140
130
  }
141
131
 
142
132
  /** @override */
@@ -1,3 +1,5 @@
1
+ import { applyVariables } from "../../util/strings.js";
2
+
1
3
  /**
2
4
  * Class to manage a 'chunk' (approximately a paragraph) of text on a page.
3
5
  */
@@ -16,32 +18,20 @@ export default class PageChunk {
16
18
  }
17
19
 
18
20
  /**
19
- * @param {string} server
20
- * @param {string} bookPath
21
+ * @param {import('@/src/util/strings.js').StringWithVars} pageChunkUrl
21
22
  * @param {number} leafIndex
22
23
  * @return {Promise<PageChunk[]>}
23
24
  */
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
- });
25
+ static async fetch(pageChunkUrl, leafIndex) {
26
+ const chunks = await $.ajax({
27
+ type: 'GET',
28
+ url: applyVariables(pageChunkUrl, { pageIndex: leafIndex }),
29
+ cache: true,
30
+ xhrFields: {
31
+ withCredentials: window.br.protected,
32
+ },
44
33
  });
34
+ return PageChunk._fromTextWrapperResponse(leafIndex, chunks);
45
35
  }
46
36
 
47
37
  /**
@@ -97,7 +87,10 @@ export default class PageChunk {
97
87
  * @return {string}
98
88
  */
99
89
  static _removeDanglingHyphens(text) {
100
- return text.replace(/-\s+/g, '');
90
+ // Some books mis-OCR a dangling hyphen as a ¬ (mathematical not sign) . Since in math
91
+ // the not sign should not appear followed by a space, we think we can safely assume
92
+ // this should be replaced.
93
+ return text.replace(/[-¬]\s+/g, '');
101
94
  }
102
95
  }
103
96
 
@@ -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
  /**
@@ -142,7 +138,7 @@ export default class PageChunkIterator {
142
138
  * @param {number} index
143
139
  */
144
140
  _fetchPageChunksDirect(index) {
145
- return PageChunk.fetch(this.opts.server, this.opts.bookPath, index);
141
+ return PageChunk.fetch(this.opts.pageChunkUrl, index);
146
142
  }
147
143
  }
148
144
 
@@ -150,14 +146,12 @@ PageChunkIterator.AT_END = "__PageChunkIterator.AT_END__";
150
146
 
151
147
  /** @type {PageChunkIteratorOptions} */
152
148
  const DEFAULT_OPTS = {
153
- server: null,
154
- bookPath: null,
149
+ pageChunkUrl: null,
155
150
  pageBufferSize: 2,
156
151
  };
157
152
 
158
153
  /**
159
154
  * @typedef {Object} PageChunkIteratorOptions
160
- * @property {string} server
161
- * @property {string} bookPath
155
+ * @property {import('@/src/util/strings.js').StringWithVars} pageChunkUrl
162
156
  * @property {number} [pageBufferSize] number of pages to buffer before/after the current page
163
157
  */