@internetarchive/bookreader 5.0.0-9 → 5.0.0-90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/.eslintrc.js +21 -19
  2. package/.github/workflows/node.js.yml +76 -11
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +404 -1125
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1782 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +7 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/1up.svg +1 -1
  13. package/BookReader/icons/2up.svg +1 -1
  14. package/BookReader/icons/advance.svg +1 -1
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -1
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -1
  19. package/BookReader/icons/fullscreen_exit.svg +1 -1
  20. package/BookReader/icons/hamburger.svg +1 -1
  21. package/BookReader/icons/left-arrow.svg +1 -1
  22. package/BookReader/icons/magnify-minus.svg +1 -1
  23. package/BookReader/icons/magnify-plus.svg +1 -1
  24. package/BookReader/icons/magnify.svg +1 -1
  25. package/BookReader/icons/pause.svg +1 -1
  26. package/BookReader/icons/play.svg +1 -1
  27. package/BookReader/icons/playback-speed.svg +1 -1
  28. package/BookReader/icons/read-aloud.svg +1 -1
  29. package/BookReader/icons/review.svg +1 -1
  30. package/BookReader/icons/thumbnails.svg +1 -1
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -1
  33. package/BookReader/images/BRicons.svg +3 -3
  34. package/BookReader/images/books_graphic.svg +1 -1
  35. package/BookReader/images/icon_book.svg +1 -1
  36. package/BookReader/images/icon_bookmark.svg +1 -1
  37. package/BookReader/images/icon_gear.svg +1 -1
  38. package/BookReader/images/icon_hamburger.svg +1 -1
  39. package/BookReader/images/icon_home.svg +1 -1
  40. package/BookReader/images/icon_info.svg +1 -1
  41. package/BookReader/images/icon_one_page.svg +1 -1
  42. package/BookReader/images/icon_pause.svg +1 -1
  43. package/BookReader/images/icon_play.svg +1 -1
  44. package/BookReader/images/icon_playback-rate.svg +1 -1
  45. package/BookReader/images/icon_search_button.svg +1 -1
  46. package/BookReader/images/icon_share.svg +1 -1
  47. package/BookReader/images/icon_skip-ahead.svg +1 -1
  48. package/BookReader/images/icon_skip-back.svg +1 -1
  49. package/BookReader/images/icon_speaker.svg +1 -1
  50. package/BookReader/images/icon_speaker_open.svg +1 -1
  51. package/BookReader/images/icon_thumbnails.svg +1 -1
  52. package/BookReader/images/icon_toc.svg +1 -1
  53. package/BookReader/images/icon_two_pages.svg +1 -1
  54. package/BookReader/images/marker_chap-off.svg +1 -1
  55. package/BookReader/images/marker_chap-on.svg +1 -1
  56. package/BookReader/images/marker_srch-on.svg +1 -1
  57. package/BookReader/images/unviewable_page.png +0 -0
  58. package/BookReader/jquery-3.js +2 -0
  59. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  60. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  61. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  62. package/BookReader/plugins/plugin.autoplay.js +1 -1
  63. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  64. package/BookReader/plugins/plugin.chapters.js +25 -1
  65. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  66. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  67. package/BookReader/plugins/plugin.iframe.js +1 -1
  68. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  69. package/BookReader/plugins/plugin.iiif.js +2 -0
  70. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  71. package/BookReader/plugins/plugin.resume.js +1 -1
  72. package/BookReader/plugins/plugin.resume.js.map +1 -1
  73. package/BookReader/plugins/plugin.search.js +2 -1
  74. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  75. package/BookReader/plugins/plugin.search.js.map +1 -1
  76. package/BookReader/plugins/plugin.text_selection.js +2 -1
  77. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  78. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  79. package/BookReader/plugins/plugin.tts.js +1 -1
  80. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  81. package/BookReader/plugins/plugin.tts.js.map +1 -1
  82. package/BookReader/plugins/plugin.url.js +1 -1
  83. package/BookReader/plugins/plugin.url.js.map +1 -1
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  85. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  86. package/BookReader/webcomponents-bundle.js +3 -0
  87. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  88. package/BookReader/webcomponents-bundle.js.map +1 -0
  89. package/BookReaderDemo/BookReaderDemo.css +18 -19
  90. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  91. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  92. package/BookReaderDemo/IADemoBr.js +144 -0
  93. package/BookReaderDemo/demo-advanced.html +2 -2
  94. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  95. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  96. package/BookReaderDemo/demo-fullscreen.html +2 -4
  97. package/BookReaderDemo/demo-iiif.html +99 -12
  98. package/BookReaderDemo/demo-internetarchive.html +214 -18
  99. package/BookReaderDemo/demo-multiple.html +2 -1
  100. package/BookReaderDemo/demo-preview-pages.html +526 -525
  101. package/BookReaderDemo/demo-simple.html +2 -1
  102. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
  103. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  104. package/BookReaderDemo/immersion-1up.html +2 -2
  105. package/BookReaderDemo/immersion-mode.html +2 -4
  106. package/BookReaderDemo/toggle_controls.html +3 -2
  107. package/BookReaderDemo/view_mode.html +2 -1
  108. package/BookReaderDemo/viewmode-cycle.html +2 -3
  109. package/CHANGELOG.md +584 -33
  110. package/README.md +14 -1
  111. package/babel.config.js +20 -0
  112. package/codecov.yml +6 -0
  113. package/index.html +5 -2
  114. package/jsconfig.json +19 -0
  115. package/netlify.toml +9 -0
  116. package/package.json +70 -62
  117. package/renovate.json +52 -0
  118. package/scripts/preversion.js +0 -1
  119. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  120. package/src/BookNavigator/assets/button-base.js +5 -2
  121. package/src/BookNavigator/assets/ia-logo.js +17 -0
  122. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  123. package/src/BookNavigator/assets/icon_close.js +1 -1
  124. package/src/BookNavigator/book-navigator.js +590 -0
  125. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  126. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  127. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  128. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +4 -9
  129. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  130. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  131. package/src/BookNavigator/delete-modal-actions.js +1 -1
  132. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  133. package/src/BookNavigator/downloads/downloads.js +29 -25
  134. package/src/BookNavigator/search/search-provider.js +50 -28
  135. package/src/BookNavigator/search/search-results.js +24 -10
  136. package/src/BookNavigator/sharing.js +27 -0
  137. package/src/BookNavigator/viewable-files.js +95 -0
  138. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +13 -12
  139. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +7 -7
  140. package/src/BookReader/BookModel.js +76 -41
  141. package/src/BookReader/DragScrollable.js +233 -0
  142. package/src/BookReader/ImageCache.js +48 -15
  143. package/src/BookReader/Mode1Up.js +56 -351
  144. package/src/BookReader/Mode1UpLit.js +388 -0
  145. package/src/BookReader/Mode2Up.js +73 -1318
  146. package/src/BookReader/Mode2UpLit.js +777 -0
  147. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  148. package/src/BookReader/ModeSmoothZoom.js +312 -0
  149. package/src/BookReader/ModeThumb.js +19 -13
  150. package/src/BookReader/Navbar/Navbar.js +70 -54
  151. package/src/BookReader/PageContainer.js +116 -22
  152. package/src/BookReader/ReduceSet.js +3 -3
  153. package/src/BookReader/Toolbar/Toolbar.js +14 -41
  154. package/src/BookReader/events.js +2 -3
  155. package/src/BookReader/options.js +73 -15
  156. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  157. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  158. package/src/BookReader/utils/SelectionObserver.js +45 -0
  159. package/src/BookReader/utils/classes.js +1 -1
  160. package/src/BookReader/utils.js +128 -13
  161. package/src/BookReader.js +544 -1078
  162. package/src/BookReaderPlugin.js +44 -0
  163. package/src/assets/icons/magnify-minus.svg +3 -7
  164. package/src/assets/icons/magnify-plus.svg +3 -7
  165. package/src/assets/icons/voice.svg +1 -0
  166. package/src/assets/images/unviewable_page.png +0 -0
  167. package/src/css/BookReader.scss +1 -5
  168. package/src/css/_BRBookmarks.scss +1 -1
  169. package/src/css/_BRComponent.scss +1 -1
  170. package/src/css/_BRicon.scss +8 -2
  171. package/src/css/_BRmain.scss +16 -3
  172. package/src/css/_BRnav.scss +12 -42
  173. package/src/css/_BRpages.scss +170 -42
  174. package/src/css/_BRsearch.scss +68 -25
  175. package/src/css/_BRtoolbar.scss +5 -5
  176. package/src/css/_TextSelection.scss +87 -27
  177. package/src/css/_colorbox.scss +2 -2
  178. package/src/css/_controls.scss +24 -7
  179. package/src/css/_icons.scss +1 -1
  180. package/src/ia-bookreader/ia-bookreader.js +224 -0
  181. package/src/plugins/plugin.archive_analytics.js +84 -78
  182. package/src/plugins/plugin.autoplay.js +99 -104
  183. package/src/plugins/plugin.chapters.js +237 -191
  184. package/src/plugins/plugin.iframe.js +1 -1
  185. package/src/plugins/plugin.iiif.js +141 -0
  186. package/src/plugins/plugin.resume.js +53 -50
  187. package/src/plugins/plugin.text_selection.js +503 -175
  188. package/src/plugins/plugin.vendor-fullscreen.js +7 -7
  189. package/src/plugins/search/plugin.search.js +151 -127
  190. package/src/plugins/search/utils.js +43 -0
  191. package/src/plugins/search/view.js +37 -59
  192. package/src/plugins/tts/AbstractTTSEngine.js +75 -45
  193. package/src/plugins/tts/FestivalTTSEngine.js +21 -31
  194. package/src/plugins/tts/PageChunk.js +16 -23
  195. package/src/plugins/tts/PageChunkIterator.js +11 -17
  196. package/src/plugins/tts/WebTTSEngine.js +88 -72
  197. package/src/plugins/tts/plugin.tts.js +310 -350
  198. package/src/plugins/tts/utils.js +16 -26
  199. package/src/plugins/url/UrlPlugin.js +191 -0
  200. package/src/plugins/{plugin.url.js → url/plugin.url.js} +47 -18
  201. package/src/util/browserSniffing.js +22 -0
  202. package/src/util/docCookies.js +21 -2
  203. package/src/util/strings.js +1 -0
  204. package/tests/e2e/README.md +37 -0
  205. package/tests/e2e/autoplay.test.js +9 -6
  206. package/tests/e2e/base.test.js +8 -16
  207. package/tests/e2e/helpers/base.js +55 -50
  208. package/tests/e2e/helpers/debug.js +1 -1
  209. package/tests/e2e/helpers/mockSearch.js +19 -22
  210. package/tests/e2e/helpers/params.js +17 -0
  211. package/tests/e2e/helpers/rightToLeft.js +8 -14
  212. package/tests/e2e/helpers/search.js +73 -0
  213. package/tests/e2e/models/Navigation.js +20 -37
  214. package/tests/e2e/rightToLeft.test.js +4 -5
  215. package/tests/e2e/viewmode.test.js +40 -33
  216. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  217. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  218. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  219. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  220. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  221. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  222. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  223. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  224. package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
  225. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  226. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  227. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  228. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  229. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  230. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  231. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  232. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  233. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  234. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  235. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  236. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  237. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +42 -29
  238. package/tests/jest/BookReader/PageContainer.test.js +238 -0
  239. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  240. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +3 -3
  241. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  242. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  243. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  244. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  245. package/tests/jest/BookReader/utils.test.js +250 -0
  246. package/tests/jest/BookReader.keyboard.test.js +190 -0
  247. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +10 -2
  248. package/tests/{BookReader.test.js → jest/BookReader.test.js} +43 -53
  249. package/tests/jest/plugins/plugin.archive_analytics.test.js +20 -0
  250. package/tests/jest/plugins/plugin.autoplay.test.js +35 -0
  251. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  252. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +4 -4
  253. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +22 -35
  254. package/tests/jest/plugins/plugin.text_selection.test.js +316 -0
  255. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  256. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +19 -47
  257. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +42 -9
  258. package/tests/jest/plugins/search/utils.js +25 -0
  259. package/tests/jest/plugins/search/utils.test.js +29 -0
  260. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +30 -10
  261. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  262. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  263. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  264. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  265. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  266. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  267. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +57 -18
  268. package/tests/jest/setup.js +3 -0
  269. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  270. package/tests/jest/util/docCookies.test.js +24 -0
  271. package/tests/{util → jest/util}/strings.test.js +1 -1
  272. package/tests/{utils.js → jest/utils.js} +38 -0
  273. package/webpack.config.js +16 -10
  274. package/.babelrc +0 -12
  275. package/.dependabot/config.yml +0 -6
  276. package/.testcaferc.json +0 -5
  277. package/BookReader/bookreader-component-bundle.js +0 -1450
  278. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  279. package/BookReader/bookreader-component-bundle.js.map +0 -1
  280. package/BookReader/jquery-1.10.1.js +0 -2
  281. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  282. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  283. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  284. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  285. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  286. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  287. package/BookReaderDemo/IIIFBookReader.js +0 -207
  288. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  289. package/BookReaderDemo/demo-autoplay.html +0 -38
  290. package/BookReaderDemo/demo-iiif.js +0 -26
  291. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  292. package/karma.conf.js +0 -23
  293. package/src/BookNavigator/BookModel.js +0 -14
  294. package/src/BookNavigator/BookNavigator.js +0 -446
  295. package/src/BookNavigator/assets/book-loader.js +0 -27
  296. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  297. package/src/BookNavigator/search/a-search-result.js +0 -55
  298. package/src/BookReader/DebugConsole.js +0 -54
  299. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  300. package/src/ItemNavigator/ItemNavigator.js +0 -376
  301. package/src/ItemNavigator/providers/sharing.js +0 -29
  302. package/src/css/_MobileNav.scss +0 -194
  303. package/src/dragscrollable-br.js +0 -261
  304. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  305. package/src/plugins/plugin.mobile_nav.js +0 -287
  306. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  307. package/tests/BookReader/DebugConsole.test.js +0 -25
  308. package/tests/BookReader/Mode1Up.test.js +0 -164
  309. package/tests/BookReader/Mode2Up.test.js +0 -247
  310. package/tests/BookReader/PageContainer.test.js +0 -115
  311. package/tests/BookReader/utils.test.js +0 -109
  312. package/tests/e2e/helpers/desktopSearch.js +0 -72
  313. package/tests/e2e/helpers/mobileSearch.js +0 -85
  314. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  315. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  316. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  317. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  318. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  319. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  320. package/tests/plugins/plugin.autoplay.test.js +0 -52
  321. package/tests/plugins/plugin.chapters.test.js +0 -130
  322. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  323. package/tests/plugins/plugin.text_selection.test.js +0 -203
  324. package/tests/util/docCookies.test.js +0 -15
