@internetarchive/bookreader 5.0.0-5 → 5.0.0-50-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 (260) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +77 -6
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +131 -339
  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 +1493 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +17 -0
  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/pause.svg +1 -1
  16. package/BookReader/icons/playback-speed.svg +1 -1
  17. package/BookReader/icons/read-aloud.svg +1 -1
  18. package/BookReader/icons/voice.svg +1 -0
  19. package/BookReader/images/BRicons.svg +2 -2
  20. package/BookReader/images/books_graphic.svg +1 -1
  21. package/BookReader/images/icon_book.svg +1 -1
  22. package/BookReader/images/icon_gear.svg +1 -1
  23. package/BookReader/images/icon_info.svg +1 -1
  24. package/BookReader/images/icon_playback-rate.svg +1 -1
  25. package/BookReader/images/icon_search_button.svg +1 -1
  26. package/BookReader/images/icon_share.svg +1 -1
  27. package/BookReader/images/icon_speaker.svg +1 -1
  28. package/BookReader/images/icon_speaker_open.svg +1 -1
  29. package/BookReader/images/marker_chap-off.svg +1 -1
  30. package/BookReader/images/marker_chap-on.svg +1 -1
  31. package/BookReader/images/marker_srch-on.svg +1 -1
  32. package/BookReader/jquery-3.js +2 -0
  33. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  34. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  35. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  36. package/BookReader/plugins/plugin.autoplay.js +1 -1
  37. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  38. package/BookReader/plugins/plugin.chapters.js +1 -1
  39. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  40. package/BookReader/plugins/plugin.iframe.js +1 -1
  41. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  42. package/BookReader/plugins/plugin.mobile_nav.js +1 -1
  43. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  44. package/BookReader/plugins/plugin.resume.js +1 -1
  45. package/BookReader/plugins/plugin.resume.js.map +1 -1
  46. package/BookReader/plugins/plugin.search.js +1 -1
  47. package/BookReader/plugins/plugin.search.js.map +1 -1
  48. package/BookReader/plugins/plugin.text_selection.js +1 -1
  49. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  50. package/BookReader/plugins/plugin.tts.js +1 -1
  51. package/BookReader/plugins/plugin.tts.js.map +1 -1
  52. package/BookReader/plugins/plugin.url.js +1 -1
  53. package/BookReader/plugins/plugin.url.js.map +1 -1
  54. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  55. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  56. package/BookReader/webcomponents-bundle.js +3 -0
  57. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  58. package/BookReader/webcomponents-bundle.js.map +1 -0
  59. package/BookReaderDemo/BookReaderDemo.css +14 -1
  60. package/BookReaderDemo/IADemoBr.js +148 -0
  61. package/BookReaderDemo/demo-advanced.html +2 -2
  62. package/BookReaderDemo/demo-autoplay.html +2 -1
  63. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  64. package/BookReaderDemo/demo-fullscreen-mobile.html +2 -1
  65. package/BookReaderDemo/demo-fullscreen.html +2 -1
  66. package/BookReaderDemo/demo-iiif.html +2 -1
  67. package/BookReaderDemo/demo-internetarchive.html +84 -17
  68. package/BookReaderDemo/demo-multiple.html +2 -1
  69. package/BookReaderDemo/demo-preview-pages.html +2 -1
  70. package/BookReaderDemo/demo-simple.html +2 -1
  71. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -1
  72. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  73. package/BookReaderDemo/immersion-1up.html +2 -1
  74. package/BookReaderDemo/immersion-mode.html +2 -1
  75. package/BookReaderDemo/toggle_controls.html +2 -1
  76. package/BookReaderDemo/view_mode.html +2 -1
  77. package/BookReaderDemo/viewmode-cycle.html +2 -3
  78. package/CHANGELOG.md +202 -0
  79. package/README.md +14 -1
  80. package/babel.config.js +18 -0
  81. package/codecov.yml +6 -0
  82. package/index.html +3 -0
  83. package/jsconfig.json +19 -0
  84. package/package.json +66 -56
  85. package/renovate.json +52 -0
  86. package/scripts/preversion.js +4 -1
  87. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  88. package/src/BookNavigator/assets/button-base.js +9 -2
  89. package/src/BookNavigator/assets/ia-logo.js +17 -0
  90. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  91. package/src/BookNavigator/assets/icon_close.js +1 -1
  92. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  93. package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
  94. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  95. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  96. package/src/BookNavigator/book-navigator.js +583 -0
  97. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  98. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  99. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  100. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  101. package/src/BookNavigator/bookmarks/bookmarks-provider.js +21 -8
  102. package/src/BookNavigator/bookmarks/ia-bookmarks.js +102 -66
  103. package/src/BookNavigator/delete-modal-actions.js +1 -1
  104. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  105. package/src/BookNavigator/downloads/downloads.js +41 -25
  106. package/src/BookNavigator/search/a-search-result.js +18 -13
  107. package/src/BookNavigator/search/search-provider.js +80 -28
  108. package/src/BookNavigator/search/search-results.js +10 -18
  109. package/src/BookNavigator/sharing.js +27 -0
  110. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  111. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  112. package/src/BookNavigator/volumes/volumes-provider.js +114 -0
  113. package/src/BookNavigator/volumes/volumes.js +188 -0
  114. package/src/BookReader/BookModel.js +0 -29
  115. package/src/BookReader/DebugConsole.js +3 -3
  116. package/src/BookReader/DragScrollable.js +233 -0
  117. package/src/BookReader/Mode1Up.js +51 -351
  118. package/src/BookReader/Mode1UpLit.js +441 -0
  119. package/src/BookReader/Mode2Up.js +120 -105
  120. package/src/BookReader/ModeSmoothZoom.js +179 -0
  121. package/src/BookReader/ModeThumb.js +17 -11
  122. package/src/BookReader/Navbar/Navbar.js +10 -36
  123. package/src/BookReader/PageContainer.js +69 -6
  124. package/src/BookReader/ReduceSet.js +1 -1
  125. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  126. package/src/BookReader/options.js +10 -0
  127. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  128. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  129. package/src/BookReader/utils.js +92 -13
  130. package/src/BookReader.js +431 -620
  131. package/src/assets/icons/close-circle-dark.svg +1 -0
  132. package/src/assets/icons/magnify-minus.svg +3 -7
  133. package/src/assets/icons/magnify-plus.svg +3 -7
  134. package/src/assets/icons/voice.svg +1 -0
  135. package/src/css/BookReader.scss +0 -12
  136. package/src/css/_BRComponent.scss +1 -1
  137. package/src/css/_BRmain.scss +19 -24
  138. package/src/css/_BRnav.scss +4 -26
  139. package/src/css/_BRpages.scss +35 -0
  140. package/src/css/_BRsearch.scss +25 -216
  141. package/src/css/_TextSelection.scss +14 -17
  142. package/src/css/_colorbox.scss +2 -2
  143. package/src/css/_controls.scss +17 -5
  144. package/src/css/_icons.scss +6 -0
  145. package/src/ia-bookreader/ia-bookreader.js +224 -0
  146. package/src/plugins/plugin.autoplay.js +4 -4
  147. package/src/plugins/plugin.chapters.js +28 -35
  148. package/src/plugins/plugin.mobile_nav.js +11 -10
  149. package/src/plugins/plugin.resume.js +3 -3
  150. package/src/plugins/plugin.text_selection.js +26 -39
  151. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  152. package/src/plugins/search/plugin.search.js +174 -116
  153. package/src/plugins/search/view.js +63 -179
  154. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  155. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  156. package/src/plugins/tts/PageChunk.js +15 -21
  157. package/src/plugins/tts/PageChunkIterator.js +8 -12
  158. package/src/plugins/tts/WebTTSEngine.js +66 -69
  159. package/src/plugins/tts/plugin.tts.js +92 -109
  160. package/src/plugins/tts/utils.js +0 -9
  161. package/src/plugins/url/UrlPlugin.js +184 -0
  162. package/src/plugins/{plugin.url.js → url/plugin.url.js} +28 -6
  163. package/src/util/manifestGenerator.js +0 -0
  164. package/tests/e2e/README.md +37 -0
  165. package/tests/e2e/autoplay.test.js +2 -2
  166. package/tests/e2e/base.test.js +7 -7
  167. package/tests/e2e/helpers/base.js +9 -3
  168. package/tests/e2e/helpers/debug.js +1 -1
  169. package/tests/e2e/helpers/desktopSearch.js +14 -13
  170. package/tests/e2e/helpers/mobileSearch.js +3 -3
  171. package/tests/e2e/helpers/params.js +17 -0
  172. package/tests/e2e/models/Navigation.js +13 -4
  173. package/tests/e2e/rightToLeft.test.js +4 -5
  174. package/tests/e2e/viewmode.test.js +38 -33
  175. package/tests/jest/BookNavigator/book-navigator.test.js +634 -0
  176. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  177. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  178. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  179. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  180. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  181. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  182. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  183. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +102 -58
  184. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  185. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  186. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +184 -0
  187. package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
  188. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +34 -14
  189. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +176 -0
  190. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +1 -1
  191. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  192. package/tests/jest/BookReader/Mode1UpLit.test.js +92 -0
  193. package/tests/{BookReader → jest/BookReader}/Mode2Up.test.js +36 -15
  194. package/tests/jest/BookReader/ModeSmoothZoom.test.js +149 -0
  195. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  196. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +7 -7
  197. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  198. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  199. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  200. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  201. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  202. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  203. package/tests/jest/BookReader/utils.test.js +186 -0
  204. package/tests/jest/BookReader.keyboard.test.js +190 -0
  205. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  206. package/tests/{BookReader.test.js → jest/BookReader.test.js} +18 -37
  207. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  208. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  209. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +10 -11
  210. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  211. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +5 -5
  212. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  213. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +39 -47
  214. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  215. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +63 -47
  216. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +35 -6
  217. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +9 -9
  218. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  219. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  220. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  221. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +1 -1
  222. package/tests/{plugins → jest/plugins}/tts/utils.test.js +3 -28
  223. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  224. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +33 -14
  225. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  226. package/tests/{util → jest/util}/docCookies.test.js +1 -1
  227. package/tests/{util → jest/util}/strings.test.js +1 -1
  228. package/tests/{utils.js → jest/utils.js} +38 -0
  229. package/webpack.config.js +11 -5
  230. package/.babelrc +0 -12
  231. package/.dependabot/config.yml +0 -6
  232. package/.testcaferc.json +0 -5
  233. package/BookReader/bookreader-component-bundle.js +0 -1450
  234. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  235. package/BookReader/bookreader-component-bundle.js.map +0 -1
  236. package/BookReader/jquery-1.10.1.js +0 -2
  237. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  238. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  239. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  240. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  241. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  242. package/karma.conf.js +0 -23
  243. package/src/BookNavigator/BookModel.js +0 -14
  244. package/src/BookNavigator/BookNavigator.js +0 -438
  245. package/src/BookNavigator/assets/book-loader.js +0 -27
  246. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  247. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  248. package/src/ItemNavigator/ItemNavigator.js +0 -372
  249. package/src/ItemNavigator/providers/sharing.js +0 -29
  250. package/src/Layers/sharing/sharing-provider.js +0 -22
  251. package/src/dragscrollable-br.js +0 -261
  252. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  253. package/src/plugins/plugin.bookmarks.js +0 -50
  254. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  255. package/tests/BookReader/Mode1Up.test.js +0 -164
  256. package/tests/BookReader/utils.test.js +0 -109
  257. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  258. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  259. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  260. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /* global BookReader */
