@internetarchive/bookreader 5.0.0-88-alpha.11 → 5.0.0-89

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 (243) hide show
  1. package/BookReader/BookReader.css +17 -3
  2. package/BookReader/BookReader.js +1 -1
  3. package/BookReader/BookReader.js.map +1 -1
  4. package/BookReader/ia-bookreader-bundle.js +87 -108
  5. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  6. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  7. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  8. package/BookReader/plugins/plugin.autoplay.js +1 -1
  9. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  10. package/BookReader/plugins/plugin.iiif.js +1 -1
  11. package/BookReader/plugins/plugin.iiif.js.map +1 -1
  12. package/BookReader/plugins/plugin.resume.js +1 -1
  13. package/BookReader/plugins/plugin.resume.js.map +1 -1
  14. package/BookReader/plugins/plugin.search.js +1 -1
  15. package/BookReader/plugins/plugin.search.js.map +1 -1
  16. package/BookReader/plugins/plugin.text_selection.js +1 -1
  17. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  18. package/BookReader/plugins/plugin.tts.js +1 -1
  19. package/BookReader/plugins/plugin.tts.js.map +1 -1
  20. package/BookReader/plugins/plugin.url.js +1 -1
  21. package/BookReader/plugins/plugin.url.js.map +1 -1
  22. package/CHANGELOG.md +14 -0
  23. package/babel.config.js +12 -30
  24. package/codecov.yml +1 -1
  25. package/jsconfig.json +1 -3
  26. package/package.json +14 -16
  27. package/src/BookNavigator/search/search-results.js +1 -1
  28. package/src/BookReader/ImageCache.js +48 -15
  29. package/src/BookReader/Mode1UpLit.js +56 -86
  30. package/src/BookReader/Mode2UpLit.js +5 -5
  31. package/src/BookReader/Navbar/Navbar.js +53 -11
  32. package/src/BookReader/PageContainer.js +41 -22
  33. package/src/BookReader/options.js +27 -3
  34. package/src/BookReader/utils.js +10 -0
  35. package/src/BookReader.js +120 -21
  36. package/src/BookReaderPlugin.js +44 -0
  37. package/src/css/_BRnav.scss +0 -3
  38. package/src/css/_BRpages.scss +21 -2
  39. package/src/css/_controls.scss +4 -0
  40. package/src/plugins/plugin.archive_analytics.js +84 -78
  41. package/src/plugins/plugin.autoplay.js +98 -102
  42. package/src/plugins/plugin.chapters.js +17 -22
  43. package/src/plugins/plugin.iiif.js +16 -30
  44. package/src/plugins/plugin.resume.js +54 -51
  45. package/src/plugins/plugin.text_selection.js +68 -76
  46. package/src/plugins/tts/AbstractTTSEngine.js +2 -4
  47. package/src/plugins/tts/PageChunk.js +5 -9
  48. package/src/plugins/tts/PageChunkIterator.js +3 -5
  49. package/src/plugins/tts/plugin.tts.js +309 -329
  50. package/src/plugins/url/plugin.url.js +1 -1
  51. package/src/util/strings.js +1 -0
  52. package/tests/e2e/autoplay.test.js +8 -5
  53. package/tests/e2e/helpers/base.js +2 -2
  54. package/tests/e2e/helpers/mockSearch.js +6 -9
  55. package/tests/jest/BookReader/Navbar/Navbar.test.js +16 -3
  56. package/tests/jest/BookReader/PageContainer.test.js +96 -55
  57. package/tests/jest/BookReader/utils.test.js +21 -0
  58. package/tests/jest/BookReader.test.js +13 -12
  59. package/tests/jest/plugins/plugin.archive_analytics.test.js +8 -11
  60. package/tests/jest/plugins/plugin.autoplay.test.js +9 -22
  61. package/tests/jest/plugins/plugin.resume.test.js +19 -32
  62. package/tests/jest/plugins/plugin.text_selection.test.js +23 -24
  63. package/dist/esm/BookNavigator/assets/bookmark-colors.js +0 -4
  64. package/dist/esm/BookNavigator/assets/button-base.js +0 -4
  65. package/dist/esm/BookNavigator/assets/ia-logo.js +0 -4
  66. package/dist/esm/BookNavigator/assets/icon_checkmark.js +0 -8
  67. package/dist/esm/BookNavigator/assets/icon_close.js +0 -4
  68. package/dist/esm/BookNavigator/book-navigator.js +0 -612
  69. package/dist/esm/BookNavigator/bookmarks/bookmark-button.js +0 -35
  70. package/dist/esm/BookNavigator/bookmarks/bookmark-edit.js +0 -78
  71. package/dist/esm/BookNavigator/bookmarks/bookmarks-list.js +0 -160
  72. package/dist/esm/BookNavigator/bookmarks/bookmarks-loginCTA.js +0 -24
  73. package/dist/esm/BookNavigator/bookmarks/bookmarks-provider.js +0 -55
  74. package/dist/esm/BookNavigator/bookmarks/ia-bookmarks.js +0 -521
  75. package/dist/esm/BookNavigator/delete-modal-actions.js +0 -29
  76. package/dist/esm/BookNavigator/downloads/downloads-provider.js +0 -84
  77. package/dist/esm/BookNavigator/downloads/downloads.js +0 -69
  78. package/dist/esm/BookNavigator/search/search-provider.js +0 -238
  79. package/dist/esm/BookNavigator/search/search-results.js +0 -161
  80. package/dist/esm/BookNavigator/sharing.js +0 -26
  81. package/dist/esm/BookNavigator/viewable-files.js +0 -94
  82. package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments-provider.js +0 -83
  83. package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments.js +0 -131
  84. package/dist/esm/BookReader/BookModel.js +0 -575
  85. package/dist/esm/BookReader/DragScrollable.js +0 -224
  86. package/dist/esm/BookReader/ImageCache.js +0 -122
  87. package/dist/esm/BookReader/Mode1Up.js +0 -114
  88. package/dist/esm/BookReader/Mode1UpLit.js +0 -579
  89. package/dist/esm/BookReader/Mode2Up.js +0 -106
  90. package/dist/esm/BookReader/Mode2UpLit.js +0 -1020
  91. package/dist/esm/BookReader/ModeCoordinateSpace.js +0 -28
  92. package/dist/esm/BookReader/ModeSmoothZoom.js +0 -318
  93. package/dist/esm/BookReader/ModeThumb.js +0 -366
  94. package/dist/esm/BookReader/Navbar/Navbar.js +0 -253
  95. package/dist/esm/BookReader/PageContainer.js +0 -165
  96. package/dist/esm/BookReader/ReduceSet.js +0 -27
  97. package/dist/esm/BookReader/Toolbar/Toolbar.js +0 -242
  98. package/dist/esm/BookReader/events.js +0 -20
  99. package/dist/esm/BookReader/options.js +0 -331
  100. package/dist/esm/BookReader/utils/HTMLDimensionsCacher.js +0 -48
  101. package/dist/esm/BookReader/utils/ScrollClassAdder.js +0 -31
  102. package/dist/esm/BookReader/utils/SelectionObserver.js +0 -42
  103. package/dist/esm/BookReader/utils/classes.js +0 -37
  104. package/dist/esm/BookReader/utils.js +0 -315
  105. package/dist/esm/BookReader.js +0 -1828
  106. package/dist/esm/assets/icons/1up.svg +0 -12
  107. package/dist/esm/assets/icons/2up.svg +0 -15
  108. package/dist/esm/assets/icons/advance.svg +0 -26
  109. package/dist/esm/assets/icons/chevron-right.svg +0 -1
  110. package/dist/esm/assets/icons/close-circle-dark.svg +0 -1
  111. package/dist/esm/assets/icons/close-circle.svg +0 -1
  112. package/dist/esm/assets/icons/fullscreen.svg +0 -17
  113. package/dist/esm/assets/icons/fullscreen_exit.svg +0 -17
  114. package/dist/esm/assets/icons/hamburger.svg +0 -15
  115. package/dist/esm/assets/icons/left-arrow.svg +0 -12
  116. package/dist/esm/assets/icons/magnify-minus.svg +0 -12
  117. package/dist/esm/assets/icons/magnify-plus.svg +0 -13
  118. package/dist/esm/assets/icons/magnify.svg +0 -15
  119. package/dist/esm/assets/icons/pause.svg +0 -23
  120. package/dist/esm/assets/icons/play.svg +0 -22
  121. package/dist/esm/assets/icons/playback-speed.svg +0 -34
  122. package/dist/esm/assets/icons/read-aloud.svg +0 -22
  123. package/dist/esm/assets/icons/review.svg +0 -22
  124. package/dist/esm/assets/icons/thumbnails.svg +0 -17
  125. package/dist/esm/assets/icons/voice.svg +0 -1
  126. package/dist/esm/assets/icons/volume-full.svg +0 -22
  127. package/dist/esm/assets/images/BRicons.png +0 -0
  128. package/dist/esm/assets/images/BRicons.svg +0 -94
  129. package/dist/esm/assets/images/BRicons_ia.png +0 -0
  130. package/dist/esm/assets/images/back_pages.png +0 -0
  131. package/dist/esm/assets/images/book_bottom_icon.png +0 -0
  132. package/dist/esm/assets/images/book_down_icon.png +0 -0
  133. package/dist/esm/assets/images/book_left_icon.png +0 -0
  134. package/dist/esm/assets/images/book_leftmost_icon.png +0 -0
  135. package/dist/esm/assets/images/book_right_icon.png +0 -0
  136. package/dist/esm/assets/images/book_rightmost_icon.png +0 -0
  137. package/dist/esm/assets/images/book_top_icon.png +0 -0
  138. package/dist/esm/assets/images/book_up_icon.png +0 -0
  139. package/dist/esm/assets/images/books_graphic.svg +0 -177
  140. package/dist/esm/assets/images/booksplit.png +0 -0
  141. package/dist/esm/assets/images/control_pause_icon.png +0 -0
  142. package/dist/esm/assets/images/control_play_icon.png +0 -0
  143. package/dist/esm/assets/images/embed_icon.png +0 -0
  144. package/dist/esm/assets/images/icon-home-ia.png +0 -0
  145. package/dist/esm/assets/images/icon_OL-logo-xs.png +0 -0
  146. package/dist/esm/assets/images/icon_alert-xs.png +0 -0
  147. package/dist/esm/assets/images/icon_book.svg +0 -12
  148. package/dist/esm/assets/images/icon_bookmark.svg +0 -12
  149. package/dist/esm/assets/images/icon_close-pop.png +0 -0
  150. package/dist/esm/assets/images/icon_download.png +0 -0
  151. package/dist/esm/assets/images/icon_gear.svg +0 -14
  152. package/dist/esm/assets/images/icon_hamburger.svg +0 -20
  153. package/dist/esm/assets/images/icon_home.png +0 -0
  154. package/dist/esm/assets/images/icon_home.svg +0 -21
  155. package/dist/esm/assets/images/icon_home_ia.png +0 -0
  156. package/dist/esm/assets/images/icon_indicator.png +0 -0
  157. package/dist/esm/assets/images/icon_info.svg +0 -11
  158. package/dist/esm/assets/images/icon_one_page.svg +0 -8
  159. package/dist/esm/assets/images/icon_pause.svg +0 -1
  160. package/dist/esm/assets/images/icon_play.svg +0 -1
  161. package/dist/esm/assets/images/icon_playback-rate.svg +0 -15
  162. package/dist/esm/assets/images/icon_return.png +0 -0
  163. package/dist/esm/assets/images/icon_search_button.svg +0 -8
  164. package/dist/esm/assets/images/icon_share.svg +0 -9
  165. package/dist/esm/assets/images/icon_skip-ahead.svg +0 -6
  166. package/dist/esm/assets/images/icon_skip-back.svg +0 -13
  167. package/dist/esm/assets/images/icon_speaker.svg +0 -18
  168. package/dist/esm/assets/images/icon_speaker_open.svg +0 -10
  169. package/dist/esm/assets/images/icon_thumbnails.svg +0 -12
  170. package/dist/esm/assets/images/icon_toc.svg +0 -5
  171. package/dist/esm/assets/images/icon_two_pages.svg +0 -9
  172. package/dist/esm/assets/images/icon_zoomer.png +0 -0
  173. package/dist/esm/assets/images/loading.gif +0 -0
  174. package/dist/esm/assets/images/logo_icon.png +0 -0
  175. package/dist/esm/assets/images/marker_chap-off.png +0 -0
  176. package/dist/esm/assets/images/marker_chap-off.svg +0 -11
  177. package/dist/esm/assets/images/marker_chap-off_ia.png +0 -0
  178. package/dist/esm/assets/images/marker_chap-on.png +0 -0
  179. package/dist/esm/assets/images/marker_chap-on.svg +0 -11
  180. package/dist/esm/assets/images/marker_srch-on.svg +0 -11
  181. package/dist/esm/assets/images/marker_srchchap-off.png +0 -0
  182. package/dist/esm/assets/images/marker_srchchap-on.png +0 -0
  183. package/dist/esm/assets/images/nav_control-dn.png +0 -0
  184. package/dist/esm/assets/images/nav_control-dn_ia.png +0 -0
  185. package/dist/esm/assets/images/nav_control-up.png +0 -0
  186. package/dist/esm/assets/images/nav_control-up_ia.png +0 -0
  187. package/dist/esm/assets/images/nav_control.png +0 -0
  188. package/dist/esm/assets/images/one_page_mode_icon.png +0 -0
  189. package/dist/esm/assets/images/paper-badge.png +0 -0
  190. package/dist/esm/assets/images/print_icon.png +0 -0
  191. package/dist/esm/assets/images/progressbar.gif +0 -0
  192. package/dist/esm/assets/images/right_edges.png +0 -0
  193. package/dist/esm/assets/images/slider.png +0 -0
  194. package/dist/esm/assets/images/slider_ia.png +0 -0
  195. package/dist/esm/assets/images/thumbnail_mode_icon.png +0 -0
  196. package/dist/esm/assets/images/transparent.png +0 -0
  197. package/dist/esm/assets/images/two_page_mode_icon.png +0 -0
  198. package/dist/esm/assets/images/unviewable_page.png +0 -0
  199. package/dist/esm/assets/images/zoom_in_icon.png +0 -0
  200. package/dist/esm/assets/images/zoom_out_icon.png +0 -0
  201. package/dist/esm/css/BookReader.scss +0 -85
  202. package/dist/esm/css/_BRBookmarks.scss +0 -29
  203. package/dist/esm/css/_BRComponent.scss +0 -13
  204. package/dist/esm/css/_BRfloat.scss +0 -197
  205. package/dist/esm/css/_BRicon.scss +0 -54
  206. package/dist/esm/css/_BRmain.scss +0 -262
  207. package/dist/esm/css/_BRnav.scss +0 -354
  208. package/dist/esm/css/_BRpages.scss +0 -213
  209. package/dist/esm/css/_BRsearch.scss +0 -268
  210. package/dist/esm/css/_BRtoolbar.scss +0 -84
  211. package/dist/esm/css/_BRvendor.scss +0 -5
  212. package/dist/esm/css/_TextSelection.scss +0 -108
  213. package/dist/esm/css/_colorbox.scss +0 -52
  214. package/dist/esm/css/_controls.scss +0 -257
  215. package/dist/esm/css/_icons.scss +0 -121
  216. package/dist/esm/ia-bookreader/ia-bookreader.js +0 -141
  217. package/dist/esm/jquery-wrapper.js +0 -3
  218. package/dist/esm/plugins/plugin.archive_analytics.js +0 -72
  219. package/dist/esm/plugins/plugin.autoplay.js +0 -119
  220. package/dist/esm/plugins/plugin.chapters.js +0 -288
  221. package/dist/esm/plugins/plugin.iframe.js +0 -44
  222. package/dist/esm/plugins/plugin.iiif.js +0 -146
  223. package/dist/esm/plugins/plugin.resume.js +0 -66
  224. package/dist/esm/plugins/plugin.text_selection.js +0 -621
  225. package/dist/esm/plugins/plugin.vendor-fullscreen.js +0 -227
  226. package/dist/esm/plugins/search/plugin.search.js +0 -499
  227. package/dist/esm/plugins/search/utils.js +0 -42
  228. package/dist/esm/plugins/search/view.js +0 -360
  229. package/dist/esm/plugins/tts/AbstractTTSEngine.js +0 -282
  230. package/dist/esm/plugins/tts/FestivalTTSEngine.js +0 -192
  231. package/dist/esm/plugins/tts/PageChunk.js +0 -105
  232. package/dist/esm/plugins/tts/PageChunkIterator.js +0 -155
  233. package/dist/esm/plugins/tts/WebTTSEngine.js +0 -364
  234. package/dist/esm/plugins/tts/plugin.tts.js +0 -315
  235. package/dist/esm/plugins/tts/tooltip_dict.js +0 -14
  236. package/dist/esm/plugins/tts/utils.js +0 -79
  237. package/dist/esm/plugins/url/UrlPlugin.js +0 -197
  238. package/dist/esm/plugins/url/plugin.url.js +0 -212
  239. package/dist/esm/util/browserSniffing.js +0 -56
  240. package/dist/esm/util/debouncer.js +0 -25
  241. package/dist/esm/util/docCookies.js +0 -75
  242. package/dist/esm/util/strings.js +0 -34
  243. package/index.js +0 -2
