@internetarchive/bookreader 5.0.0-8-multiple-files → 5.0.0-80

Sign up to get free protection for your applications and to get access to all the features.
Files changed (321) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +78 -6
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +442 -1393
  6. package/BookReader/BookReader.js +2 -21564
  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 -12
  13. package/BookReader/icons/2up.svg +1 -15
  14. package/BookReader/icons/advance.svg +3 -26
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -0
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -17
  19. package/BookReader/icons/fullscreen_exit.svg +1 -17
  20. package/BookReader/icons/hamburger.svg +1 -15
  21. package/BookReader/icons/left-arrow.svg +1 -12
  22. package/BookReader/icons/magnify-minus.svg +1 -16
  23. package/BookReader/icons/magnify-plus.svg +1 -17
  24. package/BookReader/icons/magnify.svg +1 -15
  25. package/BookReader/icons/pause.svg +1 -23
  26. package/BookReader/icons/play.svg +1 -22
  27. package/BookReader/icons/playback-speed.svg +1 -34
  28. package/BookReader/icons/read-aloud.svg +1 -22
  29. package/BookReader/icons/review.svg +3 -22
  30. package/BookReader/icons/thumbnails.svg +1 -17
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -22
  33. package/BookReader/images/BRicons.svg +5 -94
  34. package/BookReader/images/books_graphic.svg +1 -177
  35. package/BookReader/images/icon_book.svg +1 -12
  36. package/BookReader/images/icon_bookmark.svg +1 -12
  37. package/BookReader/images/icon_gear.svg +1 -14
  38. package/BookReader/images/icon_hamburger.svg +1 -20
  39. package/BookReader/images/icon_home.svg +1 -21
  40. package/BookReader/images/icon_info.svg +1 -11
  41. package/BookReader/images/icon_one_page.svg +1 -8
  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 -15
  45. package/BookReader/images/icon_search_button.svg +1 -8
  46. package/BookReader/images/icon_share.svg +1 -9
  47. package/BookReader/images/icon_skip-ahead.svg +1 -6
  48. package/BookReader/images/icon_skip-back.svg +2 -13
  49. package/BookReader/images/icon_speaker.svg +1 -18
  50. package/BookReader/images/icon_speaker_open.svg +1 -10
  51. package/BookReader/images/icon_thumbnails.svg +1 -12
  52. package/BookReader/images/icon_toc.svg +1 -5
  53. package/BookReader/images/icon_two_pages.svg +1 -9
  54. package/BookReader/images/marker_chap-off.svg +1 -11
  55. package/BookReader/images/marker_chap-on.svg +1 -11
  56. package/BookReader/images/marker_srch-on.svg +1 -11
  57. package/BookReader/jquery-3.js +2 -0
  58. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  59. package/BookReader/plugins/plugin.archive_analytics.js +1 -172
  60. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  61. package/BookReader/plugins/plugin.autoplay.js +1 -165
  62. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  63. package/BookReader/plugins/plugin.chapters.js +22 -301
  64. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  65. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  66. package/BookReader/plugins/plugin.iframe.js +1 -74
  67. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  68. package/BookReader/plugins/plugin.iiif.js +2 -0
  69. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  70. package/BookReader/plugins/plugin.resume.js +1 -368
  71. package/BookReader/plugins/plugin.resume.js.map +1 -1
  72. package/BookReader/plugins/plugin.search.js +2 -1420
  73. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  74. package/BookReader/plugins/plugin.search.js.map +1 -1
  75. package/BookReader/plugins/plugin.text_selection.js +2 -1080
  76. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  77. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  78. package/BookReader/plugins/plugin.tts.js +2 -9193
  79. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  80. package/BookReader/plugins/plugin.tts.js.map +1 -1
  81. package/BookReader/plugins/plugin.url.js +1 -269
  82. package/BookReader/plugins/plugin.url.js.map +1 -1
  83. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -379
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  85. package/BookReader/webcomponents-bundle.js +3 -0
  86. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  87. package/BookReader/webcomponents-bundle.js.map +1 -0
  88. package/BookReaderDemo/BookReaderDemo.css +18 -19
  89. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  90. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  91. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  92. package/BookReaderDemo/IADemoBr.js +147 -0
  93. package/BookReaderDemo/demo-advanced.html +2 -2
  94. package/BookReaderDemo/demo-autoplay.html +2 -3
  95. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  96. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  97. package/BookReaderDemo/demo-fullscreen.html +2 -4
  98. package/BookReaderDemo/demo-iiif.html +99 -12
  99. package/BookReaderDemo/demo-internetarchive.html +214 -18
  100. package/BookReaderDemo/demo-multiple.html +2 -1
  101. package/BookReaderDemo/demo-preview-pages.html +2 -1
  102. package/BookReaderDemo/demo-simple.html +2 -1
  103. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
  104. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  105. package/BookReaderDemo/immersion-1up.html +2 -2
  106. package/BookReaderDemo/immersion-mode.html +2 -4
  107. package/BookReaderDemo/toggle_controls.html +3 -2
  108. package/BookReaderDemo/view_mode.html +2 -1
  109. package/BookReaderDemo/viewmode-cycle.html +2 -3
  110. package/CHANGELOG.md +545 -33
  111. package/README.md +14 -1
  112. package/babel.config.js +20 -0
  113. package/codecov.yml +6 -0
  114. package/index.html +4 -1
  115. package/jsconfig.json +19 -0
  116. package/netlify.toml +9 -0
  117. package/package.json +70 -60
  118. package/renovate.json +52 -0
  119. package/scripts/preversion.js +0 -1
  120. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  121. package/src/BookNavigator/assets/button-base.js +9 -2
  122. package/src/BookNavigator/assets/ia-logo.js +17 -0
  123. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  124. package/src/BookNavigator/assets/icon_close.js +1 -1
  125. package/src/BookNavigator/book-navigator.js +590 -0
  126. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  127. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  128. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  129. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  130. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  131. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  132. package/src/BookNavigator/delete-modal-actions.js +1 -1
  133. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  134. package/src/BookNavigator/downloads/downloads.js +41 -25
  135. package/src/BookNavigator/search/search-provider.js +80 -28
  136. package/src/BookNavigator/search/search-results.js +28 -25
  137. package/src/BookNavigator/sharing.js +27 -0
  138. package/src/BookNavigator/viewable-files.js +95 -0
  139. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  140. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  141. package/src/BookReader/BookModel.js +64 -34
  142. package/src/BookReader/DragScrollable.js +233 -0
  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 +776 -0
  147. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  148. package/src/BookReader/ModeSmoothZoom.js +312 -0
  149. package/src/BookReader/ModeThumb.js +18 -12
  150. package/src/BookReader/Navbar/Navbar.js +14 -40
  151. package/src/BookReader/PageContainer.js +81 -6
  152. package/src/BookReader/ReduceSet.js +1 -1
  153. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  154. package/src/BookReader/events.js +2 -3
  155. package/src/BookReader/options.js +27 -2
  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.js +118 -13
  160. package/src/BookReader.js +446 -1062
  161. package/src/assets/icons/close-circle-dark.svg +1 -0
  162. package/src/assets/icons/magnify-minus.svg +3 -7
  163. package/src/assets/icons/magnify-plus.svg +3 -7
  164. package/src/assets/icons/voice.svg +1 -0
  165. package/src/css/BookReader.scss +1 -17
  166. package/src/css/_BRBookmarks.scss +1 -1
  167. package/src/css/_BRComponent.scss +1 -1
  168. package/src/css/_BRmain.scss +33 -27
  169. package/src/css/_BRnav.scss +12 -39
  170. package/src/css/_BRpages.scss +149 -40
  171. package/src/css/_BRsearch.scss +68 -230
  172. package/src/css/_BRtoolbar.scss +5 -5
  173. package/src/css/_TextSelection.scss +87 -27
  174. package/src/css/_colorbox.scss +2 -2
  175. package/src/css/_controls.scss +20 -7
  176. package/src/css/_icons.scss +7 -1
  177. package/src/ia-bookreader/ia-bookreader.js +224 -0
  178. package/src/plugins/plugin.archive_analytics.js +3 -3
  179. package/src/plugins/plugin.autoplay.js +5 -11
  180. package/src/plugins/plugin.chapters.js +237 -191
  181. package/src/plugins/plugin.iiif.js +151 -0
  182. package/src/plugins/plugin.resume.js +3 -3
  183. package/src/plugins/plugin.text_selection.js +464 -134
  184. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  185. package/src/plugins/search/plugin.search.js +175 -120
  186. package/src/plugins/search/utils.js +43 -0
  187. package/src/plugins/search/view.js +64 -202
  188. package/src/plugins/tts/AbstractTTSEngine.js +71 -40
  189. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  190. package/src/plugins/tts/PageChunk.js +15 -21
  191. package/src/plugins/tts/PageChunkIterator.js +8 -12
  192. package/src/plugins/tts/WebTTSEngine.js +87 -71
  193. package/src/plugins/tts/plugin.tts.js +96 -127
  194. package/src/plugins/tts/utils.js +15 -25
  195. package/src/plugins/url/UrlPlugin.js +191 -0
  196. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  197. package/src/util/browserSniffing.js +22 -0
  198. package/src/util/docCookies.js +21 -2
  199. package/tests/e2e/README.md +37 -0
  200. package/tests/e2e/autoplay.test.js +2 -2
  201. package/tests/e2e/base.test.js +8 -16
  202. package/tests/e2e/helpers/base.js +53 -48
  203. package/tests/e2e/helpers/debug.js +1 -1
  204. package/tests/e2e/helpers/params.js +17 -0
  205. package/tests/e2e/helpers/rightToLeft.js +8 -14
  206. package/tests/e2e/helpers/search.js +73 -0
  207. package/tests/e2e/models/Navigation.js +20 -37
  208. package/tests/e2e/rightToLeft.test.js +4 -5
  209. package/tests/e2e/viewmode.test.js +40 -33
  210. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  211. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  212. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  213. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  214. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  215. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  216. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  217. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  218. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +109 -60
  219. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  220. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  221. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  222. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  223. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  224. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  225. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  226. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  227. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  228. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  229. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  230. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  231. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  232. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  233. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  234. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  235. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  236. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  237. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  238. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  239. package/tests/jest/BookReader/utils.test.js +229 -0
  240. package/tests/jest/BookReader.keyboard.test.js +190 -0
  241. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  242. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  243. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  244. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  245. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  246. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  247. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  248. package/tests/jest/plugins/plugin.text_selection.test.js +317 -0
  249. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  250. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +26 -47
  251. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +39 -6
  252. package/tests/jest/plugins/search/utils.js +25 -0
  253. package/tests/jest/plugins/search/utils.test.js +29 -0
  254. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +29 -9
  255. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  256. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  257. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  258. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  259. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  260. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  261. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  262. package/tests/jest/setup.js +3 -0
  263. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  264. package/tests/jest/util/docCookies.test.js +24 -0
  265. package/tests/{util → jest/util}/strings.test.js +1 -1
  266. package/tests/{utils.js → jest/utils.js} +38 -0
  267. package/webpack.config.js +12 -6
  268. package/.babelrc +0 -12
  269. package/.dependabot/config.yml +0 -6
  270. package/.testcaferc.json +0 -5
  271. package/BookReader/bookreader-component-bundle.js +0 -14312
  272. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  273. package/BookReader/bookreader-component-bundle.js.map +0 -1
  274. package/BookReader/icons/sort-ascending.svg +0 -1
  275. package/BookReader/icons/sort-descending.svg +0 -1
  276. package/BookReader/jquery-1.10.1.js +0 -108
  277. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  278. package/BookReader/plugins/plugin.menu_toggle.js +0 -369
  279. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  280. package/BookReader/plugins/plugin.mobile_nav.js +0 -335
  281. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  282. package/BookReaderDemo/IIIFBookReader.js +0 -207
  283. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  284. package/BookReaderDemo/demo-iiif.js +0 -26
  285. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  286. package/karma.conf.js +0 -23
  287. package/src/BookNavigator/BookModel.js +0 -14
  288. package/src/BookNavigator/BookNavigator.js +0 -452
  289. package/src/BookNavigator/assets/book-loader.js +0 -27
  290. package/src/BookNavigator/assets/icon_sort_ascending.js +0 -5
  291. package/src/BookNavigator/assets/icon_sort_descending.js +0 -5
  292. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  293. package/src/BookNavigator/search/a-search-result.js +0 -55
  294. package/src/BookNavigator/volumes/volumes-provider.js +0 -76
  295. package/src/BookNavigator/volumes/volumes.js +0 -161
  296. package/src/BookReader/DebugConsole.js +0 -54
  297. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  298. package/src/ItemNavigator/ItemNavigator.js +0 -372
  299. package/src/ItemNavigator/providers/sharing.js +0 -29
  300. package/src/assets/icons/sort-ascending.svg +0 -1
  301. package/src/assets/icons/sort-descending.svg +0 -1
  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/utils.test.js +0 -109
  311. package/tests/e2e/helpers/desktopSearch.js +0 -72
  312. package/tests/e2e/helpers/mobileSearch.js +0 -85
  313. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  314. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  315. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  316. package/tests/karma/BookNavigator/volumes.test.js +0 -101
  317. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  318. package/tests/plugins/plugin.chapters.test.js +0 -130
  319. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  320. package/tests/plugins/plugin.text_selection.test.js +0 -203
  321. package/tests/util/docCookies.test.js +0 -15