@@ -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 */
@@ -26,12 +29,12 @@ export default class {
26
29
  this.bindEventListeners = this.bindEventListeners.bind(this);
27
30
  this.getMenuDetails = this.getMenuDetails.bind(this);
28
31
  this.getComponent = this.getComponent.bind(this);
29
- this.advanceToPage = this.advanceToPage.bind(this);
30
32
  this.updateMenu = this.updateMenu.bind(this);
31
33
 
32
- this.onSearchChange = onSearchChange;
33
- this.bookreader = brInstance;
34
- 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>`;
35
38
  this.label = 'Search inside';
36
39
  this.menuDetails = this.getMenuDetails();
37
40
  this.id = 'search';
@@ -41,7 +44,7 @@ export default class {
41
44
 
42
45
  getMenuDetails() {
43
46
  const { resultsCount, query, queryInProgress } = searchState;
44
- if (queryInProgress || !query) { return nothing }
47
+ if (queryInProgress || !query) { return nothing; }
45
48
  const unit = resultsCount === 1 ? 'result' : 'results';
46
49
  return html`(${resultsCount} ${unit})`;
47
50
  }
@@ -49,11 +52,11 @@ export default class {
49
52
  bindEventListeners() {
50
53
  window.addEventListener('BookReader:SearchStarted', this.onSearchStarted);
51
54
  window.addEventListener('BookReader:SearchCallback', this.onSearchResultsChange);
52
- window.addEventListener('BookReader:SearchCallbackEmpty', (event) => { this.onSearchRequestError(event, 'noResults') });
53
- window.addEventListener('BookReader:SearchCallbackNotIndexed', (event) => { this.onSearchRequestError(event, 'notIndexed') });
54
- window.addEventListener('BookReader:SearchCallbackError', (event) => { this.onSearchRequestError(event) });
55
- window.addEventListener('BookReader:SearchResultsCleared', () => { this.onSearchResultsCleared() });
56
- window.addEventListener('BookReader:SearchCanceled', (e) => { this.onSearchCanceled(e) });
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); });
57
60
  }
58
61
 
59
62
  /**
@@ -69,13 +72,20 @@ export default class {
69
72
  errorMessage: '',
70
73
  };
71
74
  const updateMenuFor = {
72
- searchCanceled: true
75
+ searchCanceled: true,
73
76
  };
74
77
  this.updateMenu(updateMenuFor);
78
+
79
+ if (this.bookreader.urlPlugin) {
80
+ this.updateSearchInUrl();
81
+ }
75
82
  }
76
83
 
77
84
  onSearchStarted(e) {
78
- const { term = '' } = e.detail.props;
85
+ const { term = '', instance } = e.detail.props;
86
+ if (instance) {
87
+ this.bookreader = instance;
88
+ }
79
89
  searchState.query = term;
80
90
  searchState.results = [];
81
91
  searchState.resultsCount = 0;
@@ -104,6 +114,7 @@ export default class {
104
114
  };
105
115
 
106
116
  const messageToShow = errorMessages[errorType] ?? errorMessages.default;
117
+ searchState.query = instance?.searchResults?.q || '';
107
118
  searchState.results = [];
108
119
  searchState.resultsCount = 0;
109
120
  searchState.queryInProgress = false;
@@ -136,9 +147,24 @@ export default class {
136
147
  resultsCount: 0,
137
148
  queryInProgress: false,
138
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
+ }
139
167
  }
140
- this.updateMenu();
141
- this.bookreader?.searchView?.clearSearchFieldAndResults();
142
168
  }
143
169
 
144
170
  /**
@@ -148,7 +174,7 @@ export default class {
148
174
  updateMenu(searchUpdates = {}) {
149
175
  this.menuDetails = this.getMenuDetails();
150
176
  this.component = this.getComponent();
151
- this.onSearchChange(this.bookreader, searchUpdates);
177
+ this.onProviderChange(this.bookreader, searchUpdates);
152
178
  }
153
179
 
154
180
  getComponent() {
@@ -168,14 +194,10 @@ export default class {
168
194
  `;
169
195
  }
