@internetarchive/bookreader 5.0.0-5 → 5.0.0-50-a1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +77 -6
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +131 -339
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +24 -0
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1493 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +17 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/close-circle-dark.svg +1 -0
  13. package/BookReader/icons/magnify-minus.svg +1 -1
  14. package/BookReader/icons/magnify-plus.svg +1 -1
  15. package/BookReader/icons/pause.svg +1 -1
  16. package/BookReader/icons/playback-speed.svg +1 -1
  17. package/BookReader/icons/read-aloud.svg +1 -1
  18. package/BookReader/icons/voice.svg +1 -0
  19. package/BookReader/images/BRicons.svg +2 -2
  20. package/BookReader/images/books_graphic.svg +1 -1
  21. package/BookReader/images/icon_book.svg +1 -1
  22. package/BookReader/images/icon_gear.svg +1 -1
  23. package/BookReader/images/icon_info.svg +1 -1
  24. package/BookReader/images/icon_playback-rate.svg +1 -1
  25. package/BookReader/images/icon_search_button.svg +1 -1
  26. package/BookReader/images/icon_share.svg +1 -1
  27. package/BookReader/images/icon_speaker.svg +1 -1
  28. package/BookReader/images/icon_speaker_open.svg +1 -1
  29. package/BookReader/images/marker_chap-off.svg +1 -1
  30. package/BookReader/images/marker_chap-on.svg +1 -1
  31. package/BookReader/images/marker_srch-on.svg +1 -1
  32. package/BookReader/jquery-3.js +2 -0
  33. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  34. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  35. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  36. package/BookReader/plugins/plugin.autoplay.js +1 -1
  37. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  38. package/BookReader/plugins/plugin.chapters.js +1 -1
  39. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  40. package/BookReader/plugins/plugin.iframe.js +1 -1
  41. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  42. package/BookReader/plugins/plugin.mobile_nav.js +1 -1
  43. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  44. package/BookReader/plugins/plugin.resume.js +1 -1
  45. package/BookReader/plugins/plugin.resume.js.map +1 -1
  46. package/BookReader/plugins/plugin.search.js +1 -1
  47. package/BookReader/plugins/plugin.search.js.map +1 -1
  48. package/BookReader/plugins/plugin.text_selection.js +1 -1
  49. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  50. package/BookReader/plugins/plugin.tts.js +1 -1
  51. package/BookReader/plugins/plugin.tts.js.map +1 -1
  52. package/BookReader/plugins/plugin.url.js +1 -1
  53. package/BookReader/plugins/plugin.url.js.map +1 -1
  54. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  55. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  56. package/BookReader/webcomponents-bundle.js +3 -0
  57. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  58. package/BookReader/webcomponents-bundle.js.map +1 -0
  59. package/BookReaderDemo/BookReaderDemo.css +14 -1
  60. package/BookReaderDemo/IADemoBr.js +148 -0
  61. package/BookReaderDemo/demo-advanced.html +2 -2
  62. package/BookReaderDemo/demo-autoplay.html +2 -1
  63. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  64. package/BookReaderDemo/demo-fullscreen-mobile.html +2 -1
  65. package/BookReaderDemo/demo-fullscreen.html +2 -1
  66. package/BookReaderDemo/demo-iiif.html +2 -1
  67. package/BookReaderDemo/demo-internetarchive.html +84 -17
  68. package/BookReaderDemo/demo-multiple.html +2 -1
  69. package/BookReaderDemo/demo-preview-pages.html +2 -1
  70. package/BookReaderDemo/demo-simple.html +2 -1
  71. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -1
  72. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  73. package/BookReaderDemo/immersion-1up.html +2 -1
  74. package/BookReaderDemo/immersion-mode.html +2 -1
  75. package/BookReaderDemo/toggle_controls.html +2 -1
  76. package/BookReaderDemo/view_mode.html +2 -1
  77. package/BookReaderDemo/viewmode-cycle.html +2 -3
  78. package/CHANGELOG.md +202 -0
  79. package/README.md +14 -1
  80. package/babel.config.js +18 -0
  81. package/codecov.yml +6 -0
  82. package/index.html +3 -0
  83. package/jsconfig.json +19 -0
  84. package/package.json +66 -56
  85. package/renovate.json +52 -0
  86. package/scripts/preversion.js +4 -1
  87. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  88. package/src/BookNavigator/assets/button-base.js +9 -2
  89. package/src/BookNavigator/assets/ia-logo.js +17 -0
  90. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  91. package/src/BookNavigator/assets/icon_close.js +1 -1
  92. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  93. package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
  94. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  95. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  96. package/src/BookNavigator/book-navigator.js +583 -0
  97. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  98. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  99. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  100. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  101. package/src/BookNavigator/bookmarks/bookmarks-provider.js +21 -8
  102. package/src/BookNavigator/bookmarks/ia-bookmarks.js +102 -66
  103. package/src/BookNavigator/delete-modal-actions.js +1 -1
  104. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  105. package/src/BookNavigator/downloads/downloads.js +41 -25
  106. package/src/BookNavigator/search/a-search-result.js +18 -13
  107. package/src/BookNavigator/search/search-provider.js +80 -28
  108. package/src/BookNavigator/search/search-results.js +10 -18
  109. package/src/BookNavigator/sharing.js +27 -0
  110. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  111. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  112. package/src/BookNavigator/volumes/volumes-provider.js +114 -0
  113. package/src/BookNavigator/volumes/volumes.js +188 -0
  114. package/src/BookReader/BookModel.js +0 -29
  115. package/src/BookReader/DebugConsole.js +3 -3
  116. package/src/BookReader/DragScrollable.js +233 -0
  117. package/src/BookReader/Mode1Up.js +51 -351
  118. package/src/BookReader/Mode1UpLit.js +441 -0
  119. package/src/BookReader/Mode2Up.js +120 -105
  120. package/src/BookReader/ModeSmoothZoom.js +179 -0
  121. package/src/BookReader/ModeThumb.js +17 -11
  122. package/src/BookReader/Navbar/Navbar.js +10 -36
  123. package/src/BookReader/PageContainer.js +69 -6
  124. package/src/BookReader/ReduceSet.js +1 -1
  125. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  126. package/src/BookReader/options.js +10 -0
  127. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  128. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  129. package/src/BookReader/utils.js +92 -13
  130. package/src/BookReader.js +431 -620
  131. package/src/assets/icons/close-circle-dark.svg +1 -0
  132. package/src/assets/icons/magnify-minus.svg +3 -7
  133. package/src/assets/icons/magnify-plus.svg +3 -7
  134. package/src/assets/icons/voice.svg +1 -0
  135. package/src/css/BookReader.scss +0 -12
  136. package/src/css/_BRComponent.scss +1 -1
  137. package/src/css/_BRmain.scss +19 -24
  138. package/src/css/_BRnav.scss +4 -26
  139. package/src/css/_BRpages.scss +35 -0
  140. package/src/css/_BRsearch.scss +25 -216
  141. package/src/css/_TextSelection.scss +14 -17
  142. package/src/css/_colorbox.scss +2 -2
  143. package/src/css/_controls.scss +17 -5
  144. package/src/css/_icons.scss +6 -0
  145. package/src/ia-bookreader/ia-bookreader.js +224 -0
  146. package/src/plugins/plugin.autoplay.js +4 -4
  147. package/src/plugins/plugin.chapters.js +28 -35
  148. package/src/plugins/plugin.mobile_nav.js +11 -10
  149. package/src/plugins/plugin.resume.js +3 -3
  150. package/src/plugins/plugin.text_selection.js +26 -39
  151. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  152. package/src/plugins/search/plugin.search.js +174 -116
  153. package/src/plugins/search/view.js +63 -179
  154. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  155. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  156. package/src/plugins/tts/PageChunk.js +15 -21
  157. package/src/plugins/tts/PageChunkIterator.js +8 -12
  158. package/src/plugins/tts/WebTTSEngine.js +66 -69
  159. package/src/plugins/tts/plugin.tts.js +92 -109
  160. package/src/plugins/tts/utils.js +0 -9
  161. package/src/plugins/url/UrlPlugin.js +184 -0
  162. package/src/plugins/{plugin.url.js → url/plugin.url.js} +28 -6
  163. package/src/util/manifestGenerator.js +0 -0
  164. package/tests/e2e/README.md +37 -0
  165. package/tests/e2e/autoplay.test.js +2 -2
  166. package/tests/e2e/base.test.js +7 -7
  167. package/tests/e2e/helpers/base.js +9 -3
  168. package/tests/e2e/helpers/debug.js +1 -1
  169. package/tests/e2e/helpers/desktopSearch.js +14 -13
  170. package/tests/e2e/helpers/mobileSearch.js +3 -3
  171. package/tests/e2e/helpers/params.js +17 -0
  172. package/tests/e2e/models/Navigation.js +13 -4
  173. package/tests/e2e/rightToLeft.test.js +4 -5
  174. package/tests/e2e/viewmode.test.js +38 -33
  175. package/tests/jest/BookNavigator/book-navigator.test.js +634 -0
  176. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  177. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  178. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  179. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  180. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  181. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  182. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  183. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +102 -58
  184. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  185. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  186. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +184 -0
  187. package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
  188. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +34 -14
  189. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +176 -0
  190. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +1 -1
  191. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  192. package/tests/jest/BookReader/Mode1UpLit.test.js +92 -0
  193. package/tests/{BookReader → jest/BookReader}/Mode2Up.test.js +36 -15
  194. package/tests/jest/BookReader/ModeSmoothZoom.test.js +149 -0
  195. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  196. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +7 -7
  197. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  198. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  199. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  200. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  201. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  202. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  203. package/tests/jest/BookReader/utils.test.js +186 -0
  204. package/tests/jest/BookReader.keyboard.test.js +190 -0
  205. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  206. package/tests/{BookReader.test.js → jest/BookReader.test.js} +18 -37
  207. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  208. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  209. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +10 -11
  210. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  211. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +5 -5
  212. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  213. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +39 -47
  214. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  215. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +63 -47
  216. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +35 -6
  217. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +9 -9
  218. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  219. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  220. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  221. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +1 -1
  222. package/tests/{plugins → jest/plugins}/tts/utils.test.js +3 -28
  223. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  224. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +33 -14
  225. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  226. package/tests/{util → jest/util}/docCookies.test.js +1 -1
  227. package/tests/{util → jest/util}/strings.test.js +1 -1
  228. package/tests/{utils.js → jest/utils.js} +38 -0
  229. package/webpack.config.js +11 -5
  230. package/.babelrc +0 -12
  231. package/.dependabot/config.yml +0 -6
  232. package/.testcaferc.json +0 -5
  233. package/BookReader/bookreader-component-bundle.js +0 -1450
  234. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  235. package/BookReader/bookreader-component-bundle.js.map +0 -1
  236. package/BookReader/jquery-1.10.1.js +0 -2
  237. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  238. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  239. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  240. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  241. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  242. package/karma.conf.js +0 -23
  243. package/src/BookNavigator/BookModel.js +0 -14
  244. package/src/BookNavigator/BookNavigator.js +0 -438
  245. package/src/BookNavigator/assets/book-loader.js +0 -27
  246. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  247. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  248. package/src/ItemNavigator/ItemNavigator.js +0 -372
  249. package/src/ItemNavigator/providers/sharing.js +0 -29
  250. package/src/Layers/sharing/sharing-provider.js +0 -22
  251. package/src/dragscrollable-br.js +0 -261
  252. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  253. package/src/plugins/plugin.bookmarks.js +0 -50
  254. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  255. package/tests/BookReader/Mode1Up.test.js +0 -164
  256. package/tests/BookReader/utils.test.js +0 -109
  257. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  258. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  259. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  260. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
@@ -4,6 +4,7 @@ import 'jquery-ui/ui/widget.js';
4
4
  import 'jquery-ui/ui/widgets/mouse.js';
5
5
  import 'jquery-ui/ui/widgets/slider.js';
6
6
  import { EVENTS } from '../events.js';
7
+ import { throttle } from '../utils.js';
7
8
 
8
9
  export class Navbar {
9
10
  /**
@@ -27,6 +28,8 @@ export class Navbar {
27
28
  this.maximumControls = [
28
29
  'book_left', 'book_right', 'zoom_in', 'zoom_out', 'onepg', 'twopg', 'thumb'
29
30
  ];
31
+
32
+ this.updateNavIndexThrottled = throttle(this.updateNavIndex.bind(this), 250, false);
30
33
  }
31
34
 
32
35
  controlFor(controlName) {
@@ -213,7 +216,7 @@ export class Navbar {
213
216
  const $slider = this.$root.find('.BRpager').slider({
214
217
  animate: true,
215
218
  min: 0,
216
- max: br.getNumLeafs() - 1,
219
+ max: br.book.getNumLeafs() - 1,
217
220
  value: br.currentIndex(),
218
221
  range: "min"
219
222
  });
@@ -240,35 +243,6 @@ export class Navbar {
240
243
  return this.$nav;
241
244
  }
242
245
 
243
- /**
244
- * Initialize the navigation bar when embedded
245
- */
246
- initEmbed() {
247
- const { br } = this;
248
- // IA-specific
249
- const thisLink = (window.location + '')
250
- .replace('?ui=embed','')
251
- .replace('/stream/', '/details/')
252
- .replace('#', '/');
253
- const logoHtml = br.showLogo ? `<a class="logo" href="${br.logoURL}" target="_blank"></a>` : '';
254
-
255
- br.refs.$BRfooter = this.$root = $('<div class="BRfooter"></div>');
256
- br.refs.$BRnav = this.$nav = $(
257
- `<div class="BRnav BRnavEmbed">
258
- ${logoHtml}
259
- <span class="BRembedreturn">
260
- <a href="${thisLink}" target="_blank">${br.bookTitle}</a>
261
- </span>
262
- <span class="BRtoolbarbuttons">
263
- <button class="BRicon book_left"></button>
264
- <button class="BRicon book_right"></button>
265
- <button class="BRicon full"></button>
266
- </span>
267
- </div>`);
268
- this.$root.append(this.$nav);
269
- br.refs.$br.append(this.$root);
270
- }
271
-
272
246
  /**
273
247
  * Returns the textual representation of the current page for the navbar
274
248
  * @param {number} index
@@ -277,16 +251,16 @@ export class Navbar {
277
251
  getNavPageNumString(index) {
278
252
  const { br } = this;
279
253
  // Accessible index starts at 0 (alas) so we add 1 to make human
280
- const pageNum = br.getPageNum(index);
281
- const pageType = br.getPageProp(index, 'pageType');
282
- const numLeafs = br.getNumLeafs();
254
+ const pageNum = br.book.getPageNum(index);
255
+ const pageType = br.book.getPageProp(index, 'pageType');
256
+ const numLeafs = br.book.getNumLeafs();
283
257
 
284
258
  if (!this.maxPageNum) {
285
259
  // Calculate Max page num (used for pagination display)
286
260
  let maxPageNum = 0;
287
261
  let pageNumVal;
288
262
  for (let i = 0; i < numLeafs; i++) {
289
- pageNumVal = br.getPageNum(i);
263
+ pageNumVal = br.book.getPageNum(i);
290
264
  if (!isNaN(pageNumVal) && pageNumVal > maxPageNum) {
291
265
  maxPageNum = pageNumVal;
292
266
  }
@@ -331,9 +305,9 @@ export function getNavPageNumHtml(index, numLeafs, pageNum, pageType, maxPageNum
331
305
 
332
306
  if (!pageIsAsserted) {
333
307
  const pageIndex = index + 1;
334
- return `Page (${pageIndex} of ${numLeafs})`; // Page (8 of 10)
308
+ return `(${pageIndex} of ${numLeafs})`; // Page (8 of 10)
335
309
  }
336
310
 
337
311
  const bookLengthLabel = maxPageNum ? ` of ${maxPageNum}` : '';
338
- return `Page ${pageNum}${bookLengthLabel}`;
312
+ return `${pageNum}${bookLengthLabel}`;
339
313
  }
@@ -46,11 +46,17 @@ export class PageContainer {
46
46
  const alreadyLoaded = this.imageCache.imageLoaded(this.page.index, reduce);
47
47
  const nextBestLoadedReduce = !alreadyLoaded && this.imageCache.getBestLoadedReduce(this.page.index, reduce);
48
48
 
49
- // Add the actual, highres image
49
+ // Create high res image
50
+ const $newImg = this.imageCache.image(this.page.index, reduce);
51
+
52
+ // Avoid removing/re-adding the image if it's already there
53
+ // This can be called quite a bit, so we need to be fast
54
+ if (this.$img?.[0].src == $newImg[0].src) {
55
+ return this;
56
+ }
57
+
50
58
  this.$img?.remove();
51
- this.$img = this.imageCache
52
- .image(this.page.index, reduce)
53
- .prependTo(this.$container);
59
+ this.$img = $newImg.prependTo(this.$container);
54
60
 
55
61
  const backgroundLayers = [];
56
62
  if (!alreadyLoaded) {
@@ -58,14 +64,14 @@ export class PageContainer {
58
64
  backgroundLayers.push(`url("${this.loadingImage}") center/20px no-repeat`);
59
65
  }
60
66
  if (nextBestLoadedReduce) {
61
- backgroundLayers.push(`url("${this.page.getURI(nextBestLoadedReduce, 0)}") center/100% no-repeat`);
67
+ backgroundLayers.push(`url("${this.page.getURI(nextBestLoadedReduce, 0)}") center/100% 100% no-repeat`);
62
68
  }
63
69
 
64
70
  if (!alreadyLoaded) {
65
71
  this.$img
66
72
  .css('background', backgroundLayers.join(','))
67
73
  .one('loadend', async (ev) => {
68
- $(ev.target).css({ 'background': '' })
74
+ $(ev.target).css({ 'background': '' });
69
75
  $(ev.target).parent().removeClass('BRpageloading');
70
76
  });
71
77
  }
@@ -73,3 +79,60 @@ export class PageContainer {
73
79
  return this;
74
80
  }
75
81
  }
82
+
83
+
84
+ /**
85
+ * @param {PageModel} page
86
+ * @param {string} className
87
+ */
88
+ export function createSVGPageLayer(page, className) {
89
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
90
+ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
91
+ svg.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
92
+ svg.setAttribute('class', `BRPageLayer ${className}`);
93
+ svg.setAttribute('preserveAspectRatio', 'none');
94
+ return svg;
95
+ }
96
+
97
+ /**
98
+ * @param {{ l: number, r: number, b: number, t: number }} box
99
+ */
100
+ export function boxToSVGRect({ l: left, r: right, b: bottom, t: top }) {
101
+ const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
102
+ rect.setAttribute("x", left.toString());
103
+ rect.setAttribute("y", top.toString());
104
+ rect.setAttribute("width", (right - left).toString());
105
+ rect.setAttribute("height", (bottom - top).toString());
106
+
107
+ // Some style; corner radius 4px. Can't set this in CSS yet
108
+ rect.setAttribute("rx", "4");
109
+ rect.setAttribute("ry", "4");
110
+ return rect;
111
+ }
112
+
113
+ /**
114
+ * @param {string} layerClass
115
+ * @param {Array<{ l: number, r: number, b: number, t: number }>} boxes
116
+ * @param {PageModel} page
117
+ * @param {HTMLElement} containerEl
118
+ * @param {string[]} [rectClasses] CSS classes to add to the rects
119
+ */
120
+ export function renderBoxesInPageContainerLayer(layerClass, boxes, page, containerEl, rectClasses = null) {
121
+ const mountedSvg = containerEl.querySelector(`.${layerClass}`);
122
+ // Create the layer if it's not there
123
+ const svg = mountedSvg || createSVGPageLayer(page, layerClass);
124
+ if (!mountedSvg) {
125
+ // Insert after the image if the image is already loaded.
126
+ const imgEl = containerEl.querySelector('.BRpageimage');
127
+ if (imgEl) $(svg).insertAfter(imgEl);
128
+ else $(svg).prependTo(containerEl);
129
+ }
130
+
131
+ for (const [i, box] of boxes.entries()) {
132
+ const rect = boxToSVGRect(box);
133
+ if (rectClasses) {
134
+ rect.setAttribute('class', rectClasses[i]);
135
+ }
136
+ svg.appendChild(rect);
137
+ }
138
+ }
@@ -18,7 +18,7 @@ export const Pow2ReduceSet = {
18
18
  decr(n) {
19
19
  return 2 ** (Math.log2(n) - 1);
20
20
  }
21
- }
21
+ };
22
22
 
23
23
  export const NAMED_REDUCE_SETS = {
24
24
  pow2: Pow2ReduceSet,
@@ -54,7 +54,7 @@ export class Toolbar {
54
54
  .attr({href: br.bookUrl, title: br.bookUrlTitle})
55
55
  .addClass('BRreturn')
56
56
  .html(br.bookUrlText || br.bookTitle)
57
- )
57
+ );
58
58
  } else if (br.bookTitle) {
59
59
  $titleSectionEl.append(br.bookUrlText || br.bookTitle);
60
60
  }
@@ -75,8 +75,6 @@ export class Toolbar {
75
75
  br.$('.BRnavCntl').addClass('BRup');
76
76
  br.$('.pause').hide();
77
77
 
78
- this.updateToolbarZoom(br.reduce); // Pretty format
79
-
80
78
  // We build in mode 2
81
79
  br.refs.$BRtoolbar.append();
82
80
 
@@ -127,31 +125,6 @@ export class Toolbar {
127
125
  });
128
126
  }
129
127
 
130
- /**
131
- * @deprecated
132
- * @todo .BRzoom doesn't exist anywhere, so this is likely dead code
133
- * Update the displayed zoom factor based on reduction factor
134
- * @param {number} reduce
135
- */
136
- updateToolbarZoom(reduce) {
137
- const { br } = this;
138
- // $$$ TODO preserve zoom/fit for each mode
139
- const autofit = br.mode == br.constMode2up ? br.twoPage.autofit : br.onePage.autofit;
140
- /** @type {string} */
141
- let value;
142
- if (autofit) {
143
- value = autofit.slice(0,1).toUpperCase() + autofit.slice(1);
144
- } else {
145
- value = (100 / reduce)
146
- .toFixed(2)
147
- // Strip trailing zeroes and decimal if all zeroes
148
- .replace(/0+$/,'')
149
- .replace(/\.$/,'')
150
- + '%';
151
- }
152
- br.$('.BRzoom').text(value);
153
- }
154
-
155
128
  /**
156
129
  * @param {JQuery} $shareDiv
157
130
  */