@@ -56,7 +56,7 @@ if (!isMobile()) {
56
56
  $(document.body).append(cboxOverlay).append(cbox);
57
57
  }
58
58
  });
59
- }
59
+ };
60
60
  })(BookReader.prototype.init);
61
61
 
62
62
  /**
@@ -92,7 +92,7 @@ if (!isMobile()) {
92
92
  $(document).off('keyup' + EVENT_NAMESPACE);
93
93
 
94
94
  this.isFullscreenActive = false;
95
- this.updateBrClasses()
95
+ this.updateBrClasses();
96
96
 
97
97
  this.resize();
98
98
  this.refs.$brContainer.animate({ opacity: 1 }, 400, 'linear');
@@ -233,8 +233,8 @@ export function bindFullscreenChangeListener(
233
233
  'moz',
234
234
  'ms'
235
235
  ];
236
- const all_events = $.trim(event + vendor_prefixes.join(event) + event);
237
- $(document).bind(all_events, data, fullscreenchangeListener);
236
+ const all_events = (event + vendor_prefixes.join(event) + event).trim();
237
+ $(document).on(all_events, data, fullscreenchangeListener);
238
238
  }
239
239
 
240
240
  /**
@@ -1,9 +1,7 @@
1
+ // @ts-check
1
2
  /* global BookReader */
2
3
  /**
3
4
  * Plugin for Archive.org book search
4
- * NOTE: This script must be loaded AFTER `plugin.mobile_nav.js`
5
- * as it mutates mobile nav drawer
6
- *
7
5
  * Events fired at various points throughout search processing are published
8
6
  * on the document DOM element. These can be subscribed to using jQuery's event
9
7
  * binding method `$.fn.on`. All of the events are prefixed with a BookReader
@@ -20,8 +18,17 @@
20
18
  * the book has not had OCR text indexed yet. Receives `instance`
21
19
  * @event BookReader:SearchCallbackEmpty - When no results found. Receives
22
20
  * `instance`
21
+ * @event BookReader:SearchCanceled - When no results found. Receives
22
+ * `instance`
23
23
  */
24
+ import { poll } from '../../BookReader/utils.js';
25
+ import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
24
26
  import SearchView from './view.js';
27
+ import { marshallSearchResults } from './utils.js';
28
+ /** @typedef {import('../../BookReader/PageContainer').PageContainer} PageContainer */
29
+ /** @typedef {import('../../BookReader/BookModel').PageIndex} PageIndex */
30
+ /** @typedef {import('../../BookReader/BookModel').LeafNum} LeafNum */
31
+ /** @typedef {import('../../BookReader/BookModel').PageNumString} PageNumString */
25
32
 