170
196
 
197
+ /**
198
+ * @param {{ detail: {match: SearchInsideMatch} }} param0
199
+ */
171
200
  onSearchResultsClicked({ detail }) {
172
- const page = detail.match.par[0].page;
173
- this.advanceToPage(page);
174
- }
175
-
176
- advanceToPage(leaf) {
177
- const page = this.bookreader.leafNumToIndex(leaf);
178
- this.bookreader._searchPluginGoToResult(page);
179
- this.bookreader.updateSearchHilites();
201
+ this.bookreader._searchPluginGoToResult(detail.match.matchIndex);
180
202
  }
181
203
  }
@@ -1,12 +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';
9
-
8
+ /** @typedef {import('@/src/plugins/search/plugin.search.js').SearchInsideMatch} SearchInsideMatch */
10
9
 
11
10
  export class IABookSearchResults extends LitElement {
12
11
  static get properties() {
@@ -24,11 +23,12 @@ export class IABookSearchResults extends LitElement {
24
23
  constructor() {
25
24
  super();
26
25
 
26
+ /** @type {SearchInsideMatch[]} */
27
27
  this.results = [];
28
28
  this.query = '';
29
29
  this.queryInProgress = false;
30
30
  this.renderHeader = false;
31
- this.renderSearchAllFields = false;
31
+ this.renderSearchAllFiles = false;
32
32
  this.displayResultImages = false;
33
33
  this.errorMessage = '';
34
34
 
@@ -61,6 +61,9 @@ export class IABookSearchResults extends LitElement {
61
61
 
62
62
  setQuery(e) {
63
63
  this.query = e.currentTarget.value;
64
+ if (!this.query) {
65
+ this.cancelSearch();
66
+ }
64
67
  }
65
68
 
66
69
  performSearch(e) {
@@ -78,7 +81,15 @@ export class IABookSearchResults extends LitElement {
78
81
  }));
79
82
  }
80
83
 
81
- 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
+ }));
82
93
  this.dispatchEvent(new CustomEvent('closeMenu', {
83
94
  bubbles: true,
84
95
  composed: true,
@@ -130,10 +141,12 @@ export class IABookSearchResults extends LitElement {
130
141
  return html`
131
142
  <ul class="results ${resultsClass}">
132
143
  ${this.results.map(match => html`
133
- <book-search-result
134
- .match=${match}
135
- @resultSelected=${this.selectResult}
136
- ></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>
137
150
  `)}
138
151
  </ul>
139
152
  `;
@@ -149,6 +162,7 @@ export class IABookSearchResults extends LitElement {
149
162
  name="query"
150
163
  alt="Search inside this book."
151
164
  @keyup=${this.setQuery}
165
+ @search=${this.setQuery}
152
166
  .value=${this.query}
153
167
  />
154
168
  </fieldset>
@@ -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',
@@ -19,19 +20,19 @@ const visualAdjustmentOptions = [{
19
20
  value: 100,
20
21
  }, {
21
22
  id: 'invert',
22
- name: 'Inverted colors (dark mode)',
23
+ name: 'Invert colors (dark mode)',
23
24
  active: false,
24
25
  }, {
25
26
  id: 'grayscale',
26
- name: 'Grayscale',
27
+ name: 'Convert to grayscale',
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";
@@ -43,7 +43,7 @@ export class IABookVisualAdjustments extends LitElement {
43
43
  get activeOptions() {
44
44
  return this.options.reduce(
45
45
  (results, option) => (option.active ? [...results, option.id] : results),
46
- []
46
+ [],
47
47
  );
48
48
  }
49
49
 
@@ -71,7 +71,7 @@ export class IABookVisualAdjustments extends LitElement {
71
71
  bubbles: true,
72
72
  composed: true,
73
73
  detail,
74
- })
74
+ }),
75
75
  );
76
76
  }
77
77
 
@@ -93,7 +93,7 @@ export class IABookVisualAdjustments extends LitElement {
93
93
  changeActiveStateFor(optionName) {
94
94
  const updatedOptions = [...this.options];
95
95
  const checkedOption = updatedOptions.find(
96
- (option) => option.id === optionName
96
+ (option) => option.id === optionName,
97
97
  );
98
98
  checkedOption.active = !checkedOption.active;
99
99
  this.options = updatedOptions;
@@ -157,7 +157,7 @@ export class IABookVisualAdjustments extends LitElement {
157
157
 
158
158
  get zoomControls() {
159
159
  return html`
160
- <h4>Zoom</h4>
160
+ <h4>Adjust zoom</h4>
161
161
  <button class="zoom_out" @click=${this.emitZoomOut} title="zoom out">
162
162
  <ia-icon-magnify-minus></ia-icon-magnify-minus>
163
163
  </button>
@@ -4,11 +4,6 @@ import { clamp } from './utils.js';
4
4
  /** @typedef {import('./options.js').PageData} PageData */
5
5
  /** @typedef {import('../BookReader.js').default} BookReader */
6
6
 
7
- // URI to display when a page is not viewable.
8
- // TODO Render configurable html for the user instead.
9
- // FIXME Don't reference files on archive.org
10
- const UNVIEWABLE_PAGE_URI = '/bookreader/static/preview-default.png';
11
-
12
7
  /**
13
8
  * Contains information about the Book/Document independent of the way it is
14
9
  * being rendering. Nothing here should reference e.g. the mode, zoom, etc.
@@ -23,42 +18,15 @@ export class BookModel {
23
18
  this.br = br;
24
19
  this.reduceSet = br.reduceSet;
25
20
  this.ppi = br.options?.ppi ?? DEFAULT_OPTIONS.ppi;
21
+ /** @type {'lr' | 'rl'} Page progression */
22
+ this.pageProgression = br.options?.pageProgression ?? DEFAULT_OPTIONS.pageProgression;
26
23
 
27
24
  /** @type {{width: number, height: number}} memoize storage */
28
25
  this._medianPageSize = null;
29
- /** @deprecated @type {{width: number, height: number}} memoize storage */
30
- this._medianPageSizePixels = null;
31
26
  /** @type {[PageData[], number]} */
32
27
  this._getDataFlattenedCached = null;
33
28
  }
34
29
 
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
30
  /** Get median width/height of page in inches. Memoized for performance. */
63
31
  getMedianPageSizeInches() {
64
32
  if (this._medianPageSize) {
@@ -72,12 +40,12 @@ export class BookModel {
72
40
  heights.push(page.heightInches);
73
41
  }
74
42
 
75
- widths.sort();
76
- heights.sort();
43
+ widths.sort((a, b) => a - b);
44
+ heights.sort((a, b) => a - b);
77
45
 
78
46
  this._medianPageSize = {
79
47
  width: widths[Math.floor(widths.length / 2)],
80
- height: heights[Math.floor(heights.length / 2)]
48
+ height: heights[Math.floor(heights.length / 2)],
81
49
  };
82
50
  return this._medianPageSize;
83
51
  }
@@ -187,7 +155,17 @@ export class BookModel {
187
155
  */
188
156
  // eslint-disable-next-line no-unused-vars
189
157
  getPageURI(index, reduce, rotate) {
190
- return !this.getPageProp(index, 'viewable', true) ? UNVIEWABLE_PAGE_URI : this.getPageProp(index, 'uri');
158
+ if (!this.getPageProp(index, 'viewable', true)) {
159
+ const uri = this.br.options.unviewablePageURI;
160
+ if (uri.startsWith('.')) {
161
+ // It's a relative path, so make it relative to the images path
162
+ return this.br.options.imagesBaseURL + uri;
163
+ } else {
164
+ return uri;
165
+ }
166
+ } else {
167
+ return this.getPageProp(index, 'uri');
168
+ }
191
169
  }
192
170
 
193
171
  /**
@@ -226,7 +204,7 @@ export class BookModel {
226
204
  * @return {[PageIndex, PageIndex]} eg [0, 1]
227
205
  */
228
206
  getSpreadIndices(pindex) {
229
- if (this.br.pageProgression == 'rl') {
207
+ if (this.pageProgression == 'rl') {
230
208
  return this.getPageSide(pindex) == 'R' ? [pindex + 1, pindex] : [pindex, pindex - 1];
231
209
  } else {
232
210
  return this.getPageSide(pindex) == 'L' ? [pindex, pindex + 1] : [pindex - 1, pindex];
@@ -379,8 +357,9 @@ export class PageModel {
379
357
  * @param {PageIndex} index
380
358
  */
381
359
  constructor(book, index) {
382
- // TODO: Get default from config
383
- this.ppi = book._getDataProp(index, 'ppi', book.ppi);
360
+ // Values less than 10 cause the UI to not work correctly
361
+ const pagePPI = book._getDataProp(index, 'ppi', book.ppi);
362
+ this.ppi = Math.max(pagePPI < 10 ? book.ppi : pagePPI, 10);
384
363
  this.book = book;
385
364
  this.index = index;
386
365
  this.width = book.getPageWidth(index);
@@ -437,6 +416,42 @@ export class PageModel {
437
416
  return this.findNext();
438
417
  }
439
418
 
419
+ /** @type {PageModel | null} */
420
+ get left() {
421
+ return this.book.pageProgression === 'lr' ? this.prev : this.next;
422
+ }
423
+
424
+ /** @type {PageModel | null} */
425
+ get right() {
426
+ return this.book.pageProgression === 'lr' ? this.next : this.prev;
427
+ }
428
+
429
+ /**
430
+ * @type {{left: PageModel | null, right: PageModel | null}}
431
+ */
432
+ get spread() {
433
+ return {
434
+ left: this.pageSide === 'L' ? this : this.left,
435
+ right: this.pageSide === 'R' ? this : this.right,
436
+ };
437
+ }
438
+
439
+ /**
440
+ * @param {number} pages
441
+ */
442
+ goLeft(pages) {
443
+ const newIndex = this.book.pageProgression === 'lr' ? this.index - pages : this.index + pages;
444
+ return this.book.getPage(newIndex);
445
+ }
446
+
447
+ /**
448
+ * @param {number} pages
449
+ */
450
+ goRight(pages) {
451
+ const newIndex = this.book.pageProgression === 'lr' ? this.index + pages : this.index - pages;
452
+ return this.book.getPage(newIndex);
453
+ }
454
+
440
455
  /**
441
456
  * @param {number} reduce
442
457
  * @param {number} rotate
@@ -498,6 +513,26 @@ export class PageModel {
498
513
  return new PageModel(this.book, this.index - 1);
499
514
  }
500
515
  }
516
+
517
+ /**
518
+ * @param {object} [arg0]
519
+ * @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
520
+ * of a series of unviewable pages instead of each page
521
+ * @return {PageModel|void}
522
+ */
523
+ findLeft({ combineConsecutiveUnviewables = false } = {}) {
524
+ return this.book.pageProgression === 'lr' ? this.findPrev({ combineConsecutiveUnviewables }) : this.findNext({ combineConsecutiveUnviewables });
525
+ }
526
+
527
+ /**
528
+ * @param {object} [arg0]
529
+ * @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
530
+ * of a series of unviewable pages instead of each page
531
+ * @return {PageModel|void}
532
+ */
533
+ findRight({ combineConsecutiveUnviewables = false } = {}) {
534
+ return this.book.pageProgression === 'lr' ? this.findNext({ combineConsecutiveUnviewables }) : this.findPrev({ combineConsecutiveUnviewables });
535
+ }
501
536
  }
502
537
 
503
538
  // There are a few main ways we can reference a specific page in a book: