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

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 (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
@@ -1,7 +1,7 @@
1
- import { html } from 'lit-element';
2
- import { nothing } from 'lit-html';
3
-
1
+ import { html, nothing } from 'lit';
2
+ import '@internetarchive/icon-search/icon-search';
4
3
  import './search-results';
4
+ /** @typedef {import('@/src/plugins/search/plugin.search.js').SearchInsideMatch} SearchInsideMatch */
5
5
 
6
6
  let searchState = {
7
7
  query: '',
@@ -10,8 +10,11 @@ let searchState = {
10
10
  queryInProgress: false,
11
11
  errorMessage: '',
12
12
  };
13
- export default class {
14
- constructor(onSearchChange = () => {}, brInstance) {
13
+ export default class SearchProvider {
14
+ constructor({
15
+ onProviderChange,
16
+ bookreader
17
+ }) {
15
18
  /* search menu events */
16
19
  this.onBookSearchInitiated = this.onBookSearchInitiated.bind(this);
17
20
  /* bookreader search events */
@@ -20,16 +23,18 @@ export default class {
20
23
  this.onSearchResultsClicked = this.onSearchResultsClicked.bind(this);
21
24
  this.onSearchResultsChange = this.onSearchResultsChange.bind(this);
22
25
  this.onSearchResultsCleared = this.onSearchResultsCleared.bind(this);
26
+ this.searchCanceledInMenu = this.searchCanceledInMenu.bind(this);
27
+
23
28
  /* class methods */
24
29
  this.bindEventListeners = this.bindEventListeners.bind(this);
25
30
  this.getMenuDetails = this.getMenuDetails.bind(this);
26
31
  this.getComponent = this.getComponent.bind(this);
27
- this.advanceToPage = this.advanceToPage.bind(this);
28
32
  this.updateMenu = this.updateMenu.bind(this);
29
33
 
30
- this.onSearchChange = onSearchChange;
31
- this.bookreader = brInstance;
32
- this.icon = html`<ia-icon icon="search" style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon>`;
34
+ this.onProviderChange = onProviderChange;
35
+ /** @type {import('@/src/BookReader.js').default} */
36
+ this.bookreader = bookreader;
37
+ this.icon = html`<ia-icon-search style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon-search>`;
33
38
  this.label = 'Search inside';
34
39
  this.menuDetails = this.getMenuDetails();
35
40
  this.id = 'search';
@@ -39,7 +44,7 @@ export default class {
39
44
 
40
45
  getMenuDetails() {
41
46
  const { resultsCount, query, queryInProgress } = searchState;
42
- if (queryInProgress || !query) { return nothing }
47
+ if (queryInProgress || !query) { return nothing; }
43
48
  const unit = resultsCount === 1 ? 'result' : 'results';
44
49
  return html`(${resultsCount} ${unit})`;
45
50
  }
@@ -47,14 +52,40 @@ export default class {
47
52
  bindEventListeners() {
48
53
  window.addEventListener('BookReader:SearchStarted', this.onSearchStarted);
49
54
  window.addEventListener('BookReader:SearchCallback', this.onSearchResultsChange);
50
- window.addEventListener('BookReader:SearchCallbackEmpty', (event) => { this.onSearchRequestError(event, 'noResults') });
51
- window.addEventListener('BookReader:SearchCallbackNotIndexed', (event) => { this.onSearchRequestError(event, 'notIndexed') });
52
- window.addEventListener('BookReader:SearchCallbackError', (event) => { this.onSearchRequestError(event) });
53
- window.addEventListener('BookReader:SearchResultsCleared', () => { this.onSearchResultsCleared() });
55
+ window.addEventListener('BookReader:SearchCallbackEmpty', (event) => { this.onSearchRequestError(event, 'noResults'); });
56
+ window.addEventListener('BookReader:SearchCallbackNotIndexed', (event) => { this.onSearchRequestError(event, 'notIndexed'); });
57
+ window.addEventListener('BookReader:SearchCallbackError', (event) => { this.onSearchRequestError(event); });
58
+ window.addEventListener('BookReader:SearchResultsCleared', () => { this.onSearchResultsCleared(); });
59
+ window.addEventListener('BookReader:SearchCanceled', (e) => { this.onSearchCanceled(e); });
60
+ }
61
+
62
+ /**
63
+ * Cancel search handler
64
+ * resets `searchState`
65
+ */
66
+ onSearchCanceled() {
67
+ searchState = {
68
+ query: '',
69
+ results: [],
70
+ resultsCount: 0,
71
+ queryInProgress: false,
72
+ errorMessage: '',
73
+ };
74
+ const updateMenuFor = {
75
+ searchCanceled: true
76
+ };
77
+ this.updateMenu(updateMenuFor);
78
+
79
+ if (this.bookreader.urlPlugin) {
80
+ this.updateSearchInUrl();
81
+ }
54
82
  }
55
83
 
56
84
  onSearchStarted(e) {
57
- const { term = '' } = e.detail.props;
85
+ const { term = '', instance } = e.detail.props;
86
+ if (instance) {
87
+ this.bookreader = instance;
88
+ }
58
89
  searchState.query = term;
59
90
  searchState.results = [];
60
91
  searchState.resultsCount = 0;
@@ -79,10 +110,11 @@ export default class {
79
110
  noResults: '0 results',
80
111
  notIndexed: `This book hasn't been indexed for searching yet. We've just started indexing it,
81
112
  so search should be available soon. Please try again later. Thanks!`,
82
- default: 'Sorry, there was an error with your search. The text may still be processing.',
113
+ default: 'Sorry, there was an error with your search. Please try again.',
83
114
  };
84
115
 
85
116
  const messageToShow = errorMessages[errorType] ?? errorMessages.default;
117
+ searchState.query = instance?.searchResults?.q || '';
86
118
  searchState.results = [];
87
119
  searchState.resultsCount = 0;
88
120
  searchState.queryInProgress = false;
@@ -104,6 +136,10 @@ export default class {
104
136
  this.updateMenu();
105
137
  }
106
138
 
139
+ searchCanceledInMenu() {
140
+ this.bookreader?.cancelSearchRequest();
141
+ }
142
+
107
143
  onSearchResultsCleared() {
108
144
  searchState = {
109
145
  query: '',
@@ -111,15 +147,34 @@ export default class {
111
147
  resultsCount: 0,
112
148
  queryInProgress: false,
113
149
  errorMessage: '',
150
+ };
151
+ this.updateMenu({ openMenu: false });
152
+ this.bookreader?.searchView?.clearSearchFieldAndResults(false);
153
+ if (this.bookreader.urlPlugin) {
154
+ this.updateSearchInUrl();
155
+ }
156
+ }
157
+
158
+ /** update URL `q=<term>` query param in URL */
159
+ updateSearchInUrl() {
160
+ if (this.bookreader.urlPlugin) {
161
+ this.bookreader.urlPlugin.pullFromAddressBar();
162
+ if (searchState.query) {
163
+ this.bookreader.urlPlugin.setUrlParam('q', searchState.query);
164
+ } else {
165
+ this.bookreader.urlPlugin.removeUrlParam('q');
166
+ }
114
167
  }
115
- this.updateMenu();
116
- this.bookreader?.searchView?.clearSearchFieldAndResults();
117
168
  }
118
169
 
119
- updateMenu() {
170
+ /**
171
+ * Relays how to update side menu given the context of a search update
172
+ @param {{searchCanceled: boolean}} searchUpdates
173
+ */
174
+ updateMenu(searchUpdates = {}) {
120
175
  this.menuDetails = this.getMenuDetails();
121
176
  this.component = this.getComponent();
122
- this.onSearchChange(this.bookreader);
177
+ this.onProviderChange(this.bookreader, searchUpdates);
123
178
  }
124
179
 
125
180
  getComponent() {
@@ -134,18 +189,15 @@ export default class {
134
189
  @resultSelected=${this.onSearchResultsClicked}
135
190
  @bookSearchInitiated=${this.onBookSearchInitiated}
136
191
  @bookSearchResultsCleared=${this.onSearchResultsCleared}
192
+ @bookSearchCanceled=${this.searchCanceledInMenu}
137
193
  ></ia-book-search-results>
138
194
  `;
139
195
  }
140
196
 
197
+ /**
198
+ * @param {{ detail: {match: SearchInsideMatch} }} param0
199
+ */
141
200
  onSearchResultsClicked({ detail }) {
142
- const page = detail.match.par[0].page;
143
- this.advanceToPage(page);
144
- }
145
-
146
- advanceToPage(leaf) {
147
- const page = this.bookreader.leafNumToIndex(leaf);
148
- this.bookreader._searchPluginGoToResult(page);
149
- this.bookreader.updateSearchHilites();
201
+ this.bookreader._searchPluginGoToResult(detail.match.matchIndex);
150
202
  }
151
203
  }
@@ -1,11 +1,11 @@
1
1
  /* eslint-disable class-methods-use-this */
2
- import { nothing } from 'lit-html';
3
- import { css, html, LitElement } from 'lit-element';
2
+ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
3
+ import { css, html, LitElement, nothing } from 'lit';
4
4
  import '@internetarchive/ia-activity-indicator/ia-activity-indicator';
5
- import './a-search-result.js';
6
5
  import checkmarkIcon from '../assets/icon_checkmark.js';
7
6
  import closeIcon from '../assets/icon_close.js';
8
-
7
+ import buttonCSS from '../assets/button-base.js';
8
+ /** @typedef {import('@/src/plugins/search/plugin.search.js').SearchInsideMatch} SearchInsideMatch */
9
9
 
10
10
  export class IABookSearchResults extends LitElement {
11
11
  static get properties() {
@@ -23,6 +23,7 @@ export class IABookSearchResults extends LitElement {
23
23
  constructor() {
24
24
  super();
25
25
 
26
+ /** @type {SearchInsideMatch[]} */
26
27
  this.results = [];
27
28
  this.query = '';
28
29
  this.queryInProgress = false;
@@ -60,6 +61,9 @@ export class IABookSearchResults extends LitElement {
60
61
 
61
62
  setQuery(e) {
62
63
  this.query = e.currentTarget.value;
64
+ if (!this.query) {
65
+ this.cancelSearch();
66
+ }
63
67
  }
64
68
 
65
69
  performSearch(e) {
@@ -77,7 +81,15 @@ export class IABookSearchResults extends LitElement {
77
81
  }));
78
82
  }
79
83
 
80
- selectResult() {
84
+ /**
85
+ * @param {SearchInsideMatch} match
86
+ */
87
+ selectResult(match) {
88
+ this.dispatchEvent(new CustomEvent('resultSelected', {
89
+ bubbles: true,
90
+ composed: true,
91
+ detail: { match },
92
+ }));
81
93
  this.dispatchEvent(new CustomEvent('closeMenu', {
82
94
  bubbles: true,
83
95
  composed: true,
@@ -90,10 +102,7 @@ export class IABookSearchResults extends LitElement {
90
102
  }
91
103
 
92
104
  dispatchSearchCanceled() {
93
- this.dispatchEvent(new CustomEvent('bookSearchCanceled', {
94
- bubbles: true,
95
- composed: true,
96
- }));
105
+ this.dispatchEvent(new Event('bookSearchCanceled'));
97
106
  }
98
107
 
99
108
  get resultsCount() {
@@ -122,6 +131,7 @@ export class IABookSearchResults extends LitElement {
122
131
  <div class="loading">
123
132
  <ia-activity-indicator mode="processing"></ia-activity-indicator>
124
133
  <p>Searching</p>
134
+ <button class="ia-button external cancel-search" @click=${this.cancelSearch}>Cancel</button>
125
135
  </div>
126
136
  `;
127
137
  }
@@ -131,10 +141,12 @@ export class IABookSearchResults extends LitElement {
131
141
  return html`
132
142
  <ul class="results ${resultsClass}">
133
143
  ${this.results.map(match => html`
134
- <book-search-result
135
- .match=${match}
136
- @resultSelected=${this.selectResult}
137
- ></book-search-result>
144
+ <li @click=${this.selectResult.bind(this, match)}>
145
+ ${match.cover ? html`<img src="${match.cover}" />` : nothing}
146
+ <h4>${match.title || nothing}</h4>
147
+ <p class="page-num">Page ${match.displayPageNumber}</p>
148
+ <p>${unsafeHTML(match.html)}</p>
149
+ </li>
138
150
  `)}
139
151
  </ul>
140
152
  `;
@@ -150,6 +162,7 @@ export class IABookSearchResults extends LitElement {
150
162
  name="query"
151
163
  alt="Search inside this book."
152
164
  @keyup=${this.setQuery}
165
+ @search=${this.setQuery}
153
166
  .value=${this.query}
154
167
  />
155
168
  </fieldset>
@@ -188,7 +201,7 @@ export class IABookSearchResults extends LitElement {
188
201
  const searchResultBorder = css`var(--searchResultBorder, #adaedc)`;
189
202
  const activeButtonBg = css`(--tertiaryBGColor, #333)`;
190
203
 
191
- return css`
204
+ const mainCSS = css`
192
205
  :host {
193
206
  display: block;
194
207
  height: 100%;
@@ -348,17 +361,6 @@ export class IABookSearchResults extends LitElement {
348
361
  font-size: 1.2rem;
349
362
  }
350
363
 
351
- .loading button {
352
- -webkit-appearance: none;
353
- appearance: none;
354
- padding: .5rem .7rem;
355
- font: normal 1.4rem "Helvetica Neue", Helvetica, Arial, sans-serif;
356
- border: 1px solid #656565;
357
- border-radius: 3px;
358
- cursor: pointer;
359
- background: transparent;
360
- }
361
-
362
364
  ia-activity-indicator {
363
365
  display: block;
364
366
  width: 40px;
@@ -366,6 +368,7 @@ export class IABookSearchResults extends LitElement {
366
368
  margin: 0 auto;
367
369
  }
368
370
  `;
371
+ return [buttonCSS, mainCSS];
369
372
  }
370
373
  }
371
374
  customElements.define('ia-book-search-results', IABookSearchResults);
@@ -0,0 +1,27 @@
1
+ import { html } from 'lit';
2
+ import { iauxShareIcon } from '@internetarchive/ia-item-navigator/dist/src/menus/share-panel';
3
+ import '@internetarchive/ia-item-navigator/dist/src/menus/share-panel';
4
+
5
+ export default class SharingProvider {
6
+ constructor({
7
+ item,
8
+ baseHost,
9
+ bookreader
10
+ }) {
11
+ const { identifier, creator, title } = item?.metadata;
12
+ const creatorToUse = Array.isArray(creator) ? creator[0] : creator;
13
+ const subPrefix = bookreader.options.subPrefix || '';
14
+ const label = `Share this book`;
15
+ this.icon = html`${iauxShareIcon}`;
16
+ this.label = label;
17
+ this.id = 'share';
18
+ this.component = html`<iaux-in-share-panel
19
+ .identifier=${identifier}
20
+ .type=${`book`}
21
+ .creator=${creatorToUse}
22
+ .description=${title}
23
+ .baseHost=${baseHost}
24
+ .fileSubPrefix=${subPrefix}
25
+ ></iaux-in-share-panel>`;
26
+ }
27
+ }
@@ -0,0 +1,95 @@
1
+ import { html } from 'lit';
2
+
3
+ import { viewableFilesIcon } from '@internetarchive/ia-item-navigator/dist/src/menus/viewable-files';
4
+ import '@internetarchive/ia-item-navigator/dist/src/menus/viewable-files';
5
+
6
+ /**
7
+ * * @typedef { 'title_asc' | 'title_desc' | 'default'} SortTypesT
8
+ */
9
+ const sortTypes = {
10
+ title_asc: 'title_asc',
11
+ title_desc: 'title_desc',
12
+ default: 'default'
13
+ };
14
+ export default class ViewableFilesProvider {
15
+ /**
16
+ * @param {import('../BookReader').default} bookreader
17
+ */
18
+ constructor({ baseHost, bookreader, onProviderChange }) {
19
+ /** @type {import('../BookReader').default} */
20
+ this.bookreader = bookreader;
21
+ this.onProviderChange = onProviderChange;
22
+ this.baseHost = baseHost;
23
+
24
+ const files = bookreader.options.multipleBooksList.by_subprefix;
25
+ this.viewableFiles = Object.keys(files).map(item => files[item]);
26
+ this.volumeCount = Object.keys(files).length;
27
+
28
+ this.id = "volumes";
29
+ this.label = `Viewable files (${this.volumeCount})`;
30
+ this.icon = html`${viewableFilesIcon}`;
31
+ this.sortOrderBy = sortTypes.default;
32
+
33
+ this.component = document.createElement("iaux-in-viewable-files-panel");
34
+ this.component.addSortToUrl = true;
35
+ this.component.subPrefix = bookreader.options.subPrefix || "";
36
+ this.component.baseHost = baseHost;
37
+ this.component.fileList = [...this.viewableFiles];
38
+
39
+ this.sortFilesComponent = document.createElement("iaux-in-sort-files-button");
40
+ this.sortFilesComponent.fileListRaw = this.viewableFiles;
41
+ this.sortFilesComponent.addEventListener('fileListSorted', (e) => this.handleFileListSorted(e));
42
+ this.actionButton = this.sortFilesComponent;
43
+
44
+ // get sort state from query param
45
+ if (this.bookreader.urlPlugin) {
46
+ this.bookreader.urlPlugin.pullFromAddressBar();
47
+
48
+ const urlSortValue = this.bookreader.urlPlugin.getUrlParam('sort');
49
+ if (urlSortValue === sortTypes.title_asc || urlSortValue === sortTypes.title_desc) {
50
+ this.sortOrderBy = urlSortValue;
51
+ }
52
+ }
53
+
54
+ this.sortFilesComponent.sortVolumes(this.sortOrderBy);
55
+
56
+ this.onProviderChange(this.bookreader);
57
+ }
58
+
59
+ /** @param { SortTypesT } sortType */
60
+ async handleFileListSorted(event) {
61
+ const { sortType, sortedFiles } = event.detail;
62
+
63
+ this.viewableFiles = sortedFiles;
64
+ this.sortOrderBy = sortType;
65
+
66
+ // update the component
67
+ this.component.fileList = [...this.viewableFiles];
68
+ await this.component.updateComplete;
69
+
70
+ if (this.bookreader.urlPlugin) {
71
+ this.bookreader.urlPlugin.pullFromAddressBar();
72
+ if (this.sortOrderBy !== sortTypes.default) {
73
+ this.bookreader.urlPlugin.setUrlParam('sort', this.sortOrderBy);
74
+ } else {
75
+ this.bookreader.urlPlugin.removeUrlParam('sort');
76
+ }
77
+ }
78
+
79
+ this.onProviderChange(this.bookreader);
80
+
81
+ this.multipleFilesClicked(this.sortOrderBy);
82
+ }
83
+
84
+ /**
85
+ * @param { SortTypesT } orderBy
86
+ */
87
+ multipleFilesClicked(orderBy) {
88
+ window.archive_analytics?.send_event(
89
+ 'BookReader',
90
+ `VolumesSort|${orderBy}`,
91
+ window.location.path,
92
+ );
93
+ }
94
+
95
+ }
@@ -1,5 +1,6 @@
1
- import { html } from 'lit-element';
2
- import './visual-adjustments.js';
1
+ import { html } from 'lit';
2
+ import '@internetarchive/icon-visual-adjustment/icon-visual-adjustment';
3
+ import './visual-adjustments';
3
4
 
4
5
  const visualAdjustmentOptions = [{
5
6
  id: 'brightness',
@@ -27,11 +28,11 @@ const visualAdjustmentOptions = [{
27
28
  active: false,
28
29
  }];
29
30
 
30
- export default class {
31
+ export default class VisualAdjustmentsProvider {
31
32
  constructor(options) {
32
- const { onOptionChange = () => {}, bookContainerSelector, bookreader } = options;
33
- this.onOptionChange = onOptionChange;
34
- this.bookContainerSelector = bookContainerSelector;
33
+ const { onProviderChange, bookreader } = options;
34
+ this.onProviderChange = onProviderChange;
35
+ this.bookContainer = bookreader.refs.$brContainer;
35
36
  this.bookreader = bookreader;
36
37
 
37
38
  this.onAdjustmentChange = this.onAdjustmentChange.bind(this);
@@ -41,7 +42,7 @@ export default class {
41
42
  this.onZoomOut = this.onZoomOut.bind(this);
42
43
 
43
44
  this.activeCount = 0;
44
- this.icon = html`<ia-icon icon="visualAdjustment" style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon>`;
45
+ this.icon = html`<ia-icon-visual-adjustment style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon-visual-adjustment>`;
45
46
  this.label = 'Visual Adjustments';
46
47
  this.menuDetails = this.updateOptionsCount();
47
48
  this.id = 'adjustment';
@@ -60,7 +61,7 @@ export default class {
60
61
  }
61
62
 
62
63
  onZoomOut() {
63
- this.bookreader.zoom();
64
+ this.bookreader.zoom(-1);
64
65
  }
65
66
 
66
67
  onAdjustmentChange(event) {
@@ -76,7 +77,7 @@ export default class {
76
77
  return newValue ? [...values, newValue] : values;
77
78
  }, []).join(' ');
78
79
 
79
- document.querySelector(this.bookContainerSelector).style.setProperty('filter', filters);
80
+ this.bookContainer.css('filter', filters);
80
81
 
81
82
  this.optionUpdateComplete(event);
82
83
  }
@@ -84,7 +85,7 @@ export default class {
84
85
  optionUpdateComplete(event) {
85
86
  this.activeCount = event.detail.activeCount;
86
87
  this.updateOptionsCount(event);
87
- this.onOptionChange(event);
88
+ this.onProviderChange();
88
89
  }
89
90
 
90
91
  updateOptionsCount() {
@@ -1,6 +1,6 @@
1
- import { css, html, LitElement } from "lit-element";
2
- import { repeat } from "lit-html/directives/repeat.js";
3
- import { nothing } from "lit-html";
1
+ import { css, html, LitElement } from "lit";
2
+ import { repeat } from "lit/directives/repeat.js";
3
+ import { nothing } from "lit";
4
4
  import checkmarkIcon from '../assets/icon_checkmark.js';
5
5
  import "@internetarchive/icon-magnify-minus/icon-magnify-minus";
6
6
  import "@internetarchive/icon-magnify-plus/icon-magnify-plus";
@@ -23,42 +23,15 @@ export class BookModel {
23
23
  this.br = br;
24
24
  this.reduceSet = br.reduceSet;
25
25
  this.ppi = br.options?.ppi ?? DEFAULT_OPTIONS.ppi;
26
+ /** @type {'lr' | 'rl'} Page progression */
27
+ this.pageProgression = br.options?.pageProgression ?? DEFAULT_OPTIONS.pageProgression;
26
28
 
27
29
  /** @type {{width: number, height: number}} memoize storage */
28
30
  this._medianPageSize = null;
29
- /** @deprecated @type {{width: number, height: number}} memoize storage */
30
- this._medianPageSizePixels = null;
31
31
  /** @type {[PageData[], number]} */
32
32
  this._getDataFlattenedCached = null;
33
33
  }
34
34
 
35
- /**
36
- * @deprecated Use getMedianPageSizeInches
37
- * Memoized
38
- * @return {{width: number, height: number}}
39
- */
40
- getMedianPageSize() {
41
- if (this._medianPageSizePixels) {
42
- return this._medianPageSizePixels;
43
- }
44
-
45
- // A little expensive but we just do it once
46
- const widths = [];
47
- const heights = [];
48
- for (let i = 0; i < this.getNumLeafs(); i++) {
49
- widths.push(this.getPageWidth(i));
50
- heights.push(this.getPageHeight(i));
51
- }
52
-
53
- widths.sort();
54
- heights.sort();
55
- this._medianPageSizePixels = {
56
- width: widths[Math.floor(widths.length / 2)],
57
- height: heights[Math.floor(heights.length / 2)]
58
- };
59
- return this._medianPageSizePixels;
60
- }
61
-
62
35
  /** Get median width/height of page in inches. Memoized for performance. */
63
36
  getMedianPageSizeInches() {
64
37
  if (this._medianPageSize) {
@@ -72,8 +45,8 @@ export class BookModel {
72
45
  heights.push(page.heightInches);
73
46
  }
74
47
 
75
- widths.sort();
76
- heights.sort();
48
+ widths.sort((a, b) => a - b);
49
+ heights.sort((a, b) => a - b);
77
50
 
78
51
  this._medianPageSize = {
79
52
  width: widths[Math.floor(widths.length / 2)],
@@ -226,7 +199,7 @@ export class BookModel {
226
199
  * @return {[PageIndex, PageIndex]} eg [0, 1]
227
200
  */
228
201
  getSpreadIndices(pindex) {
229
- if (this.br.pageProgression == 'rl') {
202
+ if (this.pageProgression == 'rl') {
230
203
  return this.getPageSide(pindex) == 'R' ? [pindex + 1, pindex] : [pindex, pindex - 1];
231
204
  } else {
232
205
  return this.getPageSide(pindex) == 'L' ? [pindex, pindex + 1] : [pindex - 1, pindex];
@@ -379,8 +352,9 @@ export class PageModel {
379
352
  * @param {PageIndex} index
380
353
  */
381
354
  constructor(book, index) {
382
- // TODO: Get default from config
383
- this.ppi = book._getDataProp(index, 'ppi', book.ppi);
355
+ // Values less than 10 cause the UI to not work correctly
356
+ const pagePPI = book._getDataProp(index, 'ppi', book.ppi);
357
+ this.ppi = Math.max(pagePPI < 10 ? book.ppi : pagePPI, 10);
384
358
  this.book = book;
385
359
  this.index = index;
386
360
  this.width = book.getPageWidth(index);
@@ -437,6 +411,42 @@ export class PageModel {
437
411
  return this.findNext();
438
412
  }
439
413
 
414
+ /** @type {PageModel | null} */
415
+ get left() {
416
+ return this.book.pageProgression === 'lr' ? this.prev : this.next;
417
+ }
418
+
419
+ /** @type {PageModel | null} */
420
+ get right() {
421
+ return this.book.pageProgression === 'lr' ? this.next : this.prev;
422
+ }
423
+
424
+ /**
425
+ * @type {{left: PageModel | null, right: PageModel | null}}
426
+ */
427
+ get spread() {
428
+ return {
429
+ left: this.pageSide === 'L' ? this : this.left,
430
+ right: this.pageSide === 'R' ? this : this.right,
431
+ };
432
+ }
433
+
434
+ /**
435
+ * @param {number} pages
436
+ */
437
+ goLeft(pages) {
438
+ const newIndex = this.book.pageProgression === 'lr' ? this.index - pages : this.index + pages;
439
+ return this.book.getPage(newIndex);
440
+ }
441
+
442
+ /**
443
+ * @param {number} pages
444
+ */
445
+ goRight(pages) {
446
+ const newIndex = this.book.pageProgression === 'lr' ? this.index + pages : this.index - pages;
447
+ return this.book.getPage(newIndex);
448
+ }
449
+
440
450
  /**
441
451
  * @param {number} reduce
442
452
  * @param {number} rotate
@@ -498,6 +508,26 @@ export class PageModel {
498
508
  return new PageModel(this.book, this.index - 1);
499
509
  }
500
510
  }
511
+
512
+ /**
513
+ * @param {object} [arg0]
514
+ * @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
515
+ * of a series of unviewable pages instead of each page
516
+ * @return {PageModel|void}
517
+ */
518
+ findLeft({ combineConsecutiveUnviewables = false } = {}) {
519
+ return this.book.pageProgression === 'lr' ? this.findPrev({ combineConsecutiveUnviewables }) : this.findNext({ combineConsecutiveUnviewables });
520
+ }
521
+
522
+ /**
523
+ * @param {object} [arg0]
524
+ * @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
525
+ * of a series of unviewable pages instead of each page
526
+ * @return {PageModel|void}
527
+ */
528
+ findRight({ combineConsecutiveUnviewables = false } = {}) {
529
+ return this.book.pageProgression === 'lr' ? this.findNext({ combineConsecutiveUnviewables }) : this.findPrev({ combineConsecutiveUnviewables });
530
+ }
501
531
  }
502
532
 
503
533
  // There are a few main ways we can reference a specific page in a book: