@internetarchive/bookreader 5.0.0-26 → 5.0.0-29

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 (236) hide show
  1. package/.husky/_/husky.sh +30 -0
  2. package/BookReader/BookReader.css +1 -1
  3. package/BookReader/BookReader.js +1 -1
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/bookreader-component-bundle.js +570 -542
  6. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +23 -0
  7. package/BookReader/bookreader-component-bundle.js.map +1 -1
  8. package/BookReader/plugins/plugin.search.js +1 -1
  9. package/BookReader/plugins/plugin.search.js.map +1 -1
  10. package/BookReader/plugins/plugin.tts.js.map +1 -1
  11. package/BookReader/plugins/plugin.url.js +1 -1
  12. package/BookReader/plugins/plugin.url.js.map +1 -1
  13. package/BookReaderDemo/BookReaderDemo.css +14 -1
  14. package/BookReaderDemo/IADemoBr.js +104 -0
  15. package/BookReaderDemo/demo-internetarchive.html +65 -98
  16. package/CHANGELOG.md +10 -0
  17. package/package.json +9 -6
  18. package/src/BookNavigator/assets/ia-logo.js +17 -0
  19. package/src/BookNavigator/book-navigator.js +521 -0
  20. package/src/BookNavigator/bookmarks/bookmark-button.js +2 -1
  21. package/src/BookNavigator/bookmarks/bookmarks-provider.js +20 -8
  22. package/src/BookNavigator/bookmarks/ia-bookmarks.js +84 -51
  23. package/src/BookNavigator/downloads/downloads-provider.js +5 -9
  24. package/src/BookNavigator/downloads/downloads.js +1 -0
  25. package/src/BookNavigator/search/search-provider.js +15 -8
  26. package/src/BookNavigator/sharing.js +27 -0
  27. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +9 -8
  28. package/src/BookNavigator/volumes/volumes-provider.js +44 -13
  29. package/src/BookNavigator/volumes/volumes.js +14 -3
  30. package/src/BookReader/options.js +6 -0
  31. package/src/BookReader.js +20 -8
  32. package/src/BookReaderComponent/BookReaderComponent.js +53 -32
  33. package/src/css/_BRComponent.scss +1 -1
  34. package/src/plugins/search/plugin.search.js +10 -9
  35. package/src/plugins/tts/FestivalTTSEngine.js +1 -1
  36. package/src/plugins/url/UrlPlugin.js +184 -0
  37. package/src/plugins/url/plugin.url.js +220 -0
  38. package/{src → stat}/BookNavigator/BookModel.js +0 -0
  39. package/{src → stat}/BookNavigator/BookNavigator.js +109 -102
  40. package/stat/BookNavigator/assets/bookmark-colors.js +15 -0
  41. package/stat/BookNavigator/assets/button-base.js +61 -0
  42. package/stat/BookNavigator/assets/ia-logo.js +17 -0
  43. package/stat/BookNavigator/assets/icon_checkmark.js +6 -0
  44. package/stat/BookNavigator/assets/icon_close.js +3 -0
  45. package/stat/BookNavigator/assets/icon_sort_asc.js +5 -0
  46. package/stat/BookNavigator/assets/icon_sort_desc.js +5 -0
  47. package/stat/BookNavigator/assets/icon_sort_neutral.js +5 -0
  48. package/stat/BookNavigator/assets/icon_volumes.js +11 -0
  49. package/stat/BookNavigator/bookmarks/bookmark-button.js +64 -0
  50. package/stat/BookNavigator/bookmarks/bookmark-edit.js +215 -0
  51. package/stat/BookNavigator/bookmarks/bookmarks-list.js +285 -0
  52. package/stat/BookNavigator/bookmarks/bookmarks-loginCTA.js +28 -0
  53. package/stat/BookNavigator/bookmarks/bookmarks-provider.js +56 -0
  54. package/stat/BookNavigator/bookmarks/ia-bookmarks.js +523 -0
  55. package/{src → stat}/BookNavigator/br-fullscreen-mgr.js +1 -2
  56. package/stat/BookNavigator/delete-modal-actions.js +49 -0
  57. package/stat/BookNavigator/downloads/downloads-provider.js +72 -0
  58. package/stat/BookNavigator/downloads/downloads.js +139 -0
  59. package/stat/BookNavigator/provider-config.js +0 -0
  60. package/stat/BookNavigator/search/a-search-result.js +55 -0
  61. package/stat/BookNavigator/search/search-provider.js +180 -0
  62. package/stat/BookNavigator/search/search-results.js +360 -0
  63. package/{src/ItemNavigator/providers → stat/BookNavigator}/sharing.js +3 -5
  64. package/stat/BookNavigator/visual-adjustments/visual-adjustments-provider.js +94 -0
  65. package/stat/BookNavigator/visual-adjustments/visual-adjustments.js +280 -0
  66. package/stat/BookNavigator/volumes/volumes-provider.js +83 -0
  67. package/stat/BookNavigator/volumes/volumes.js +178 -0
  68. package/stat/BookReader/BookModel.js +518 -0
  69. package/stat/BookReader/DebugConsole.js +54 -0
  70. package/stat/BookReader/DragScrollable.js +233 -0
  71. package/stat/BookReader/ImageCache.js +116 -0
  72. package/stat/BookReader/Mode1Up.js +102 -0
  73. package/stat/BookReader/Mode1UpLit.js +434 -0
  74. package/stat/BookReader/Mode2Up.js +1372 -0
  75. package/stat/BookReader/ModeSmoothZoom.js +177 -0
  76. package/stat/BookReader/ModeThumb.js +344 -0
  77. package/stat/BookReader/Navbar/Navbar.js +310 -0
  78. package/stat/BookReader/PageContainer.js +120 -0
  79. package/stat/BookReader/ReduceSet.js +26 -0
  80. package/stat/BookReader/Toolbar/Toolbar.js +384 -0
  81. package/stat/BookReader/events.js +20 -0
  82. package/stat/BookReader/options.js +324 -0
  83. package/stat/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  84. package/stat/BookReader/utils/classes.js +36 -0
  85. package/stat/BookReader/utils.js +240 -0
  86. package/stat/BookReader.js +2550 -0
  87. package/stat/BookReaderComponent/BookReaderComponent.js +117 -0
  88. package/stat/assets/icons/1up.svg +12 -0
  89. package/stat/assets/icons/2up.svg +15 -0
  90. package/stat/assets/icons/advance.svg +26 -0
  91. package/stat/assets/icons/chevron-right.svg +1 -0
  92. package/stat/assets/icons/close-circle-dark.svg +1 -0
  93. package/stat/assets/icons/close-circle.svg +1 -0
  94. package/stat/assets/icons/fullscreen.svg +17 -0
  95. package/stat/assets/icons/fullscreen_exit.svg +17 -0
  96. package/stat/assets/icons/hamburger.svg +15 -0
  97. package/stat/assets/icons/left-arrow.svg +12 -0
  98. package/stat/assets/icons/magnify-minus.svg +16 -0
  99. package/stat/assets/icons/magnify-plus.svg +17 -0
  100. package/stat/assets/icons/magnify.svg +15 -0
  101. package/stat/assets/icons/pause.svg +23 -0
  102. package/stat/assets/icons/play.svg +22 -0
  103. package/stat/assets/icons/playback-speed.svg +34 -0
  104. package/stat/assets/icons/read-aloud.svg +22 -0
  105. package/stat/assets/icons/review.svg +22 -0
  106. package/stat/assets/icons/thumbnails.svg +17 -0
  107. package/stat/assets/icons/voice.svg +1 -0
  108. package/stat/assets/icons/volume-full.svg +22 -0
  109. package/stat/assets/images/BRicons.png +0 -0
  110. package/stat/assets/images/BRicons.svg +94 -0
  111. package/stat/assets/images/BRicons_ia.png +0 -0
  112. package/stat/assets/images/back_pages.png +0 -0
  113. package/stat/assets/images/book_bottom_icon.png +0 -0
  114. package/stat/assets/images/book_down_icon.png +0 -0
  115. package/stat/assets/images/book_left_icon.png +0 -0
  116. package/stat/assets/images/book_leftmost_icon.png +0 -0
  117. package/stat/assets/images/book_right_icon.png +0 -0
  118. package/stat/assets/images/book_rightmost_icon.png +0 -0
  119. package/stat/assets/images/book_top_icon.png +0 -0
  120. package/stat/assets/images/book_up_icon.png +0 -0
  121. package/stat/assets/images/books_graphic.svg +177 -0
  122. package/stat/assets/images/booksplit.png +0 -0
  123. package/stat/assets/images/control_pause_icon.png +0 -0
  124. package/stat/assets/images/control_play_icon.png +0 -0
  125. package/stat/assets/images/embed_icon.png +0 -0
  126. package/stat/assets/images/icon-home-ia.png +0 -0
  127. package/stat/assets/images/icon_OL-logo-xs.png +0 -0
  128. package/stat/assets/images/icon_alert-xs.png +0 -0
  129. package/stat/assets/images/icon_book.svg +12 -0
  130. package/stat/assets/images/icon_bookmark.svg +12 -0
  131. package/stat/assets/images/icon_close-pop.png +0 -0
  132. package/stat/assets/images/icon_download.png +0 -0
  133. package/stat/assets/images/icon_gear.svg +14 -0
  134. package/stat/assets/images/icon_hamburger.svg +20 -0
  135. package/stat/assets/images/icon_home.png +0 -0
  136. package/stat/assets/images/icon_home.svg +21 -0
  137. package/stat/assets/images/icon_home_ia.png +0 -0
  138. package/stat/assets/images/icon_indicator.png +0 -0
  139. package/stat/assets/images/icon_info.svg +11 -0
  140. package/stat/assets/images/icon_one_page.svg +8 -0
  141. package/stat/assets/images/icon_pause.svg +1 -0
  142. package/stat/assets/images/icon_play.svg +1 -0
  143. package/stat/assets/images/icon_playback-rate.svg +15 -0
  144. package/stat/assets/images/icon_return.png +0 -0
  145. package/stat/assets/images/icon_search_button.svg +8 -0
  146. package/stat/assets/images/icon_share.svg +9 -0
  147. package/stat/assets/images/icon_skip-ahead.svg +6 -0
  148. package/stat/assets/images/icon_skip-back.svg +13 -0
  149. package/stat/assets/images/icon_speaker.svg +18 -0
  150. package/stat/assets/images/icon_speaker_open.svg +10 -0
  151. package/stat/assets/images/icon_thumbnails.svg +12 -0
  152. package/stat/assets/images/icon_toc.svg +5 -0
  153. package/stat/assets/images/icon_two_pages.svg +9 -0
  154. package/stat/assets/images/icon_zoomer.png +0 -0
  155. package/stat/assets/images/loading.gif +0 -0
  156. package/stat/assets/images/logo_icon.png +0 -0
  157. package/stat/assets/images/marker_chap-off.png +0 -0
  158. package/stat/assets/images/marker_chap-off.svg +11 -0
  159. package/stat/assets/images/marker_chap-off_ia.png +0 -0
  160. package/stat/assets/images/marker_chap-on.png +0 -0
  161. package/stat/assets/images/marker_chap-on.svg +11 -0
  162. package/stat/assets/images/marker_srch-on.svg +11 -0
  163. package/stat/assets/images/marker_srchchap-off.png +0 -0
  164. package/stat/assets/images/marker_srchchap-on.png +0 -0
  165. package/stat/assets/images/nav_control-dn.png +0 -0
  166. package/stat/assets/images/nav_control-dn_ia.png +0 -0
  167. package/stat/assets/images/nav_control-up.png +0 -0
  168. package/stat/assets/images/nav_control-up_ia.png +0 -0
  169. package/stat/assets/images/nav_control.png +0 -0
  170. package/stat/assets/images/one_page_mode_icon.png +0 -0
  171. package/stat/assets/images/paper-badge.png +0 -0
  172. package/stat/assets/images/print_icon.png +0 -0
  173. package/stat/assets/images/progressbar.gif +0 -0
  174. package/stat/assets/images/right_edges.png +0 -0
  175. package/stat/assets/images/slider.png +0 -0
  176. package/stat/assets/images/slider_ia.png +0 -0
  177. package/stat/assets/images/thumbnail_mode_icon.png +0 -0
  178. package/stat/assets/images/transparent.png +0 -0
  179. package/stat/assets/images/two_page_mode_icon.png +0 -0
  180. package/stat/assets/images/zoom_in_icon.png +0 -0
  181. package/stat/assets/images/zoom_out_icon.png +0 -0
  182. package/stat/css/BookReader.scss +89 -0
  183. package/stat/css/_BRBookmarks.scss +29 -0
  184. package/stat/css/_BRComponent.scss +13 -0
  185. package/stat/css/_BRfloat.scss +197 -0
  186. package/stat/css/_BRicon.scss +48 -0
  187. package/stat/css/_BRmain.scss +251 -0
  188. package/stat/css/_BRnav.scss +359 -0
  189. package/stat/css/_BRpages.scss +139 -0
  190. package/stat/css/_BRsearch.scss +226 -0
  191. package/stat/css/_BRtoolbar.scss +84 -0
  192. package/stat/css/_BRvendor.scss +5 -0
  193. package/stat/css/_MobileNav.scss +194 -0
  194. package/stat/css/_TextSelection.scss +32 -0
  195. package/stat/css/_colorbox.scss +52 -0
  196. package/stat/css/_controls.scss +253 -0
  197. package/stat/css/_icons.scss +121 -0
  198. package/stat/jquery-wrapper.js +4 -0
  199. package/stat/plugins/plugin.archive_analytics.js +86 -0
  200. package/stat/plugins/plugin.autoplay.js +129 -0
  201. package/stat/plugins/plugin.chapters.js +248 -0
  202. package/stat/plugins/plugin.iframe.js +48 -0
  203. package/stat/plugins/plugin.mobile_nav.js +288 -0
  204. package/stat/plugins/plugin.resume.js +68 -0
  205. package/stat/plugins/plugin.text_selection.js +291 -0
  206. package/{src → stat}/plugins/plugin.url.js +0 -0
  207. package/stat/plugins/plugin.vendor-fullscreen.js +247 -0
  208. package/stat/plugins/search/plugin.search.js +439 -0
  209. package/stat/plugins/search/view.js +439 -0
  210. package/stat/plugins/tts/AbstractTTSEngine.js +249 -0
  211. package/stat/plugins/tts/FestivalTTSEngine.js +169 -0
  212. package/stat/plugins/tts/PageChunk.js +107 -0
  213. package/stat/plugins/tts/PageChunkIterator.js +163 -0
  214. package/stat/plugins/tts/WebTTSEngine.js +357 -0
  215. package/stat/plugins/tts/plugin.tts.js +357 -0
  216. package/stat/plugins/tts/tooltip_dict.js +15 -0
  217. package/stat/plugins/tts/utils.js +91 -0
  218. package/stat/util/browserSniffing.js +30 -0
  219. package/stat/util/debouncer.js +26 -0
  220. package/stat/util/docCookies.js +67 -0
  221. package/stat/util/strings.js +34 -0
  222. package/tests/e2e/viewmode.test.js +30 -30
  223. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +64 -52
  224. package/tests/jest/BookReader.test.js +1 -1
  225. package/tests/jest/plugins/url/UrlPlugin.test.js +175 -0
  226. package/tests/jest/plugins/{plugin.url.test.js → url/plugin.url.test.js} +3 -2
  227. package/tests/karma/BookNavigator/book-navigator.test.js +413 -108
  228. package/tests/karma/BookNavigator/bookmarks/bookmark-button.test.js +44 -0
  229. package/tests/karma/BookNavigator/downloads/downloads-provider.test.js +6 -3
  230. package/tests/karma/BookNavigator/search/search-provider.test.js +106 -6
  231. package/tests/karma/BookNavigator/search/search-results.test.js +0 -2
  232. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +29 -20
  233. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +46 -22
  234. package/webpack.config.js +1 -1
  235. package/src/BookNavigator/assets/book-loader.js +0 -27
  236. package/src/ItemNavigator/ItemNavigator.js +0 -377