26
33
  jQuery.extend(BookReader.defaultOptions, {
27
34
  server: 'ia600609.us.archive.org',
@@ -29,7 +36,10 @@ jQuery.extend(BookReader.defaultOptions, {
29
36
  subPrefix: '',
30
37
  bookPath: '',
31
38
  enableSearch: true,
39
+ searchInsideProtocol: 'https',
32
40
  searchInsideUrl: '/fulltext/inside.php',
41
+ searchInsidePreTag: '{{{',
42
+ searchInsidePostTag: '}}}',
33
43
  initialSearchTerm: null,
34
44
  });
35
45
 
@@ -42,7 +52,6 @@ BookReader.prototype.setup = (function (super_) {
42
52
  this.searchResults = null;
43
53
  this.searchInsideUrl = options.searchInsideUrl;
44
54
  this.enableSearch = options.enableSearch;
45
- this.goToFirstResult = false;
46
55
 
47
56
  // Base server used by some api calls
48
57
  this.bookId = options.bookId;
@@ -50,11 +59,14 @@ BookReader.prototype.setup = (function (super_) {
50
59
  this.subPrefix = options.subPrefix;
51
60
  this.bookPath = options.bookPath;
52
61
 
53
- if (this.searchView) { return; }
54
- this.searchView = new SearchView({
55
- br: this,
56
- selector: '#BRsearch_tray',
57
- });
62
+ this.searchXHR = null;
63
+ this._cancelSearch.bind(this);
64
+ this.cancelSearchRequest.bind(this);
65
+
66
+ /** @type { {[pageIndex: number]: SearchInsideMatchBox[]} } */
67
+ this._searchBoxesByIndex = {};
68
+
69
+ this.searchView = undefined;
58
70
  };
59
71
  })(BookReader.prototype.setup);
60
72
 
@@ -62,28 +74,31 @@ BookReader.prototype.setup = (function (super_) {
62
74
  BookReader.prototype.init = (function (super_) {
63
75
  return function () {
64
76
  super_.call(this);
65
-
77
+ // give SearchView the most complete bookreader state
78
+ this.searchView = new SearchView({
79
+ br: this,
80
+ searchCancelledCallback: () => {
81
+ this._cancelSearch();
82
+ this.trigger('SearchCanceled', { term: this.searchTerm, instance: this });
83
+ }
84
+ });
66
85
  if (this.options.enableSearch && this.options.initialSearchTerm) {
86
+ /**
87
+ * this.search() take two parameter
88
+ * 1. this.options.initialSearchTerm - search term
89
+ * 2. {
90
+ * goToFirstResult: this.options.goToFirstResult,
91
+ * suppressFragmentChange: false // always want to change fragment in URL
92
+ * }
93
+ */
67
94
  this.search(
68
95
  this.options.initialSearchTerm,
69
- { goToFirstResult: this.goToFirstResult, suppressFragmentChange: true }
96
+ { goToFirstResult: this.options.goToFirstResult, suppressFragmentChange: false }
70
97
  );
71
98
  }
72
99
  };
73
100
  })(BookReader.prototype.init);
74
101
 
75
- /** @override */
76
- BookReader.prototype.buildMobileDrawerElement = (function (super_) {
77
- return function () {
78
- const $el = super_.call(this);
79
- if (!this.enableSearch) { return; }
80
- if (this.searchView.dom.mobileSearch) {
81
- $el.find('.BRmobileMenu__moreInfoRow').after(this.searchView.dom.mobileSearch);
82
- }
83
- return $el;
84
- };
85
- })(BookReader.prototype.buildMobileDrawerElement);
86
-
87
102
  /** @override */
88
103
  BookReader.prototype.buildToolbarElement = (function (super_) {
89
104
  return function () {
@@ -96,6 +111,25 @@ BookReader.prototype.buildToolbarElement = (function (super_) {
96
111
  };
97
112
  })(BookReader.prototype.buildToolbarElement);
98
113
 
114
+ /** @override */
115
+ BookReader.prototype._createPageContainer = (function (super_) {
116
+ return function (index) {
117
+ const pageContainer = super_.call(this, index);
118
+ if (this.enableSearch && pageContainer.page && index in this._searchBoxesByIndex) {
119
+ const pageIndex = pageContainer.page.index;
120
+ const boxes = this._searchBoxesByIndex[pageIndex];
121
+ renderBoxesInPageContainerLayer(
122
+ 'searchHiliteLayer',
123
+ boxes,
124
+ pageContainer.page,
125
+ pageContainer.$container[0],
126
+ boxes.map(b => `match-index-${b.matchIndex}`),
127
+ );
128
+ }
129
+ return pageContainer;
130
+ };
131
+ })(BookReader.prototype._createPageContainer);
132
+
99
133
  /**
100
134
  * @typedef {object} SearchOptions
101
135
  * @property {boolean} goToFirstResult
@@ -110,7 +144,7 @@ BookReader.prototype.buildToolbarElement = (function (super_) {
110
144
  * @param {string} term
111
145
  * @param {SearchOptions} overrides
112
146
  */
113
- BookReader.prototype.search = function(term = '', overrides = {}) {
147
+ BookReader.prototype.search = async function(term = '', overrides = {}) {
114
148
  /** @type {SearchOptions} */
115
149
  const defaultOptions = {
116
150
  goToFirstResult: false, /* jump to the first result (default=false) */
@@ -122,6 +156,7 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
122
156
  };
123
157
  const options = jQuery.extend({}, defaultOptions, overrides);
124
158
  this.suppressFragmentChange = options.suppressFragmentChange;
159
+ this.searchCancelled = false;
125
160
 
126
161
  // strip slashes, since this goes in the url
127
162
  this.searchTerm = term.replace(/\//g, ' ');
@@ -136,7 +171,7 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
136
171
 
137
172
  // Remove the port and userdir
138
173
  const serverPath = this.server.replace(/:.+/, '');
139
- const baseUrl = `https://${serverPath}${this.searchInsideUrl}?`;
174
+ const baseUrl = `${this.options.searchInsideProtocol}://${serverPath}${this.searchInsideUrl}?`;
140
175
 
141
176
  // Remove subPrefix from end of path
142
177
  let path = this.bookPath;
@@ -150,6 +185,8 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
150
185
  doc: this.subPrefix,
151
186
  path,
152
187
  q: term,
188
+ pre_tag: this.options.searchInsidePreTag,
189
+ post_tag: this.options.searchInsidePostTag,
153
190
  };
154
191
 
155
192
  // NOTE that the API does not expect / (slashes) to be encoded. (%2F) won't work
@@ -157,12 +194,16 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
157
194
 
158
195
  const url = `${baseUrl}${paramStr}`;
159
196
 
160
- const processSearchResults = (searchInsideResults) => {
197
+ const callSearchResultsCallback = (searchInsideResults) => {
198
+ if (this.searchCancelled) {
199
+ return;
200
+ }
161
201
  const responseHasError = searchInsideResults.error || !searchInsideResults.matches.length;
162
202
  const hasCustomError = typeof options.error === 'function';
163
203
  const hasCustomSuccess = typeof options.success === 'function';
164
204
 
165
205
  if (responseHasError) {
206
+ console.error('Search Inside Response Error', searchInsideResults.error || 'matches.length == 0');
166
207
  hasCustomError
167
208
  ? options.error.call(this, searchInsideResults, options)
168
209
  : this.BRSearchCallbackError(searchInsideResults, options);
@@ -173,11 +214,39 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
173
214
  }
174
215
  };
175
216
 
176
- this.trigger('SearchStarted', { term: this.searchTerm });
177
- return $.ajax({
217
+ this.trigger('SearchStarted', { term: this.searchTerm, instance: this });
218
+ callSearchResultsCallback(await $.ajax({
178
219
  url: url,
179
- dataType: 'jsonp'
180
- }).then(processSearchResults);
220
+ dataType: 'jsonp',
221
+ cache: true,
222
+ beforeSend: xhr => { this.searchXHR = xhr; },
223
+ }));
224
+ };
225
+
226
+ /**
227
+ * cancels AJAX Call
228
+ * emits custom event
229
+ */
230
+ BookReader.prototype._cancelSearch = function () {
231
+ this.searchXHR?.abort();
232
+ this.searchView.clearSearchFieldAndResults(false);
233
+ this.searchTerm = '';
234
+ this.searchXHR = null;
235
+ this.searchCancelled = true;
236
+ this.searchResults = [];
237
+ };
238
+
239
+ /**
240
+ * External function to cancel search
241
+ * checks for term & xhr in flight before running
242
+ */
243
+ BookReader.prototype.cancelSearchRequest = function () {
244
+ this.searchCancelled = true;
245
+ if (this.searchXHR !== null) {
246
+ this._cancelSearch();
247
+ this.searchView.toggleSearchPending();
248
+ this.trigger('SearchCanceled', { term: this.searchTerm, instance: this });
249
+ }
181
250
  };
182
251
 
183
252
  /**
@@ -188,10 +257,14 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
188
257
  * @property {number} b
189
258
  * @property {number} t
190
259
  * @property {HTMLDivElement} [div]
260
+ * @property {number} matchIndex This is a fake field! not part of the API response. The index of the match that contains this box in total search results matches.
191
261
  */
192
262
 
193
263
  /**
194
264
  * @typedef {object} SearchInsideMatch
265
+ * @property {number} matchIndex This is a fake field! Not part of the API response. It is added by the JS.
266
+ * @property {string} displayPageNumber (fake field) The page number as it should be displayed in the UI.
267
+ * @property {string} html (computed field) The html-escaped raw html to display in the UI.
195
268
  * @property {string} text
196
269
  * @property {Array<{ page: number, boxes: SearchInsideMatchBox[] }>} par
197
270
  */
@@ -205,21 +278,26 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
205
278
 
206
279
  /**
207
280
  * Search Results return handler
208
- * @callback
209
281
  * @param {SearchInsideResults} results
210
282
  * @param {object} options
211
283
  * @param {boolean} options.goToFirstResult
212
284
  */
213
285
  BookReader.prototype.BRSearchCallback = function(results, options) {
214
- this.searchResults = results;
286
+ marshallSearchResults(
287
+ results,
288
+ pageNum => this.book.getPageNum(this.book.leafNumToIndex(pageNum)),
289
+ this.options.searchInsidePreTag,
290
+ this.options.searchInsidePostTag,
291
+ );
292
+ this.searchResults = results || [];
215
293
 
216
294
  this.updateSearchHilites();
217
295
  this.removeProgressPopup();
218
296
  if (options.goToFirstResult) {
219
- this._searchPluginGoToResult(results.matches[0].par[0].page);
297
+ this._searchPluginGoToResult(0);
220
298
  }
221
299
  this.trigger('SearchCallback', { results, options, instance: this });
222
- }
300
+ };
223
301
 
224
302
  /**
225
303
  * Main search results error handler
@@ -259,95 +337,41 @@ BookReader.prototype._BRSearchCallbackError = function(results) {
259
337
  * updates search on-page highlights controller
260
338
  */
261
339
  BookReader.prototype.updateSearchHilites = function() {
262
- if (this.constMode2up == this.mode) {
263
- this.updateSearchHilites2UP();
264
- return;
340
+ /** @type {SearchInsideMatch[]} */
341
+ const matches = this.searchResults?.matches || [];
342
+ /** @type { {[pageIndex: number]: SearchInsideMatchBox[]} } */
343
+ const boxesByIndex = {};
344
+
345
+ // Clear any existing svg layers
346
+ this.removeSearchHilites();
347
+
348
+ // Group by pageIndex
349
+ for (const match of matches) {
350
+ for (const box of match.par[0].boxes) {
351
+ const pageIndex = this.book.leafNumToIndex(box.page);
352
+ const pageBoxes = boxesByIndex[pageIndex] || (boxesByIndex[pageIndex] = []);
353
+ pageBoxes.push(box);
354
+ }
265
355
  }
266
- this.updateSearchHilites1UP();
267
- };
268
356
 
269
- /**
270
- * update search on-page highlights in 1up mode
271
- */
272
- BookReader.prototype.updateSearchHilites1UP = function() {
273
- const results = this.searchResults;
274
- if (null == results) return;
275
- results.matches.forEach(match => {
276
- match.par[0].boxes.forEach(box => {
277
- const pageIndex = this.leafNumToIndex(box.page);
278
- const pageIsInView = jQuery.inArray(pageIndex, this.displayedIndices) >= 0;
279
- if (pageIsInView) {
280
- if (!box.div) {
281
- //create a div for the search highlight, and stash it in the box object
282
- box.div = document.createElement('div');
283
- $(box.div).prop('className', 'BookReaderSearchHilite').appendTo(this.$(`.pagediv${pageIndex}`));
284
- }
285
- const page = this._models.book.getPage(pageIndex);
286
- const highlight = {
287
- width: this._modes.mode1Up.physicalInchesToDisplayPixels((box.r - box.l) / page.ppi),
288
- height: this._modes.mode1Up.physicalInchesToDisplayPixels((box.b - box.t) / page.ppi),
289
- left: this._modes.mode1Up.physicalInchesToDisplayPixels(box.l / page.ppi),
290
- top: this._modes.mode1Up.physicalInchesToDisplayPixels(box.t / page.ppi),
291
- };
292
- $(box.div).css(highlight);
293
- } else {
294
- if (box.div) {
295
- $(box.div).remove();
296
- box.div = null;
297
- }
298
- }
299
- });
300
- });
301
- };
302
-
303
- /**
304
- * update search on-page highlights in 2up mode
305
- */
306
- BookReader.prototype.updateSearchHilites2UP = function() {
307
- const results = this.searchResults;
357
+ // update any already created pages
358
+ for (const [pageIndexString, boxes] of Object.entries(boxesByIndex)) {
359
+ const pageIndex = parseFloat(pageIndexString);
360
+ const page = this.book.getPage(pageIndex);
361
+ const pageContainers = this.getActivePageContainerElementsForIndex(pageIndex);
362
+ for (const container of pageContainers) {
363
+ renderBoxesInPageContainerLayer('searchHiliteLayer', boxes, page, container, boxes.map(b => `match-index-${b.matchIndex}`));
364
+ }
365
+ }
308
366
 
309
- if (results === null) return;
310
-
311
- const { matches } = results;
312
- matches.forEach((match) => {
313
- match.par[0].boxes.forEach(box => {
314
- const pageIndex = this.leafNumToIndex(match.par[0].page);
315
- const pageIsInView = jQuery.inArray(pageIndex, this.displayedIndices) >= 0;
316
- const { isViewable } = this._models.book.getPage(pageIndex);
317
-
318
- if (pageIsInView && isViewable) {
319
- if (!box.div) {
320
- //create a div for the search highlight, and stash it in the box object
321
- box.div = document.createElement('div');
322
- $(box.div).addClass('BookReaderSearchHilite')
323
- .appendTo(this.refs.$brTwoPageView);
324
- }
325
- this.setHilightCss2UP(box.div, pageIndex, box.l, box.r, box.t, box.b);
326
- } else {
327
- // clear stale reference
328
- if (box.div) {
329
- $(box.div).remove();
330
- box.div = null;
331
- }
332
- }
333
- });
334
- });
367
+ this._searchBoxesByIndex = boxesByIndex;
335
368
  };
336
369
 
337
370
  /**
338
371
  * remove search highlights
339
372
  */
340
373
  BookReader.prototype.removeSearchHilites = function() {
341
- const results = this.searchResults;
342
- if (null == results || !results.matches) { return; }
343
- results.matches.forEach(match => {
344
- match.par[0].boxes.forEach(box => {
345
- if (null != box.div) {
346
- $(box.div).remove();
347
- box.div = null;
348
- }
349
- });
350
- });
374
+ $(this.getActivePageContainerElements()).find('.searchHiliteLayer').remove();
351
375
  };
352
376
 
353
377
  /**
@@ -355,11 +379,14 @@ BookReader.prototype.removeSearchHilites = function() {
355
379
  * Goes to the page specified. If the page is not viewable, tries to load the page
356
380
  * FIXME Most of this logic is IA specific, and should be less integrated into here
357
381
  * or at least more configurable.
358
- * @param {PageIndex} pageIndex
382
+ * @param {number} matchIndex
359
383
  */
360
- BookReader.prototype._searchPluginGoToResult = async function (pageIndex) {
361
- const { book } = this._models;
384
+ BookReader.prototype._searchPluginGoToResult = async function (matchIndex) {
385
+ const match = this.searchResults?.matches[matchIndex];
386
+ const book = this.book;
387
+ const pageIndex = book.leafNumToIndex(match.par[0].page);
362
388
  const page = book.getPage(pageIndex);
389
+ const onNearbyPage = Math.abs(this.currentIndex() - pageIndex) < 3;
363
390
  let makeUnviewableAtEnd = false;
364
391
  if (!page.isViewable) {
365
392
  const resp = await fetch('/services/bookreader/request_page?' + new URLSearchParams({
@@ -378,15 +405,43 @@ BookReader.prototype._searchPluginGoToResult = async function (pageIndex) {
378
405
  book.getPage(pageIndex).makeViewable();
379
406
  makeUnviewableAtEnd = true;
380
407
  }
408
+
409
+ // Trigger an update of book
410
+ this._modes.mode1Up.mode1UpLit.updatePages();
411
+ if (this.activeMode == this._modes.mode1Up) {
412
+ await this._modes.mode1Up.mode1UpLit.updateComplete;
413
+ }
381
414
  }
382
415
  /* this updates the URL */
383
- this.suppressFragmentChange = false;
384
- this.jumpToIndex(pageIndex);
416
+ if (!this._isIndexDisplayed(pageIndex)) {
417
+ this.suppressFragmentChange = false;
418
+ this.jumpToIndex(pageIndex);
419
+ }
385
420
 
386
421
  // Reset it to unviewable if it wasn't resolved
387
422
  if (makeUnviewableAtEnd) {
388
423
  book.getPage(pageIndex).makeViewable(false);
389
424
  }
425
+
426
+ // Scroll/flash in the ui
427
+ const $boxes = await poll(() => $(`rect.match-index-${match.matchIndex}`), { until: result => result.length > 0 });
428
+ if ($boxes.length) {
429
+ $boxes.css('animation', 'none');
430
+ $boxes[0].scrollIntoView({
431
+ // Only vertically center the highlight if we're in 1up or in full screen. In
432
+ // 2up, if we're not fullscreen, the whole body gets scrolled around to try to
433
+ // center the highlight 🙄 See:
434
+ // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move/11041376
435
+ // Note: nearest doesn't quite work great, because the ReadAloud toolbar is now
436
+ // full-width, and covers up the last line of the highlight.
437
+ block: this.constMode1up == this.mode || this.isFullscreenActive ? 'center' : 'nearest',
438
+ inline: 'center',
439
+ behavior: onNearbyPage ? 'smooth' : 'auto',
440
+ });
441
+ // wait for animation to start
442
+ await new Promise(resolve => setTimeout(resolve, 100));
443
+ $boxes.removeAttr("style");
444
+ }
390
445
  };
391
446
 
392
447
  /**
@@ -420,7 +475,7 @@ BookReader.prototype.searchHighlightVisible = function() {
420
475
 
421
476
  results.matches.some(match => {
422
477
  return match.par[0].boxes.some(box => {
423
- const pageIndex = this.leafNumToIndex(box.page);
478
+ const pageIndex = this.book.leafNumToIndex(box.page);
424
479
  if (jQuery.inArray(pageIndex, visiblePages) >= 0) {
425
480
  return true;
426
481
  }
@@ -0,0 +1,43 @@
1
+ import { escapeHTML, escapeRegExp } from '../../BookReader/utils.js';
2
+
3
+ /**
4
+ * @param {string} match
5
+ * @param {string} preTag
6
+ * @param {string} postTag
7
+ * @returns {string}
8
+ */
9
+ export function renderMatch(match, preTag, postTag) {
10
+ // Search results are returned as a text blob with the hits wrapped in
11
+ // triple mustaches. Hits occasionally include text beyond the search
12
+ // term, so everything within the staches is captured and wrapped.
13
+ const preTagRe = escapeRegExp(escapeHTML(preTag));
14
+ const postTagRe = escapeRegExp(escapeHTML(postTag));
15
+ // [^] matches any character, including line breaks
16
+ const regex = new RegExp(`${preTagRe}([^]+?)${postTagRe}`, 'g');
17
+ return escapeHTML(match)
18
+ .replace(regex, '<mark>$1</mark>')
19
+ // Fix trailing hyphens. This over-corrects but is net useful.
20
+ .replace(/(\b)- /g, '$1');
21
+ }
22
+
23
+ /**
24
+ * Attach some fields to search inside results
25
+ * @param {SearchInsideResults} results
26
+ * @param {(pageNum: LeafNum) => PageNumString} displayPageNumberFn
27
+ * @param {string} preTag
28
+ * @param {string} postTag
29
+ */
30
+ export function marshallSearchResults(results, displayPageNumberFn, preTag, postTag) {
31
+ // Attach matchIndex to a few things to make it easier to identify
32
+ // an active/selected match
33
+ for (const [index, match] of results.matches.entries()) {
34
+ match.matchIndex = index;
35
+ match.displayPageNumber = displayPageNumberFn(match.par[0].page);
36
+ match.html = renderMatch(match.text, preTag, postTag);
37
+ for (const par of match.par) {
38
+ for (const box of par.boxes) {
39
+ box.matchIndex = index;
40
+ }
41
+ }
42
+ }
43
+ }