@@ -212,11 +185,11 @@ export class Toolbar {
212
185
  $form.appendTo($shareDiv);
213
186
 
214
187
  $form.find('.fieldset-embed input').on('change', event => {
215
- const form = $(event.target).parents('form:first');
188
+ const form = $(event.target).parents('form').first();
216
189
  const params = {};
217
190
  params.mode = $(form.find('.fieldset-embed input[name=pages]:checked')).val();
218
191
  if (form.find('.fieldset-embed input[name=thispage]').prop('checked')) {
219
- params.page = br.getPageNum(br.currentIndex());
192
+ params.page = br.book.getPageNum(br.currentIndex());
220
193
  }
221
194
 
222
195
  if (br.getEmbedCode) {
@@ -232,12 +205,12 @@ export class Toolbar {
232
205
  // Bind share buttons
233
206
 
234
207
  // Use url without hashes
235
- $form.find('.facebook-share-button').click(() => {
208
+ $form.find('.facebook-share-button').on("click", () => {
236
209
  const params = $.param({ u: this._getSocialShareUrl() });
237
210
  const url = 'https://www.facebook.com/sharer.php?' + params;
238
211
  createPopup(url, 600, 400, 'Share');
239
212
  });
240
- $form.find('.twitter-share-button').click(() => {
213
+ $form.find('.twitter-share-button').on("click", () => {
241
214
  const params = $.param({
242
215
  url: this._getSocialShareUrl(),
243
216
  text: br.bookTitle
@@ -245,7 +218,7 @@ export class Toolbar {
245
218
  const url = 'https://twitter.com/intent/tweet?' + params;
246
219
  createPopup(url, 600, 400, 'Share');
247
220
  });
248
- $form.find('.email-share-button').click(() => {
221
+ $form.find('.email-share-button').on("click", () => {
249
222
  const body = `${br.bookTitle}\n\n${this._getSocialShareUrl()}`;
250
223
  window.location.href = `mailto:?subject=${encodeURI(br.bookTitle)}&body=${encodeURI(body)}`;
251
224
  });
@@ -331,11 +304,11 @@ export class Toolbar {
331
304
  }
332
305
  }
333
306
 
334
- export function blankInfoDiv() {
307
+ function blankInfoDiv() {
335
308
  return $(`
336
309
  <div class="BRfloat BRinfo">
337
310
  <div class="BRfloatHead">About this book
338
- <button class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="shift">Close</span></button>
311
+ <button class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="br-colorbox-shift">Close</span></button>
339
312
  </div>
340
313
  <div class="BRfloatBody">
341
314
  <div class="BRfloatCover"></div>
@@ -351,12 +324,12 @@ export function blankInfoDiv() {
351
324
  </div>`);
352
325
  }
353
326
 
354
- export function blankShareDiv() {
327
+ function blankShareDiv() {
355
328
  return $(`
356
329
  <div class="BRfloat BRshare">
357
330
  <div class="BRfloatHead">
358
331
  Share
359
- <button class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="shift">Close</span></button>
332
+ <button class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="br-colorbox-shift">Close</span></button>
360
333
  </div>
361
334
  </div>`);
362
335
  }
@@ -25,6 +25,10 @@ export const DEFAULT_OPTIONS = {
25
25
  thumbMaxLoading: 4,
26
26
  /** spacing between thumbnails */
27
27
  thumbPadding: 10,
28
+ /** min zoom in columns */
29
+ thumbMinZoomColumns: 2,
30
+ /** max zoom out columns */
31
+ thumbMaxZoomColumns: 8,
28
32
 
29
33
  /** @type {number | 'fast' | 'slow'} speed for flip animation */
30
34
  flipSpeed: 'fast',
@@ -275,6 +279,12 @@ export const DEFAULT_OPTIONS = {
275
279
  */
276
280
  startFullscreen: false,
277
281
 
282
+ /**
283
+ * @type {Boolean}
284
+ * will show logo at fullscreen mode
285
+ */
286
+ enableFSLogoShortcut: false,
287
+
278
288
  /**
279
289
  * @type {Boolean}
280
290
  * On init, by default, we want to handle resizing bookreader
@@ -0,0 +1,44 @@
1
+ // @ts-check
2
+ import { debounce } from '../utils';
3
+
4
+ /**
5
+ * Computing these things repeatedly is expensive (the browser needs to
6
+ * do a lot of computations/redrawing to make sure these are correct),
7
+ * so we store them here, and only recompute them when necessary:
8
+ * - window resize could have cause the container to change size
9
+ * - zoom could have cause scrollbars to appear/disappear, changing
10
+ * the client size.
11
+ */
12
+ export class HTMLDimensionsCacher {
13
+ clientWidth = 100;
14
+ clientHeight = 100;
15
+
16
+ boundingClientRect = { top: 0, left: 0 };
17
+
18
+ /**
19
+ * @param {HTMLElement} element
20
+ */
21
+ constructor(element) {
22
+ /** @type {HTMLElement} */
23
+ this.element = element;
24
+ }
25
+
26
+ updateClientSizes = () => {
27
+ const bc = this.element.getBoundingClientRect();
28
+ this.clientWidth = this.element.clientWidth;
29
+ this.clientHeight = this.element.clientHeight;
30
+ this.boundingClientRect.top = bc.top;
31
+ this.boundingClientRect.left = bc.left;
32
+ }
33
+ debouncedUpdateClientSizes = debounce(this.updateClientSizes, 150, false);
34
+
35
+ /** @param {EventTarget} win */
36
+ attachResizeListener(win = window) {
37
+ win.addEventListener('resize', this.debouncedUpdateClientSizes);
38
+ }
39
+
40
+ /** @param {EventTarget} win */
41
+ detachResizeListener(win = window) {
42
+ win.removeEventListener('resize', this.debouncedUpdateClientSizes);
43
+ }
44
+ }
@@ -0,0 +1,31 @@
1
+ /** Adds a class while the given element is experiencing scrolling */
2
+ export class ScrollClassAdder {
3
+ /**
4
+ * @param {HTMLElement} element
5
+ * @param {string} className
6
+ */
7
+ constructor(element, className) {
8
+ /** @type {HTMLElement} */
9
+ this.element = element;
10
+ /** @type {string} */
11
+ this.className = className;
12
+ this.timeout = null;
13
+ }
14
+
15
+ attach() {
16
+ this.element.addEventListener('scroll', this.onScroll);
17
+ }
18
+
19
+ detach() {
20
+ this.element.removeEventListener('scroll', this.onScroll);
21
+ }
22
+
23
+ onScroll = () => {
24
+ this.element.classList.add(this.className);
25
+ clearTimeout(this.timeout);
26
+ // TODO: Also remove class on mousemove, touch, click, etc.
27
+ this.timeout = setTimeout(() => {
28
+ this.element.classList.remove(this.className);
29
+ }, 600);
30
+ }
31
+ }
@@ -40,6 +40,24 @@ export function notInArray(value, array) {
40
40
  return !array.includes(value);
41
41
  }
42
42
 
43
+ /**
44
+ * Determines the active element, going into shadow doms.
45
+ * @return {Element}
46
+ */
47
+ export function getActiveElement(doc = document, recurseShadowDom = true) {
48
+ const activeElement = doc.activeElement;
49
+ if (recurseShadowDom && activeElement?.shadowRoot) {
50
+ return getActiveElement(activeElement.shadowRoot, true);
51
+ }
52
+ return activeElement;
53
+ }
54
+
55
+ /** Check if an input field/textarea is active. Also checks shadow DOMs. */
56
+ export function isInputActive(doc = document) {
57
+ const activeEl = getActiveElement(doc);
58
+ return activeEl?.tagName == "INPUT" || activeEl?.tagName == "TEXTAREA";
59
+ }
60
+
43
61
  /**
44
62
  * @param {HTMLIFrameElement} iframe
45
63
  * @return {Document}
@@ -80,16 +98,17 @@ export function encodeURIComponentPlus(value) {
80
98
  }
81
99
 
82
100
  /**
101
+ * @template {Function} T
83
102
  * Returns a function, that, as long as it continues to be invoked, will not
84
103
  * be triggered. The function will be called after it stops being called for
85
104
  * N milliseconds. If `immediate` is passed, trigger the function on the
86
105
  * leading edge, instead of the trailing.
87
106
  * @see https://davidwalsh.name/javascript-debounce-function
88
107
  *
89
- * @param {Function} func
108
+ * @param {T} func
90
109
  * @param {number} wait
91
110
  * @param {boolean} immediate
92
- * @return {Function}
111
+ * @return {T}
93
112
  */
94
113
  export function debounce(func, wait, immediate) {
95
114
  let timeout;
@@ -108,12 +127,13 @@ export function debounce(func, wait, immediate) {
108
127
  }
109
128
 
110
129
  /**
130
+ * @template T
111
131
  * Throttle function
112
132
  * @see https://remysharp.com/2010/07/21/throttling-function-calls
113
- * @param {Function} fn
133
+ * @param {T} fn
114
134
  * @param {number} threshold
115
135
  * @param {boolean} delay
116
- * @return {Function}
136
+ * @return {T}
117
137
  */
118
138
  export function throttle(fn, threshold, delay) {
119
139
  threshold || (threshold = 250);
@@ -160,14 +180,6 @@ export function PolyfilledCustomEvent(eventName, {bubbles = false, cancelable =
160
180
  return event;
161
181
  }
162
182
 
163
- /**
164
- * Promise based sleep - resolves at default 500ms
165
- * @param {Number} wait time in milliseconds
166
- */
167
- export function sleep(ms = 500) {
168
- return new Promise(res => setTimeout(res(true), ms));
169
- }
170
-
171
183
  /*
172
184
  * Returns the number pixels something should be rendered at to be ~1n on the users
173
185
  * screen when measured with a ruler.
@@ -179,7 +191,74 @@ export function calcScreenDPI() {
179
191
  const dpi = el.offsetWidth;
180
192
  document.body.removeChild(el);
181
193
 
182
- const screenDPI = dpi * devicePixelRatio;
194
+ // Do you believe in magic... numbers? We tested on some devices, and the displayed
195
+ // size of `width: 1in` was less than desired. On @pezvi's mac, it was ~75% ; on
196
+ // @cdrini's laptop it was ~85%. Since we want to avoid things appearing too small,
197
+ // let's just use a multiplier of 1.25
198
+ const screenDPI = dpi * 1.25;
183
199
  // This will return 0 in testing; never want it to be 0!
184
200
  return screenDPI == 0 ? 100 : screenDPI;
185
201
  }
202
+
203
+ /**
204
+ * @param {number[]} nums
205
+ * @returns {number}
206
+ */
207
+ export function sum(nums) {
208
+ return nums.reduce((cur, acc) => cur + acc, 0);
209
+ }
210
+
211
+ /**
212
+ * @template T
213
+ * @param {Generator<T>} gen
214
+ * @returns {T[]}
215
+ */
216
+ export function genToArray(gen) {
217
+ const result = [];
218
+ for (const item of gen) {
219
+ result.push(item);
220
+ }
221
+ return result;
222
+ }
223
+
224
+ /**
225
+ * Check if arrays contain the same elements. Does reference comparison.
226
+ * @param {Array} arr1
227
+ * @param {Array} arr2
228
+ */
229
+ export function arrEquals(arr1, arr2) {
230
+ return arr1.length == arr2.length && arr1.every((x, i) => x == arr2[i]);
231
+ }
232
+
233
+ /**
234
+ * Check if array has changed; namely to be used with lit's property.hasChanged
235
+ * @param {Array} [arr1]
236
+ * @param {Array} [arr2]
237
+ */
238
+ export function arrChanged(arr1, arr2) {
239
+ return arr1 && arr2 && !arrEquals(arr1, arr2);
240
+ }
241
+
242
+ /**
243
+ * Waits the provided number of ms and then resolves with a promise
244
+ * @param {number} ms
245
+ **/
246
+ export async function sleep(ms) {
247
+ return new Promise(resolve => setTimeout(resolve, ms));
248
+ }
249
+
250
+ /**
251
+ * @template T
252
+ * @param {function(): T} fn
253
+ * @param {Object} options
254
+ * @param {function(T): boolean} [options.until]
255
+ * @return {T | undefined}
256
+ */
257
+ export async function poll(fn, { step = 50, timeout = 500, until = val => Boolean(val), _sleep = sleep } = {}) {
258
+ const startTime = Date.now();
259
+ while (Date.now() - startTime < timeout) {
260
+ const result = fn();
261
+ if (until(result)) return result;
262
+ await _sleep(step);
263
+ }
264
+ }