package/src/BookReader.js CHANGED
@@ -178,7 +178,7 @@ BookReader.prototype.setup = function(options) {
178
178
  */
179
179
  this.firstIndex = null;
180
180
  this.lastDisplayableIndex2up = null;
181
- this.isFullscreenActive = false;
181
+ this.isFullscreenActive = options.startFullscreen || false;
182
182
  this.lastScroll = null;
183
183
 
184
184
  this.showLogo = options.showLogo;
@@ -486,7 +486,6 @@ BookReader.prototype.getInitialMode = function(params) {
486
486
  if ('undefined' != typeof(params.mode)) {
487
487
  nextMode = params.mode;
488
488
  } else if (this.ui == 'full'
489
- && this.enableMobileNav
490
489
  && this.isFullscreenActive
491
490
  && windowWidth <= this.onePageMinBreakpoint
492
491
  ) {
@@ -588,14 +587,16 @@ BookReader.prototype.init = function() {
588
587
  this.suppressFragmentChange = false;
589
588
  }
590
589
 
590
+ if (this.options.startFullscreen) {
591
+ this.enterFullscreen(true);
592
+ }
593
+
591
594
  this.init.initComplete = true;
592
595
  this.trigger(BookReader.eventNames.PostInit);
593
596
 
594
597
  // Must be called after this.init.initComplete set to true to allow
595
598
  // BookReader.prototype.resize to run.
596
- if (this.options.startFullscreen) {
597
- this.toggleFullscreen();
598
- }
599
+
599
600
  };
600
601
 
601
602
  /**
@@ -604,7 +605,6 @@ BookReader.prototype.init = function() {
604
605
  */
605
606
  BookReader.prototype.trigger = function(name, props = this) {
606
607
  const eventName = 'BookReader:' + name;
607
- $(document).trigger(eventName, props);
608
608
 
609
609
  utils.polyfillCustomEvent(window);
610
610
  window.dispatchEvent(new CustomEvent(eventName, {
@@ -612,6 +612,7 @@ BookReader.prototype.trigger = function(name, props = this) {
612
612
  composed: true,
613
613
  detail: { props },
614
614
  }));
615
+ $(document).trigger(eventName, props);
615
616
  };
616
617
 
617
618
  BookReader.prototype.bind = function(name, callback) {
@@ -1184,9 +1185,10 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
1184
1185
  }
1185
1186
 
1186
1187
  this.isFullscreenActive = true;
1188
+ // prioritize class updates so CSS can propagate
1189
+ this.updateBrClasses();
1187
1190
  this.animating = true;
1188
1191
  await new Promise(res => this.refs.$brContainer.animate({opacity: 1}, 'fast', 'linear', res));
1189
- this.resize();
1190
1192
  if (this.activeMode instanceof Mode1Up) {
1191
1193
  this.activeMode.mode1UpLit.scale = this.activeMode.mode1UpLit.computeDefaultScale(this._models.book.getPage(currentIndex));
1192
1194
  // Need the new scale to be applied before calling jumpToIndex
@@ -1198,7 +1200,14 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
1198
1200
  this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1199
1201
  // Add "?view=theater"
1200
1202
  this.trigger(BookReader.eventNames.fragmentChange);
1203
+ // trigger event here, so that animations,
1204
+ // class updates happen before book-nav relays to web components
1201
1205
  this.trigger(BookReader.eventNames.fullscreenToggled);
1206
+
1207
+ setTimeout(() => {
1208
+ // resize book after all events & css updates
1209
+ this.resize();
1210
+ }, 0);
1202
1211
  };
1203
1212
 
1204
1213
  /**
@@ -1221,6 +1230,10 @@ BookReader.prototype.exitFullScreen = async function () {
1221
1230
  }
1222
1231
 
1223
1232
  this.isFullscreenActive = false;
1233
+ // Trigger fullscreen event immediately
1234
+ // so that book-nav can relay to web components
1235
+ this.trigger(BookReader.eventNames.fullscreenToggled);
1236
+
1224
1237
  this.updateBrClasses();
1225
1238
  this.animating = true;
1226
1239
  await new Promise((res => this.refs.$brContainer.animate({opacity: 1}, 'fast', 'linear', res)));
@@ -1236,7 +1249,6 @@ BookReader.prototype.exitFullScreen = async function () {
1236
1249
  this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1237
1250
  // Remove "?view=theater"
1238
1251
  this.trigger(BookReader.eventNames.fragmentChange);
1239
- this.trigger(BookReader.eventNames.fullscreenToggled);
1240
1252
  };
1241
1253
 
1242
1254
  /**
@@ -4,59 +4,69 @@
4
4
 
5
5
  import { LitElement, html, css } from 'lit-element';
6
6
 
7
- import '../ItemNavigator/ItemNavigator.js';
8
- import '../BookNavigator/BookNavigator.js';
9
-
7
+ import '@internetarchive/ia-item-navigator';
8
+ import '../BookNavigator/book-navigator.js';
9
+ // eslint-disable-next-line no-unused-vars
10
+ import { ModalManager } from '@internetarchive/modal-manager';
11
+ import '@internetarchive/modal-manager';
12
+ import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
10
13
  export class BookReader extends LitElement {
11
14
  static get properties() {
12
15
  return {
13
- base64Json: { type: String },
16
+ item: { type: Object },
14
17
  baseHost: { type: String },
18
+ fullscreen: { type: Boolean, reflect: true, attribute: true },
19
+ sharedObserver: { type: Object }
15
20
  };
16
21
  }
17
22
 
18
23
  constructor() {
19
24
  super();
20
- this.base64Json = '';
25
+ this.item = undefined;
26
+ this.bookreader = undefined;
21
27
  this.baseHost = 'https://archive.org';
28
+ this.fullscreen = false;
29
+ /** @type {ModalManager} */
30
+ this.modal = undefined;
31
+ /** @type {SharedResizeObserver} */
32
+ this.sharedObserver = new SharedResizeObserver();
22
33
  }
23
34
 
24
35
  firstUpdated() {
25
- this.fetchData();
36
+ this.createModal();
26
37
  }
27
38
 
28
- /**
29
- * Fetch metadata response from public metadata API
30
- * convert response to base64 data
31
- * set base64 data to props
32
- */
33
- async fetchData() {
34
- const ocaid = new URLSearchParams(location.search).get('ocaid');
35
- const response = await fetch(`${this.baseHost}/metadata/${ocaid}`);
36
- const bookMetadata = await response.json();
37
- const jsonBtoa = btoa(JSON.stringify(bookMetadata));
38
- this.setBaseJSON(jsonBtoa);
39
+ /** Creates modal DOM & attaches to `<body>` */
40
+ createModal() {
41
+ this.modal = document.createElement(
42
+ 'modal-manager'
43
+ );
44
+ document.body.appendChild(this.modal);
39
45
  }
46
+ /* End Modal management */
40
47
 
41
- /**
42
- * Set base64 data to prop
43
- * @param {string} value - base64 string format
44
- */
45
- setBaseJSON(value) {
46
- this.base64Json = value;
48
+ manageFullscreen(e) {
49
+ const { detail } = e;
50
+ const fullscreen = !!detail.isFullScreen;
51
+ this.fullscreen = fullscreen;
47
52
  }
48
53
 
49
54
  render() {
50
55
  return html`
51
56
  <div class="ia-bookreader">
52
- <item-navigator
53
- itemType="bookreader"
54
- basehost=${this.baseHost}
55
- item=${this.base64Json}>
56
- <div slot="bookreader">
57
- <slot name="bookreader"></slot>
57
+ <ia-item-navigator
58
+ ?viewportInFullscreen=${this.fullscreen}
59
+ @fullscreenToggled=${this.manageFullscreen}
60
+ .itemType=${'bookreader'}
61
+ .basehost=${this.baseHost}
62
+ .item=${this.item}
63
+ .modal=${this.modal}
64
+ .sharedObserver=${this.sharedObserver}
65
+ >
66
+ <div slot="theater-main">
67
+ <slot name="theater-main"></slot>
58
68
  </div>
59
- </item-navigator>
69
+ </ia-item-navigator>
60
70
  </div>
61
71
  `;
62
72
  }
@@ -77,13 +87,24 @@ export class BookReader extends LitElement {
77
87
  --primaryErrorCTABorder: #f8c6c8;
78
88
  }
79
89
 
90
+ :host([fullscreen]),
91
+ ia-item-navigator[viewportinfullscreen] {
92
+ position: fixed;
93
+ inset: 0;
94
+ height: 100vh;
95
+ min-height: unset;
96
+ }
97
+
80
98
  .ia-bookreader {
81
99
  background-color: var(--primaryBGColor);
82
100
  position: relative;
83
- height: auto;
101
+ min-height: inherit;
102
+ height: inherit;
84
103
  }
85
104
 
86
- item-navigator {
105
+ ia-item-navigator {
106
+ min-height: var(--br-height, inherit);
107
+ height: var(--br-height, inherit);
87
108
  display: block;
88
109
  width: 100%;
89
110
  color: var(--primaryTextColor);
@@ -2,7 +2,7 @@
2
2
  * Hide modal-manager that loads item-navigator-modal
3
3
  * loading bookmarks related alert messages
4
4
  */
5
- #item-navigator-modal {
5
+ modal-manager[mode="closed"] {
6
6
  display: none
7
7
  }
8
8
 
@@ -61,14 +61,7 @@ BookReader.prototype.setup = (function (super_) {
61
61
  /** @type { {[pageIndex: number]: SearchInsideMatchBox[]} } */
62
62
  this._searchBoxesByIndex = {};
63
63
 
64
- if (this.searchView) { return; }
65
- this.searchView = new SearchView({
66
- br: this,
67
- searchCancelledCallback: () => {
68
- this._cancelSearch();
69
- this.trigger('SearchCanceled', { term: this.searchTerm, instance: this });
70
- }
71
- });
64
+ this.searchView = undefined;
72
65
  };
73
66
  })(BookReader.prototype.setup);
74
67
 
@@ -76,7 +69,14 @@ BookReader.prototype.setup = (function (super_) {
76
69
  BookReader.prototype.init = (function (super_) {
77
70
  return function () {
78
71
  super_.call(this);
79
-
72
+ // give SearchView the most complete bookreader state
73
+ this.searchView = new SearchView({
74
+ br: this,
75
+ searchCancelledCallback: () => {
76
+ this._cancelSearch();
77
+ this.trigger('SearchCanceled', { term: this.searchTerm, instance: this });
78
+ }
79
+ });
80
80
  if (this.options.enableSearch && this.options.initialSearchTerm) {
81
81
  /**
82
82
  * this.search() take two parameter
@@ -213,6 +213,7 @@ BookReader.prototype.search = function(term = '', overrides = {}) {
213
213
  return $.ajax({
214
214
  url: url,
215
215
  dataType: 'jsonp',
216
+ cache: true,
216
217
  beforeSend,
217
218
  jsonpCallback: 'BRSearchInProgress'
218
219
  }).then(processSearchResults);
@@ -23,7 +23,7 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
23
23
  // $.browsers is sometimes undefined on some Android browsers :/
24
24
  // Likely related to when $.browser was moved to npm
25
25
  /** @type {'mp3' | 'ogg'} format of audio to get */
26
- this.audioFormat = $.browser?.mozilla ? 'ogg' : 'mp3';
26
+ this.audioFormat = $.browser?.mozilla ? 'ogg' : 'mp3'; //eslint-disable-line no-jquery/no-browser
27
27
  }
28
28
 
29
29
  /** @override */
@@ -0,0 +1,184 @@
1
+ export class UrlPlugin {
2
+ constructor(options = {}) {
3
+ this.bookReaderOptions = options;
4
+
5
+ // the canonical order of elements is important in the path and query string
6
+ this.urlSchema = [
7
+ { name: 'page', position: 'path', default: 'n0' },
8
+ { name: 'mode', position: 'path', default: '2up' },
9
+ { name: 'search', position: 'path', deprecated_for: 'q' },
10
+ { name: 'q', position: 'query_param' },
11
+ { name: 'sort', position: 'query_param' },
12
+ { name: 'view', position: 'query_param' },
13
+ { name: 'admin', position: 'query_param' },
14
+ ];
15
+
16
+ this.urlState = {};
17
+ this.urlMode = this.bookReaderOptions.urlMode || 'hash';
18
+ this.urlHistoryBasePath = this.bookReaderOptions.urlHistoryBasePath || '/';
19
+ this.urlLocationPollId = null;
20
+ this.oldLocationHash = null;
21
+ this.oldUserHash = null;
22
+ }
23
+
24
+ /**
25
+ * Parse JSON object URL state to string format
26
+ * Arrange path names in an order that it is positioned on the urlSchema
27
+ * @param {Object} urlState
28
+ * @returns {string}
29
+ */
30
+ urlStateToUrlString(urlState) {
31
+ const searchParams = new URLSearchParams();
32
+ const pathParams = {};
33
+
34
+ Object.keys(urlState).forEach(key => {
35
+ let schema = this.urlSchema.find(schema => schema.name === key);
36
+ if (schema?.deprecated_for) {
37
+ schema = this.urlSchema.find(schemaKey => schemaKey.name === schema.deprecated_for);
38
+ }
39
+ if (schema?.position == 'path') {
40
+ pathParams[schema?.name] = urlState[key];
41
+ } else {
42
+ searchParams.append(schema?.name || key, urlState[key]);
43
+ }
44
+ });
45
+
46
+ const strPathParams = this.urlSchema
47
+ .filter(s => s.position == 'path')
48
+ .map(schema => pathParams[schema.name] ? `${schema.name}/${pathParams[schema.name]}` : '')
49
+ .join('/');
50
+
51
+ // replace consecutive slashes with a single slash + remove trailing slashes
52
+ const strStrippedTrailingSlash = `${strPathParams.replace(/\/+/g, '/').replace(/\/+$/, '')}`;
53
+ const concatenatedPath = `${strStrippedTrailingSlash}?${searchParams.toString()}`;
54
+ return searchParams.toString() ? concatenatedPath : `${strStrippedTrailingSlash}`;
55
+ }
56
+
57
+ /**
58
+ * Parse string URL and add it in the current urlState
59
+ * Example:
60
+ * /page/n7/mode/2up => {page: 'n7', mode: '2up'}
61
+ * /page/n7/mode/2up/search/hello => {page: 'n7', mode: '2up', q: 'hello'}
62
+ * @param {string} urlString
63
+ * @returns {object}
64
+ */
65
+ urlStringToUrlState(urlString) {
66
+ const urlState = {};
67
+
68
+ // Fetch searchParams from given {str}
69
+ // Note: whole URL path is needed for URL parsing
70
+ const urlPath = new URL(urlString, 'http://example.com');
71
+ const urlSearchParamsObj = Object.fromEntries(urlPath.searchParams.entries());
72
+ const splitUrlMatches = urlPath.pathname.match(/[^\\/]+\/[^\\/]+/g);
73
+ const urlStrSplitSlashObj = splitUrlMatches ? Object.fromEntries(splitUrlMatches.map(x => x.split('/'))) : {};
74
+
75
+ const doesKeyExists = (_object, _key) => {
76
+ return Object.keys(_object).some(value => value == _key);
77
+ };
78
+
79
+ // Add path objects to urlState
80
+ this.urlSchema
81
+ .filter(schema => schema.position == 'path')
82
+ .forEach(schema => {
83
+ const hasPropertyKey = doesKeyExists(urlStrSplitSlashObj, schema.name);
84
+ const hasDeprecatedKey = doesKeyExists(schema, 'deprecated_for') && hasPropertyKey;
85
+
86
+ if (hasDeprecatedKey) {
87
+ urlState[schema.deprecated_for] = urlStrSplitSlashObj[schema.name];
88
+ return;
89
+ }
90
+
91
+ if (hasPropertyKey) {
92
+ urlState[schema.name] = urlStrSplitSlashObj[schema.name];
93
+ return;
94
+ }
95
+ });
96
+
97
+ // Add searchParams to urlState
98
+ Object.entries(urlSearchParamsObj).forEach(([key, value]) => {
99
+ urlState[key] = value;
100
+ });
101
+
102
+ return urlState;
103
+ }
104
+
105
+ /**
106
+ * Add or update key-value to the urlState
107
+ * @param {string} key
108
+ * @param {string} val
109
+ */
110
+ setUrlParam(key, value) {
111
+ this.urlState[key] = value;
112
+
113
+ this.pushToAddressBar();
114
+ }
115
+
116
+ /**
117
+ * Delete key-value to the urlState
118
+ * @param {string} key
119
+ */
120
+ removeUrlParam(key) {
121
+ delete this.urlState[key];
122
+
123
+ this.pushToAddressBar();
124
+ }
125
+
126
+ /**
127
+ * Get key-value from the urlState
128
+ * @param {string} key
129
+ * @return {string}
130
+ */
131
+ getUrlParam(key) {
132
+ return this.urlState[key];
133
+ }
134
+
135
+ /**
136
+ * Push URL params to addressbar
137
+ */
138
+ pushToAddressBar() {
139
+ const urlStrPath = this.urlStateToUrlString(this.urlState);
140
+ const concatenatedPath = urlStrPath !== '/' ? urlStrPath : '';
141
+ if (this.urlMode == 'history') {
142
+ if (window.history && window.history.replaceState) {
143
+ const newUrlPath = `${this.urlHistoryBasePath}${concatenatedPath}`;
144
+ window.history.replaceState({}, null, newUrlPath);
145
+ }
146
+ } else {
147
+ window.location.replace('#' + concatenatedPath);
148
+ }
149
+ this.oldLocationHash = urlStrPath;
150
+ }
151
+
152
+ /**
153
+ * Get the url and check if it has changed
154
+ * If it was changeed, update the urlState
155
+ */
156
+ listenForHashChanges() {
157
+ this.oldLocationHash = window.location.hash.substr(1);
158
+ if (this.urlLocationPollId) {
159
+ clearInterval(this.urlLocationPollId);
160
+ this.urlLocationPollId = null;
161
+ }
162
+
163
+ // check if the URL changes
164
+ const updateHash = () => {
165
+ const newFragment = window.location.hash.substr(1);
166
+ const hasFragmentChange = newFragment != this.oldLocationHash;
167
+
168
+ if (!hasFragmentChange) { return; }
169
+
170
+ this.urlState = this.urlStringToUrlState(newFragment);
171
+ };
172
+ this.urlLocationPollId = setInterval(updateHash, 500);
173
+ }
174
+
175
+ /**
176
+ * Will read either the hash or URL and return the bookreader fragment
177
+ */
178
+ pullFromAddressBar (location = window.location) {
179
+ const path = this.urlMode === 'history'
180
+ ? (location.pathname.substr(this.urlHistoryBasePath.length) + location.search)
181
+ : location.hash.substr(1);
182
+ this.urlState = this.urlStringToUrlState(path);
183
+ }
184
+ }
@@ -0,0 +1,220 @@
1
+ /* global BookReader */
2
+
3
+ import { UrlPlugin } from "./UrlPlugin";
4
+
5
+ /**
6
+ * Plugin for URL management in BookReader
7
+ * Note read more about the url "fragment" here:
8
+ * https://openlibrary.org/dev/docs/bookurls
9
+ */
10
+
11
+ jQuery.extend(BookReader.defaultOptions, {
12
+ enableUrlPlugin: true,
13
+ bookId: '',
14
+ /** @type {string} Defaults can be a urlFragment string */
15
+ defaults: null,
16
+ updateWindowTitle: false,
17
+
18
+ /** @type {'history' | 'hash'} */
19
+ urlMode: 'hash',
20
+
21
+ /**
22
+ * When using 'history' mode, this part of the URL is kept constant
23
+ * @example /details/plato/
24
+ */
25
+ urlHistoryBasePath: '/',
26
+
27
+ /** Only these params will be reflected onto the URL */
28
+ urlTrackedParams: ['page', 'search', 'mode', 'region', 'highlight', 'view'],
29
+
30
+ /** If true, don't update the URL when `page == n0 (eg "/page/n0")` */
31
+ urlTrackIndex0: false,
32
+ });
33
+
34
+ /** @override */
35
+ BookReader.prototype.setup = (function(super_) {
36
+ return function(options) {
37
+ super_.call(this, options);
38
+
39
+ this.bookId = options.bookId;
40
+ this.defaults = options.defaults;
41
+
42
+ this.locationPollId = null;
43
+ this.oldLocationHash = null;
44
+ this.oldUserHash = null;
45
+ };
46
+ })(BookReader.prototype.setup);
47
+
48
+ /** @override */
49
+ BookReader.prototype.init = (function(super_) {
50
+ return function() {
51
+
52
+ if (this.options.enableUrlPlugin) {
53
+ this.bind(BookReader.eventNames.PostInit, () => {
54
+ const { updateWindowTitle, urlMode } = this.options;
55
+ if (updateWindowTitle) {
56
+ document.title = this.shortTitle(this.bookTitle, 50);
57
+ }
58
+ if (urlMode === 'hash') {
59
+ this.urlStartLocationPolling();
60
+ }
61
+ });
62
+
63
+ this.bind(BookReader.eventNames.fragmentChange,
64
+ this.urlUpdateFragment.bind(this)
65
+ );
66
+ }
67
+ super_.call(this);
68
+ };
69
+ })(BookReader.prototype.init);
70
+
71
+ /**
72
+ * Returns a shortened version of the title with the maximum number of characters
73
+ * @param {number} maximumCharacters
74
+ * @return {string}
75
+ */
76
+ BookReader.prototype.shortTitle = function(maximumCharacters) {
77
+ if (this.bookTitle.length < maximumCharacters) {
78
+ return this.bookTitle;
79
+ }
80
+
81
+ const title = `${this.bookTitle.substr(0, maximumCharacters - 3)}...`;
82
+ return title;
83
+ };
84
+
85
+ /**
86
+ * Starts polling of window.location to see hash fragment changes
87
+ */
88
+ BookReader.prototype.urlStartLocationPolling = function() {
89
+ this.oldLocationHash = this.urlReadFragment();
90
+
91
+ if (this.locationPollId) {
92
+ clearInterval(this.locationPollId);
93
+ this.locationPollId = null;
94
+ }
95
+
96
+ const updateHash = () => {
97
+ const newFragment = this.urlReadFragment();
98
+ const hasFragmentChange = (newFragment != this.oldLocationHash) && (newFragment != this.oldUserHash);
99
+
100
+ if (!hasFragmentChange) { return; }
101
+
102
+ const params = this.paramsFromFragment(newFragment);
103
+
104
+ const updateParams = () => this.updateFromParams(params);
105
+
106
+ this.trigger(BookReader.eventNames.stop);
107
+ if (this.animating) {
108
+ // Queue change if animating
109
+ if (this.autoStop) this.autoStop();
110
+ this.animationFinishedCallback = updateParams;
111
+ } else {
112
+ // update immediately
113
+ updateParams();
114
+ }
115
+ this.oldUserHash = newFragment;
116
+ };
117
+
118
+ this.locationPollId = setInterval(updateHash, 500);
119
+ };
120
+
121
+ /**
122
+ * Update URL from the current parameters.
123
+ * Call this instead of manually using window.location.replace
124
+ */
125
+ BookReader.prototype.urlUpdateFragment = function() {
126
+ const allParams = this.paramsFromCurrent();
127
+ const { urlMode, urlTrackIndex0, urlTrackedParams } = this.options;
128
+
129
+ if (!urlTrackIndex0
130
+ && (typeof(allParams.index) !== 'undefined')
131
+ && allParams.index === 0) {
132
+ delete allParams.index;
133
+ delete allParams.page;
134
+ }
135
+
136
+ const params = urlTrackedParams.reduce((validParams, paramName) => {
137
+ if (paramName in allParams) {
138
+ validParams[paramName] = allParams[paramName];
139
+ }
140
+ return validParams;
141
+ }, {});
142
+
143
+ const newFragment = this.fragmentFromParams(params, urlMode);
144
+ const currFragment = this.urlReadFragment();
145
+ const currQueryString = this.getLocationSearch();
146
+ const newQueryString = this.queryStringFromParams(params, currQueryString, urlMode);
147
+ if (currFragment === newFragment && currQueryString === newQueryString) {
148
+ return;
149
+ }
150
+
151
+ if (urlMode === 'history') {
152
+ if (window.history && window.history.replaceState) {
153
+ const baseWithoutSlash = this.options.urlHistoryBasePath.replace(/\/+$/, '');
154
+ const newFragmentWithSlash = newFragment === '' ? '' : `/${newFragment}`;
155
+
156
+ const newUrlPath = `${baseWithoutSlash}${newFragmentWithSlash}${newQueryString}`;
157
+ window.history.replaceState({}, null, newUrlPath);
158
+ this.oldLocationHash = newFragment + newQueryString;
159
+
160
+ }
161
+ } else {
162
+ const newQueryStringSearch = this.urlParamsFiltersOnlySearch(this.readQueryString());
163
+ window.location.replace('#' + newFragment + newQueryStringSearch);
164
+ this.oldLocationHash = newFragment + newQueryStringSearch;
165
+
166
+ }
167
+ };
168
+
169
+ /**
170
+ * @private
171
+ * Filtering query parameters to select only book search param (?q=foo)
172
+ This needs to be updated/URL system modified if future query params are to be added
173
+ * @param {string} url
174
+ * @return {string}
175
+ * */
176
+ BookReader.prototype.urlParamsFiltersOnlySearch = function(url) {
177
+ const params = new URLSearchParams(url);
178
+ return params.has('q') ? `?${new URLSearchParams({ q: params.get('q') })}` : '';
179
+ };
180
+
181
+
182
+ /**
183
+ * Will read either the hash or URL and return the bookreader fragment
184
+ * @return {string}
185
+ */
186
+ BookReader.prototype.urlReadFragment = function() {
187
+ const { urlMode, urlHistoryBasePath } = this.options;
188
+ if (urlMode === 'history') {
189
+ return window.location.pathname.substr(urlHistoryBasePath.length);
190
+ } else {
191
+ return window.location.hash.substr(1);
192
+ }
193
+ };
194
+
195
+ /**
196
+ * Will read the hash return the bookreader fragment
197
+ * @return {string}
198
+ */
199
+ BookReader.prototype.urlReadHashFragment = function() {
200
+ return window.location.hash.substr(1);
201
+ };
202
+ export class BookreaderUrlPlugin extends BookReader {
203
+ init() {
204
+ if (this.options.enableUrlPlugin) {
205
+ this.urlPlugin = new UrlPlugin(this.options);
206
+ this.bind(BookReader.eventNames.PostInit, () => {
207
+ const { urlMode } = this.options;
208
+
209
+ if (urlMode === 'hash') {
210
+ this.urlPlugin.listenForHashChanges();
211
+ }
212
+ });
213
+ }
214
+
215
+ super.init();
216
+ }
217
+ }
218
+
219
+ window.BookReader = BookreaderUrlPlugin;
220
+ export default BookreaderUrlPlugin;
File without changes