@@ -2,6 +2,8 @@
2
2
  /** @typedef {import('./BookModel.js').PageModel} PageModel */
3
3
  /** @typedef {import('./ImageCache.js').ImageCache} ImageCache */
4
4
 
5
+ import { sleep } from './utils.js';
6
+
5
7
 
6
8
  export class PageContainer {
7
9
  /**
@@ -9,12 +11,10 @@ export class PageContainer {
9
11
  * @param {object} opts
10
12
  * @param {boolean} opts.isProtected Whether we're in a protected book
11
13
  * @param {ImageCache} opts.imageCache
12
- * @param {string} opts.loadingImage
13
14
  */
14
- constructor(page, {isProtected, imageCache, loadingImage}) {
15
+ constructor(page, {isProtected, imageCache}) {
15
16
  this.page = page;
16
17
  this.imageCache = imageCache;
17
- this.loadingImage = loadingImage;
18
18
  this.$container = $('<div />', {
19
19
  'class': `BRpagecontainer ${page ? `pagediv${page.index}` : 'BRemptypage'}`,
20
20
  css: { position: 'absolute' },
@@ -43,39 +43,58 @@ export class PageContainer {
43
43
  return;
44
44
  }
45
45
 
46
- const alreadyLoaded = this.imageCache.imageLoaded(this.page.index, reduce);
47
- const nextBestLoadedReduce = !alreadyLoaded && this.imageCache.getBestLoadedReduce(this.page.index, reduce);
46
+ const finalReduce = this.imageCache.getFinalReduce(this.page.index, reduce);
47
+ const newImageURI = this.page.getURI(finalReduce, 0);
48
48
 
49
- // Create high res image
50
- const $newImg = this.imageCache.image(this.page.index, reduce);
49
+ // Note: These must be computed _before_ we call .image()
50
+ const alreadyLoaded = this.imageCache.imageLoaded(this.page.index, finalReduce);
51
+ const nextBestLoadedReduce = this.imageCache.getBestLoadedReduce(this.page.index, reduce);
51
52
 
52
53
  // Avoid removing/re-adding the image if it's already there
53
54
  // This can be called quite a bit, so we need to be fast
54
- if (this.$img?.[0].src == $newImg[0].src) {
55
+ if (this.$img?.data('src') == newImageURI) {
55
56
  return this;
56
57
  }
57
58
 
58
- this.$img?.remove();
59
- this.$img = $newImg.prependTo(this.$container);
59
+ let $oldImg = this.$img;
60
+ this.$img = this.imageCache.image(this.page.index, finalReduce);
61
+ if ($oldImg) {
62
+ this.$img.insertAfter($oldImg);
63
+ } else {
64
+ this.$img.prependTo(this.$container);
65
+ }
60
66
 
61
- const backgroundLayers = [];
62
67
  if (!alreadyLoaded) {
63
68
  this.$container.addClass('BRpageloading');
64
- backgroundLayers.push(`url("${this.loadingImage}") center/20px no-repeat`);
65
- }
66
- if (nextBestLoadedReduce) {
67
- backgroundLayers.push(`url("${this.page.getURI(nextBestLoadedReduce, 0)}") center/100% 100% no-repeat`);
68
69
  }
69
70
 
70
- if (!alreadyLoaded) {
71
- this.$img
72
- .css('background', backgroundLayers.join(','))
73
- .one('loadend', async (ev) => {
74
- $(ev.target).css({ 'background': '' });
75
- $(ev.target).parent().removeClass('BRpageloading');
76
- });
71
+ if (!alreadyLoaded && nextBestLoadedReduce) {
72
+ // If we have a slightly lower quality image loaded, use that as the background
73
+ // while the higher res one loads
74
+ const nextBestUri = this.page.getURI(nextBestLoadedReduce, 0);
75
+ if ($oldImg) {
76
+ if ($oldImg.data('src') == nextBestUri) {
77
+ // Do nothing! It's already showing the right thing
78
+ } else {
79
+ // We have a different src, need to update the src
80
+ this.imageCache.image(this.page.index, nextBestLoadedReduce, $oldImg[0]);
81
+ }
82
+ } else {
83
+ // We don't have an old <img>, so we need to create a new one
84
+ $oldImg = this.imageCache.image(this.page.index, nextBestLoadedReduce);
85
+ $oldImg.prependTo(this.$container);
86
+ }
77
87
  }
78
88
 
89
+ this.$img
90
+ .one('load', async (ev) => {
91
+ this.$container.removeClass('BRpageloading');
92
+ // `load` can fire a little early, so wait a spell before removing the old image
93
+ // to avoid flicker
94
+ await sleep(100);
95
+ $oldImg?.remove();
96
+ });
97
+
79
98
  return this;
80
99
  }
81
100
  }
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /** @typedef {import('./BookModel.js').PageNumString} PageNumString */
2
3
  /** @typedef {import('./BookModel.js').LeafNum} LeafNum */
3
4
 
@@ -140,8 +141,18 @@ export const DEFAULT_OPTIONS = {
140
141
  * but going forward we'll keep them here.
141
142
  **/
142
143
  plugins: {
143
- /** @type {import('../plugins/plugin.text_selection.js').TextSelectionPluginOptions} */
144
+ /** @type {import('../plugins/plugin.archive_analytics.js').ArchiveAnalyticsPlugin['options']}*/
145
+ archiveAnalytics: null,
146
+ /** @type {import('../plugins/plugin.autoplay.js').AutoplayPlugin['options']}*/
147
+ autoplay: null,
148
+ /** @type {import('../plugins/plugin.iiif.js').IiifPlugin['options']} */
149
+ iiif: null,
150
+ /** @type {import('../plugins/plugin.resume.js').ResumePlugin['options']} */
151
+ resume: null,
152
+ /** @type {import('../plugins/plugin.text_selection.js').TextSelectionPlugin['options']} */
144
153
  textSelection: null,
154
+ /** @type {import('../plugins/tts/plugin.tts.js').TtsPlugin['options']} */
155
+ tts: null,
145
156
  },
146
157
 
147
158
  /**
@@ -183,16 +194,29 @@ export const DEFAULT_OPTIONS = {
183
194
  /** @type {import('../plugins/plugin.chapters.js').TocEntry[]} */
184
195
  table_of_contents: null,
185
196
 
186
- /** Advanced methods for page rendering */
197
+ /**
198
+ * Advanced methods for page rendering.
199
+ * All option functions have their `this` object set to the BookReader instance.
200
+ **/
201
+
187
202
  /** @type {() => number} */
188
203
  getNumLeafs: null,
189
204
  /** @type {(index: number) => number} */
190
205
  getPageWidth: null,
191
206
  /** @type {(index: number) => number} */
192
207
  getPageHeight: null,
193
- /** @type {(index: number, reduce: number, rotate: number) => *} */
208
+ /** @type {(index: number, reduce: number, rotate: number) => string} */
194
209
  getPageURI: null,
195
210
 
211
+ /**
212
+ * @type {(img: HTMLImageElement, uri: string) => Promise<void>}
213
+ * Render the page URI into the image element. Perform any necessary preloading,
214
+ * authentication, etc.
215
+ */
216
+ renderPageURI(img, uri) {
217
+ img.src = uri;
218
+ },
219
+
196
220
  /**
197
221
  * @type {(index: number) => 'L' | 'R'}
198
222
  * Return which side, left or right, that a given page should be displayed on
@@ -288,3 +288,13 @@ export function promisifyEvent(target, eventType) {
288
288
  export function escapeRegExp(string) {
289
289
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
290
290
  }
291
+
292
+ /**
293
+ * @param {number | 'fast' | 'slow' | string} speed
294
+ * Parsing of the jquery animation speed; see https://api.jquery.com/animate/
295
+ */
296
+ export function parseAnimationSpeed(speed) {
297
+ if (speed === 'slow') return 600;
298
+ if (speed === 'fast') return 200;
299
+ return parseInt(speed, 10);
300
+ }
package/src/BookReader.js CHANGED
@@ -23,9 +23,8 @@ This file is part of BookReader.
23
23
  import 'jquery-ui/ui/widget.js';
24
24
  import 'jquery-ui/ui/widgets/mouse.js';
25
25
  import 'jquery-ui-touch-punch';
26
- import jQuery from 'jquery';
27
26
 
28
- // import PACKAGE_JSON from '../package.json';
27
+ import PACKAGE_JSON from '../package.json';
29
28
  import * as utils from './BookReader/utils.js';
30
29
  import { exposeOverrideable } from './BookReader/utils/classes.js';
31
30
  import { Navbar } from './BookReader/Navbar/Navbar.js';
@@ -54,7 +53,7 @@ export default function BookReader(overrides = {}) {
54
53
  this.setup(options);
55
54
  }
56
55
 
57
- BookReader.version = '123';
56
+ BookReader.version = PACKAGE_JSON.version;
58
57
 
59
58
  // Mode constants
60
59
  /** 1 page view */
@@ -63,6 +62,33 @@ BookReader.constMode1up = 1;
63
62
  BookReader.constMode2up = 2;
64
63
  /** thumbnails view */
65
64
  BookReader.constModeThumb = 3;
65
+
66
+ // Although this can actualy have any BookReaderPlugin subclass as value, we
67
+ // hardcode the known plugins here for type checking
68
+ BookReader.PLUGINS = {
69
+ /** @type {typeof import('./plugins/plugin.archive_analytics.js').ArchiveAnalyticsPlugin | null}*/
70
+ archiveAnalytics: null,
71
+ /** @type {typeof import('./plugins/plugin.autoplay.js').AutoplayPlugin | null}*/
72
+ autoplay: null,
73
+ /** @type {typeof import('./plugins/plugin.resume.js').ResumePlugin | null}*/
74
+ resume: null,
75
+ /** @type {typeof import('./plugins/plugin.text_selection.js').TextSelectionPlugin | null}*/
76
+ textSelection: null,
77
+ /** @type {typeof import('./plugins/tts/plugin.tts.js').TtsPlugin | null}*/
78
+ tts: null,
79
+ };
80
+
81
+ /**
82
+ * @param {string} pluginName
83
+ * @param {typeof import('./BookReaderPlugin.js').BookReaderPlugin} plugin
84
+ */
85
+ BookReader.registerPlugin = function(pluginName, plugin) {
86
+ if (BookReader.PLUGINS[pluginName]) {
87
+ console.warn(`Plugin ${pluginName} already registered. Overwriting.`);
88
+ }
89
+ BookReader.PLUGINS[pluginName] = plugin;
90
+ };
91
+
66
92
  /** image cache */
67
93
  BookReader.imageCache = null;
68
94
 
@@ -90,6 +116,38 @@ BookReader.prototype.setup = function(options) {
90
116
  // Store the options used to setup bookreader
91
117
  this.options = options;
92
118
 
119
+ // Construct the usual plugins first to get type hints
120
+ this._plugins = {
121
+ archiveAnalytics: BookReader.PLUGINS.archiveAnalytics ? new BookReader.PLUGINS.archiveAnalytics(this) : null,
122
+ autoplay: BookReader.PLUGINS.autoplay ? new BookReader.PLUGINS.autoplay(this) : null,
123
+ resume: BookReader.PLUGINS.resume ? new BookReader.PLUGINS.resume(this) : null,
124
+ textSelection: BookReader.PLUGINS.textSelection ? new BookReader.PLUGINS.textSelection(this) : null,
125
+ tts: BookReader.PLUGINS.tts ? new BookReader.PLUGINS.tts(this) : null,
126
+ };
127
+
128
+ // Delete anything that's null
129
+ for (const [pluginName, plugin] of Object.entries(this._plugins)) {
130
+ if (!plugin) delete this._plugins[pluginName];
131
+ }
132
+
133
+ // Now construct the rest of the plugins
134
+ for (const [pluginName, PluginClass] of Object.entries(BookReader.PLUGINS)) {
135
+ if (this._plugins[pluginName] || !PluginClass) continue;
136
+ this._plugins[pluginName] = new PluginClass(this);
137
+ }
138
+
139
+ // And call setup on them
140
+ for (const [pluginName, plugin] of Object.entries(this._plugins)) {
141
+ try {
142
+ plugin.setup(this.options.plugins?.[pluginName] ?? {});
143
+ // Write the options back; this way the plugin is the source of truth,
144
+ // and BR just contains a reference to it.
145
+ this.options.plugins[pluginName] = plugin.options;
146
+ } catch (e) {
147
+ console.error(`Error setting up plugin ${pluginName}`, e);
148
+ }
149
+ }
150
+
93
151
  /** @type {number} @deprecated some past iterations set this */
94
152
  this.numLeafs = undefined;
95
153
 
@@ -148,11 +206,7 @@ BookReader.prototype.setup = function(options) {
148
206
  this.displayedIndices = [];
149
207
 
150
208
  this.animating = false;
151
- this.flipSpeed = typeof options.flipSpeed === 'number' ? options.flipSpeed : {
152
- 'fast': 200,
153
- 'slow': 600,
154
- }[options.flipSpeed] || 400;
155
- this.flipDelay = options.flipDelay;
209
+ this.flipSpeed = utils.parseAnimationSpeed(options.flipSpeed) || 400;
156
210
 
157
211
  /**
158
212
  * Represents the first displayed index
@@ -242,6 +296,7 @@ BookReader.prototype.setup = function(options) {
242
296
  this.imageCache = new ImageCache(this.book, {
243
297
  useSrcSet: this.options.useSrcSet,
244
298
  reduceSet: this.reduceSet,
299
+ renderPageURI: options.renderPageURI.bind(this),
245
300
  });
246
301
 
247
302
  /**
@@ -350,9 +405,9 @@ BookReader.prototype.initParams = function() {
350
405
  }
351
406
 
352
407
  // Check for Resume plugin
353
- if (this.options.enablePageResume) {
408
+ if (this._plugins.resume?.options.enabled) {
354
409
  // Check cookies
355
- const val = this.getResumeValue();
410
+ const val = this._plugins.resume.getResumeValue();
356
411
  if (val !== null) {
357
412
  // If page index different from default
358
413
  if (params.index !== val) {
@@ -539,7 +594,12 @@ BookReader.prototype.init = function() {
539
594
  this.initToolbar(this.mode, this.ui); // Build inside of toolbar div
540
595
  }
541
596
  if (this.options.showNavbar) { // default navigation
542
- this.initNavbar();
597
+ const $navBar = this.initNavbar();
598
+
599
+ // extend navbar with plugins
600
+ for (const plugin of Object.values(this._plugins)) {
601
+ plugin.extendNavBar($navBar);
602
+ }
543
603
  }
544
604
 
545
605
  // Switch navbar controls on mobile/desktop
@@ -586,6 +646,16 @@ BookReader.prototype.init = function() {
586
646
  this.enterFullscreen(true);
587
647
  }
588
648
 
649
+ // Init plugins
650
+ for (const [pluginName, plugin] of Object.entries(this._plugins)) {
651
+ try {
652
+ plugin.init();
653
+ }
654
+ catch (e) {
655
+ console.error(`Error initializing plugin ${pluginName}`, e);
656
+ }
657
+ }
658
+
589
659
  this.init.initComplete = true;
590
660
  this.trigger(BookReader.eventNames.PostInit);
591
661
 
@@ -776,11 +846,17 @@ BookReader.prototype.drawLeafs = function() {
776
846
  * @param {PageIndex} index
777
847
  */
778
848
  BookReader.prototype._createPageContainer = function(index) {
779
- return new PageContainer(this.book.getPage(index, false), {
849
+ const pageContainer = new PageContainer(this.book.getPage(index, false), {
780
850
  isProtected: this.protected,
781
851
  imageCache: this.imageCache,
782
- loadingImage: this.imagesBaseURL + 'loading.gif',
783
852
  });
853
+
854
+ // Call plugin handlers
855
+ for (const plugin of Object.values(this._plugins)) {
856
+ plugin._configurePageContainer(pageContainer);
857
+ }
858
+
859
+ return pageContainer;
784
860
  };
785
861
 
786
862
  BookReader.prototype.bindGestures = function(jElement) {
@@ -829,7 +905,7 @@ BookReader.prototype.zoom = function(direction) {
829
905
  } else {
830
906
  this.activeMode.zoom('out');
831
907
  }
832
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
908
+ this._plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
833
909
  return;
834
910
  };
835
911
 
@@ -1060,7 +1136,7 @@ BookReader.prototype.switchMode = function(
1060
1136
  const eventName = mode + 'PageViewSelected';
1061
1137
  this.trigger(BookReader.eventNames[eventName]);
1062
1138
 
1063
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1139
+ this._plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
1064
1140
  };
1065
1141
 
1066
1142
  BookReader.prototype.updateBrClasses = function() {
@@ -1132,7 +1208,7 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
1132
1208
  }
1133
1209
  this.jumpToIndex(currentIndex);
1134
1210
 
1135
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1211
+ this._plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
1136
1212
  // Add "?view=theater"
1137
1213
  this.trigger(BookReader.eventNames.fragmentChange);
1138
1214
  // trigger event here, so that animations,
@@ -1178,7 +1254,7 @@ BookReader.prototype.exitFullScreen = async function () {
1178
1254
  await this.activeMode.mode1UpLit.updateComplete;
1179
1255
  }
1180
1256
 
1181
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1257
+ this._plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
1182
1258
  // Remove "?view=theater"
1183
1259
  this.trigger(BookReader.eventNames.fragmentChange);
1184
1260
  this.refs.$br.removeClass('BRfullscreenAnimation');
@@ -1277,10 +1353,19 @@ BookReader.prototype.leftmost = function() {
1277
1353
  }
1278
1354
  };
1279
1355
 
1280
- BookReader.prototype.next = function({triggerStop = true} = {}) {
1356
+ /**
1357
+ * @param {object} options
1358
+ * @param {boolean} [options.triggerStop = true]
1359
+ * @param {number | 'fast' | 'slow'} [options.flipSpeed]
1360
+ */
1361
+ BookReader.prototype.next = function({
1362
+ triggerStop = true,
1363
+ flipSpeed = null,
1364
+ } = {}) {
1281
1365
  if (this.constMode2up == this.mode) {
1282
1366
  if (triggerStop) this.trigger(BookReader.eventNames.stop);
1283
- this._modes.mode2Up.mode2UpLit.flipAnimation('next');
1367
+ flipSpeed = utils.parseAnimationSpeed(flipSpeed) || this.flipSpeed;
1368
+ this._modes.mode2Up.mode2UpLit.flipAnimation('next', {flipSpeed});
1284
1369
  } else {
1285
1370
  if (this.firstIndex < this.book.getNumLeafs() - 1) {
1286
1371
  this.jumpToIndex(this.firstIndex + 1);
@@ -1288,13 +1373,22 @@ BookReader.prototype.next = function({triggerStop = true} = {}) {
1288
1373
  }
1289
1374
  };
1290
1375
 
1291
- BookReader.prototype.prev = function({triggerStop = true} = {}) {
1376
+ /**
1377
+ * @param {object} options
1378
+ * @param {boolean} [options.triggerStop = true]
1379
+ * @param {number | 'fast' | 'slow'} [options.flipSpeed]
1380
+ */
1381
+ BookReader.prototype.prev = function({
1382
+ triggerStop = true,
1383
+ flipSpeed = null,
1384
+ } = {}) {
1292
1385
  const isOnFrontPage = this.firstIndex < 1;
1293
1386
  if (isOnFrontPage) return;
1294
1387
 
1295
1388
  if (this.constMode2up == this.mode) {
1296
1389
  if (triggerStop) this.trigger(BookReader.eventNames.stop);
1297
- this._modes.mode2Up.mode2UpLit.flipAnimation('prev');
1390
+ flipSpeed = utils.parseAnimationSpeed(flipSpeed) || this.flipSpeed;
1391
+ this._modes.mode2Up.mode2UpLit.flipAnimation('prev', {flipSpeed});
1298
1392
  } else {
1299
1393
  if (this.firstIndex >= 1) {
1300
1394
  this.jumpToIndex(this.firstIndex - 1);
@@ -1479,6 +1573,11 @@ BookReader.prototype.bindNavigationHandlers = function() {
1479
1573
  self.$('.BRnavCntl').animate({opacity:.75},250);
1480
1574
  }
1481
1575
  });
1576
+
1577
+ // Call _bindNavigationHandlers on the plugins
1578
+ for (const plugin of Object.values(this._plugins)) {
1579
+ plugin._bindNavigationHandlers();
1580
+ }
1482
1581
  };
1483
1582
 
1484
1583
  /**************************/
@@ -0,0 +1,44 @@
1
+ // @ts-check
2
+ /** @typedef {import("./BookReader.js").default} BookReader */
3
+
4
+ /**
5
+ * @template TOptions
6
+ */
7
+ export class BookReaderPlugin {
8
+ /**
9
+ * @param {BookReader} br
10
+ */
11
+ constructor(br) {
12
+ /** @type {BookReader} */
13
+ this.br = br;
14
+ /** @type {TOptions} */
15
+ this.options;
16
+ }
17
+
18
+ /**
19
+ * @abstract
20
+ * @param {TOptions} options
21
+ **/
22
+ setup(options) {
23
+ this.options = Object.assign({}, this.options, options);
24
+ }
25
+
26
+ /** @abstract */
27
+ init() {}
28
+
29
+ /**
30
+ * @abstract
31
+ * @protected
32
+ * @param {import ("./BookReader/PageContainer.js").PageContainer} pageContainer
33
+ */
34
+ _configurePageContainer(pageContainer) {
35
+ }
36
+
37
+ /** @abstract @protected */
38
+ _bindNavigationHandlers() {}
39
+
40
+ /**
41
+ * @param {JQuery<HTMLElement>} $navBar
42
+ */
43
+ extendNavBar($navBar) {}
44
+ }
@@ -110,9 +110,6 @@
110
110
  display: block;
111
111
  }
112
112
  }
113
- &.hide {
114
- display: none;
115
- }
116
113
  }
117
114
  }
118
115
 
@@ -60,9 +60,28 @@
60
60
  left: 0;
61
61
  z-index: 1;
62
62
  }
63
- &.BRpageloading img {
63
+ &.BRpageloading {
64
64
  // Don't show the alt text while loading
65
- color: transparent;
65
+ img {
66
+ color: transparent;
67
+ }
68
+
69
+ // src can be set async, so hide the image if it's not set
70
+ img:not([src]) {
71
+ display: none;
72
+ }
73
+
74
+ &::after {
75
+ display: block;
76
+ content: "";
77
+ width: 20px;
78
+ height: 20px;
79
+ position: absolute;
80
+ left: 50%;
81
+ top: 50%;
82
+ transform: translate(-50%, -50%);
83
+ background: url("images/loading.gif") center/20px no-repeat;
84
+ }
66
85
  }
67
86
  &.BRemptypage {
68
87
  background: transparent;
@@ -81,6 +81,10 @@
81
81
  height: 30px;
82
82
  }
83
83
 
84
+ .controls .hide {
85
+ display: none;
86
+ }
87
+
84
88
  @keyframes slideUp {
85
89
  from {
86
90
  opacity: 0;