2
3
  /**
3
4
  * Plugin for Archive.org book search
@@ -20,8 +21,16 @@
20
21
  * the book has not had OCR text indexed yet. Receives `instance`
21
22
  * @event BookReader:SearchCallbackEmpty - When no results found. Receives
22
23
  * `instance`
24
+ * @event BookReader:SearchCanceled - When no results found. Receives
25
+ * `instance`
23
26
  */
27
+ import { poll } from '../../BookReader/utils.js';
28
+ import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
24
29
  import SearchView from './view.js';
30
+ /** @typedef {import('../../BookReader/PageContainer').PageContainer} PageContainer */
31
+ /** @typedef {import('../../BookReader/BookModel').PageIndex} PageIndex */
32
+ /** @typedef {import('../../BookReader/BookModel').LeafNum} LeafNum */
33
+ /** @typedef {import('../../BookReader/BookModel').PageNumString} PageNumString */
25
34
 
26
35
  jQuery.extend(BookReader.defaultOptions, {
27
36
  server: 'ia600609.us.archive.org',
@@ -42,7 +51,6 @@ BookReader.prototype.setup = (function (super_) {
42
51
  this.searchResults = null;
43
52
  this.searchInsideUrl = options.searchInsideUrl;
44
53
  this.enableSearch = options.enableSearch;
45
- this.goToFirstResult = false;
46
54
 
47
55
  // Base server used by some api calls
48
56
  this.bookId = options.bookId;
@@ -50,11 +58,14 @@ BookReader.prototype.setup = (function (super_) {
50
58
  this.subPrefix = options.subPrefix;
51
59
  this.bookPath = options.bookPath;
52
60
 
53
- if (this.searchView) { return; }
54
- this.searchView = new SearchView({
55
- br: this,
56
- selector: '#BRsearch_tray',
57
- });
61
+ this.searchXHR = null;
62
+ this._cancelSearch.bind(this);
63
+ this.cancelSearchRequest.bind(this);
64
+
65
+ /** @type { {[pageIndex: number]: SearchInsideMatchBox[]} } */
66
+ this._searchBoxesByIndex = {};
67
+
68
+ this.searchView = undefined;
58
69
  };
59
70
  })(BookReader.prototype.setup);
60
71
 
@@ -62,28 +73,31 @@ BookReader.prototype.setup = (function (super_) {
62
73
  BookReader.prototype.init = (function (super_) {
63
74
  return function () {
64
75
  super_.call(this);
65
-
76
+ // give SearchView the most complete bookreader state
77
+ this.searchView = new SearchView({
78
+ br: this,
79
+ searchCancelledCallback: () => {
80
+ this._cancelSearch();
81
+ this.trigger('SearchCanceled', { term: this.searchTerm, instance: this });
82
+ }
83
+ });
66
84
  if (this.options.enableSearch && this.options.initialSearchTerm) {
85
+ /**
86
+ * this.search() take two parameter
87
+ * 1. this.options.initialSearchTerm - search term
88
+ * 2. {
89
+ * goToFirstResult: this.options.goToFirstResult,
90
+ * suppressFragmentChange: false // always want to change fragment in URL
91
+ * }
92
+ */
67
93
  this.search(
68
94
  this.options.initialSearchTerm,
69
- { goToFirstResult: this.goToFirstResult, suppressFragmentChange: true }
95
+ { goToFirstResult: this.options.goToFirstResult, suppressFragmentChange: false }
70
96
  );
71
97
  }
72
98
  };
73
99
  })(BookReader.prototype.init);
74
100
 
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
101
  /** @override */
88
102
  BookReader.prototype.buildToolbarElement = (function (super_) {
89
103
  return function () {
@@ -96,6 +110,25 @@ BookReader.prototype.buildToolbarElement = (function (super_) {
96
110
  };
97
111
  })(BookReader.prototype.buildToolbarElement);
98
112
 
113
+ /** @override */
114
+ BookReader.prototype._createPageContainer = (function (super_) {
115
+ return function (index) {
116
+ const pageContainer = super_.call(this, index);
117
+ if (this.enableSearch && pageContainer.page && index in this._searchBoxesByIndex) {
118
+ const pageIndex = pageContainer.page.index;
119
+ const boxes = this._searchBoxesByIndex[pageIndex];
120
+ renderBoxesInPageContainerLayer(
121
+ 'searchHiliteLayer',
122
+ boxes,
123
+ pageContainer.page,
124
+ pageContainer.$container[0],
125
+ boxes.map(b => `match-index-${b.matchIndex}`),
126
+ );
127
+ }
128
+ return pageContainer;
129
+ };
130
+ })(BookReader.prototype._createPageContainer);
131
+
99
132
  /**
100
133
  * @typedef {object} SearchOptions
101
134
  * @property {boolean} goToFirstResult
@@ -110,7 +143,7 @@ BookReader.prototype.buildToolbarElement = (function (super_) {
110
143
  * @param {string} term
111
144
  * @param {SearchOptions} overrides
112
145
  */
113
- BookReader.prototype.search = function(term = '', overrides = {}) {
146
+ BookReader.prototype.search = async function(term = '', overrides = {}) {
114
147
  /** @type {SearchOptions} */
115
148
  const defaultOptions = {
116
149
  goToFirstResult: false, /* jump to the first result (default=false) */
@@ -122,6 +155,7 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
122
155
  };
123
156
  const options = jQuery.extend({}, defaultOptions, overrides);
124
157
  this.suppressFragmentChange = options.suppressFragmentChange;
158
+ this.searchCancelled = false;
125
159
 
126
160
  // strip slashes, since this goes in the url
127
161
  this.searchTerm = term.replace(/\//g, ' ');
@@ -157,7 +191,10 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
157
191
 
158
192
  const url = `${baseUrl}${paramStr}`;
159
193
 
160
- const processSearchResults = (searchInsideResults) => {
194
+ const callSearchResultsCallback = (searchInsideResults) => {
195
+ if (this.searchCancelled) {
196
+ return;
197
+ }
161
198
  const responseHasError = searchInsideResults.error || !searchInsideResults.matches.length;
162
199
  const hasCustomError = typeof options.error === 'function';
163
200
  const hasCustomSuccess = typeof options.success === 'function';
@@ -173,11 +210,39 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
173
210
  }
174
211
  };
175
212
 
176
- this.trigger('SearchStarted', { term: this.searchTerm });
177
- return $.ajax({
213
+ this.trigger('SearchStarted', { term: this.searchTerm, instance: this });
214
+ callSearchResultsCallback(await $.ajax({
178
215
  url: url,
179
- dataType: 'jsonp'
180
- }).then(processSearchResults);
216
+ dataType: 'jsonp',
217
+ cache: true,
218
+ beforeSend: xhr => { this.searchXHR = xhr; },
219
+ }));
220
+ };
221
+
222
+ /**
223
+ * cancels AJAX Call
224
+ * emits custom event
225
+ */
226
+ BookReader.prototype._cancelSearch = function () {
227
+ this.searchXHR?.abort();
228
+ this.searchView.clearSearchFieldAndResults(false);
229
+ this.searchTerm = '';
230
+ this.searchXHR = null;
231
+ this.searchCancelled = true;
232
+ this.searchResults = [];
233
+ };
234
+
235
+ /**
236
+ * External function to cancel search
237
+ * checks for term & xhr in flight before running
238
+ */
239
+ BookReader.prototype.cancelSearchRequest = function () {
240
+ this.searchCancelled = true;
241
+ if (this.searchXHR !== null) {
242
+ this._cancelSearch();
243
+ this.searchView.toggleSearchPending();
244
+ this.trigger('SearchCanceled', { term: this.searchTerm, instance: this });
245
+ }
181
246
  };
182
247
 
183
248
  /**
@@ -188,10 +253,13 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
188
253
  * @property {number} b
189
254
  * @property {number} t
190
255
  * @property {HTMLDivElement} [div]
256
+ * @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
257
  */
192
258
 
193
259
  /**
194
260
  * @typedef {object} SearchInsideMatch
261
+ * @property {number} matchIndex This is a fake field! Not part of the API response. It is added by the JS.
262
+ * @property {string} displayPageNumber (fake field) The page number as it should be displayed in the UI.
195
263
  * @property {string} text
196
264
  * @property {Array<{ page: number, boxes: SearchInsideMatchBox[] }>} par
197
265
  */
@@ -203,23 +271,42 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
203
271
  * @property {boolean} indexed
204
272
  */
205
273
 
274
+ /**
275
+ * Attach some fields to search inside results
276
+ * @param {SearchInsideResults} results
277
+ * @param {(pageNum: LeafNum) => PageNumString} displayPageNumberFn
278
+ */
279
+ export function marshallSearchResults(results, displayPageNumberFn) {
280
+ // Attach matchIndex to a few things to make it easier to identify
281
+ // an active/selected match
282
+ for (const [index, match] of results.matches.entries()) {
283
+ match.matchIndex = index;
284
+ match.displayPageNumber = displayPageNumberFn(match.par[0].page);
285
+ for (const par of match.par) {
286
+ for (const box of par.boxes) {
287
+ box.matchIndex = index;
288
+ }
289
+ }
290
+ }
291
+ }
292
+
206
293
  /**
207
294
  * Search Results return handler
208
- * @callback
209
295
  * @param {SearchInsideResults} results
210
296
  * @param {object} options
211
297
  * @param {boolean} options.goToFirstResult
212
298
  */
213
299
  BookReader.prototype.BRSearchCallback = function(results, options) {
214
- this.searchResults = results;
300
+ marshallSearchResults(results, pageNum => this.book.getPageNum(this.book.leafNumToIndex(pageNum)));
301
+ this.searchResults = results || [];
215
302
 
216
303
  this.updateSearchHilites();
217
304
  this.removeProgressPopup();
218
305
  if (options.goToFirstResult) {
219
- this._searchPluginGoToResult(results.matches[0].par[0].page);
306
+ this._searchPluginGoToResult(0);
220
307
  }
221
308
  this.trigger('SearchCallback', { results, options, instance: this });
222
- }
309
+ };
223
310
 
224
311
  /**
225
312
  * Main search results error handler
@@ -259,95 +346,41 @@ BookReader.prototype._BRSearchCallbackError = function(results) {
259
346
  * updates search on-page highlights controller
260
347
  */
261
348
  BookReader.prototype.updateSearchHilites = function() {
262
- if (this.constMode2up == this.mode) {
263
- this.updateSearchHilites2UP();
264
- return;
349
+ /** @type {SearchInsideMatch[]} */
350
+ const matches = this.searchResults?.matches || [];
351
+ /** @type { {[pageIndex: number]: SearchInsideMatchBox[]} } */
352
+ const boxesByIndex = {};
353
+
354
+ // Clear any existing svg layers
355
+ this.removeSearchHilites();
356
+
357
+ // Group by pageIndex
358
+ for (const match of matches) {
359
+ for (const box of match.par[0].boxes) {
360
+ const pageIndex = this.book.leafNumToIndex(box.page);
361
+ const pageBoxes = boxesByIndex[pageIndex] || (boxesByIndex[pageIndex] = []);
362
+ pageBoxes.push(box);
363
+ }
265
364
  }
266
- this.updateSearchHilites1UP();
267
- };
268
365
 
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;
366
+ // update any already created pages
367
+ for (const [pageIndexString, boxes] of Object.entries(boxesByIndex)) {
368
+ const pageIndex = parseFloat(pageIndexString);
369
+ const page = this.book.getPage(pageIndex);
370
+ const pageContainers = this.getActivePageContainerElementsForIndex(pageIndex);
371
+ for (const container of pageContainers) {
372
+ renderBoxesInPageContainerLayer('searchHiliteLayer', boxes, page, container, boxes.map(b => `match-index-${b.matchIndex}`));
373
+ }
374
+ }
308
375
 
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
- });
376
+ this._searchBoxesByIndex = boxesByIndex;
335
377
  };
336
378
 
337
379
  /**
338
380
  * remove search highlights
339
381
  */
340
382
  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
- });
383
+ $(this.getActivePageContainerElements()).find('.searchHiliteLayer').remove();
351
384
  };
352
385
 
353
386
  /**
@@ -355,11 +388,14 @@ BookReader.prototype.removeSearchHilites = function() {
355
388
  * Goes to the page specified. If the page is not viewable, tries to load the page
356
389
  * FIXME Most of this logic is IA specific, and should be less integrated into here
357
390
  * or at least more configurable.
358
- * @param {PageIndex} pageIndex
391
+ * @param {number} matchIndex
359
392
  */
360
- BookReader.prototype._searchPluginGoToResult = async function (pageIndex) {
361
- const { book } = this._models;
393
+ BookReader.prototype._searchPluginGoToResult = async function (matchIndex) {
394
+ const match = this.searchResults?.matches[matchIndex];
395
+ const book = this.book;
396
+ const pageIndex = book.leafNumToIndex(match.par[0].page);
362
397
  const page = book.getPage(pageIndex);
398
+ const onNearbyPage = Math.abs(this.currentIndex() - pageIndex) < 3;
363
399
  let makeUnviewableAtEnd = false;
364
400
  if (!page.isViewable) {
365
401
  const resp = await fetch('/services/bookreader/request_page?' + new URLSearchParams({
@@ -380,13 +416,35 @@ BookReader.prototype._searchPluginGoToResult = async function (pageIndex) {
380
416
  }
381
417
  }
382
418
  /* this updates the URL */
383
- this.suppressFragmentChange = false;
384
- this.jumpToIndex(pageIndex);
419
+ if (!this._isIndexDisplayed(pageIndex)) {
420
+ this.suppressFragmentChange = false;
421
+ this.jumpToIndex(pageIndex);
422
+ }
385
423
 
386
424
  // Reset it to unviewable if it wasn't resolved
387
425
  if (makeUnviewableAtEnd) {
388
426
  book.getPage(pageIndex).makeViewable(false);
389
427
  }
428
+
429
+ // Scroll/flash in the ui
430
+ const $boxes = await poll(() => $(`rect.match-index-${match.matchIndex}`), { until: result => result.length > 0 });
431
+ if ($boxes.length) {
432
+ $boxes.css('animation', 'none');
433
+ $boxes[0].scrollIntoView({
434
+ // Only vertically center the highlight if we're in 1up or in full screen. In
435
+ // 2up, if we're not fullscreen, the whole body gets scrolled around to try to
436
+ // center the highlight 🙄 See:
437
+ // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move/11041376
438
+ // Note: nearest doesn't quite work great, because the ReadAloud toolbar is now
439
+ // full-width, and covers up the last line of the highlight.
440
+ block: this.constMode1up == this.mode || this.isFullscreenActive ? 'center' : 'nearest',
441
+ inline: 'center',
442
+ behavior: onNearbyPage ? 'smooth' : 'auto',
443
+ });
444
+ // wait for animation to start
445
+ await new Promise(resolve => setTimeout(resolve, 100));
446
+ $boxes.removeAttr("style");
447
+ }
390
448
  };
391
449
 
392
450
  /**
@@ -420,7 +478,7 @@ BookReader.prototype.searchHighlightVisible = function() {
420
478
 
421
479
  results.matches.some(match => {
422
480
  return match.par[0].boxes.some(box => {
423
- const pageIndex = this.leafNumToIndex(box.page);
481
+ const pageIndex = this.book.leafNumToIndex(box.page);
424
482
  if (jQuery.inArray(pageIndex, visiblePages) >= 0) {
425
483
  return true;
426
484
  }