@internetarchive/bookreader 5.0.0-48-alpha2 → 5.0.0-49-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 (35) hide show
  1. package/BookReader/BookReader.js +1 -1
  2. package/BookReader/BookReader.js.map +1 -1
  3. package/BookReader/ia-bookreader-bundle.js +89 -89
  4. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  5. package/BookReaderDemo/IADemoBr.js +2 -0
  6. package/CHANGELOG.md +4 -0
  7. package/package.json +2 -2
  8. package/src/BookNavigator/book-navigator.js +2 -2
  9. package/src/BookNavigator/bookmarks/ia-bookmarks.js +4 -4
  10. package/src/BookNavigator/downloads/downloads-provider.js +42 -10
  11. package/src/BookNavigator/downloads/downloads.js +47 -83
  12. package/src/BookNavigator/volumes/volumes-provider.js +1 -1
  13. package/src/BookReader/BookModel.js +0 -29
  14. package/src/BookReader/Mode1UpLit.js +1 -1
  15. package/src/BookReader/Mode2Up.js +9 -34
  16. package/src/BookReader/ModeThumb.js +1 -3
  17. package/src/BookReader/Navbar/Navbar.js +8 -5
  18. package/src/BookReader/Toolbar/Toolbar.js +3 -30
  19. package/src/BookReader.js +65 -338
  20. package/src/plugins/plugin.autoplay.js +4 -4
  21. package/src/plugins/plugin.chapters.js +2 -2
  22. package/src/plugins/search/plugin.search.js +6 -6
  23. package/src/plugins/search/view.js +2 -2
  24. package/src/plugins/tts/plugin.tts.js +2 -2
  25. package/src/util/manifestGenerator.js +0 -0
  26. package/tests/e2e/models/Navigation.js +1 -1
  27. package/tests/jest/BookNavigator/book-navigator.test.js +19 -15
  28. package/tests/jest/BookReader/BookModel.test.js +31 -11
  29. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +4 -4
  30. package/tests/jest/BookReader/Mode1UpLit.test.js +5 -1
  31. package/tests/jest/BookReader/Mode2Up.test.js +8 -8
  32. package/tests/jest/BookReader.test.js +0 -35
  33. package/tests/jest/plugins/plugin.autoplay.test.js +2 -2
  34. package/tests/jest/plugins/plugin.chapters.test.js +2 -3
  35. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
@@ -82,6 +82,8 @@ const initializeBookReader = (brManifest) => {
82
82
  // we expect this at the global level
83
83
  BookReaderJSIAinit(brManifest.data, options);
84
84
 
85
+ const isRestricted = brManifest.data.isRestricted;
86
+ window.dispatchEvent(new CustomEvent('contextmenu', { detail: { isRestricted } }));
85
87
  if (customAutoflipParams.autoflip) {
86
88
  br.autoToggle(customAutoflipParams);
87
89
  }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 5.0.0-48
2
+ - Fix: move analytics to sample bucket @iisa
3
+ - Dev: update dependencies (concurrently, jest) @renovate
4
+
1
5
  # 5.0.0-47
2
6
  - Fix: XSS vulnerability in search results @latonv
3
7
  - Dev: Update jQuery to v3 **BREAKING** @cdrini
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-48-alpha2",
3
+ "version": "5.0.0-49-a1",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,7 +46,7 @@
46
46
  "@babel/plugin-proposal-class-properties": "7.16.7",
47
47
  "@babel/plugin-proposal-decorators": "7.17.9",
48
48
  "@babel/preset-env": "7.16.11",
49
- "@open-wc/testing-helpers": "^2.1.3",
49
+ "@open-wc/testing-helpers": "^2.1.4",
50
50
  "@types/jest": "^29.1.1",
51
51
  "@webcomponents/webcomponentsjs": "^2.6.0",
52
52
  "babel-loader": "8.2.5",
@@ -449,10 +449,10 @@ export class BookNavigator extends LitElement {
449
449
  /** Display an element's context menu */
450
450
  manageContextMenuVisibility(e) {
451
451
  if (window.archive_analytics) {
452
- window.archive_analytics?.send_event_no_sampling(
452
+ window.archive_analytics?.send_event(
453
453
  'BookReader',
454
454
  `contextmenu-${this.bookIsRestricted ? 'restricted' : 'unrestricted'}`,
455
- e.target.classList.value
455
+ e.target?.classList?.value
456
456
  );
457
457
  }
458
458
  if (!this.bookIsRestricted) {
@@ -213,8 +213,8 @@ class IABookmarks extends LitElement {
213
213
  color: this.getBookmarkColor(color) ? color : this.defaultColor.id,
214
214
  };
215
215
 
216
- const page = IABookmarks.formatPage(this.bookreader.getPageNum(leafNum));
217
- const thumbnail = this.bookreader.getPageURI(`${leafNum}`.replace(/\D/g, ''), 32); // Request thumbnail 1/32 the size of original image
216
+ const page = IABookmarks.formatPage(this.bookreader.book.getPageNum(leafNum));
217
+ const thumbnail = this.bookreader.book.getPageURI(`${leafNum}`.replace(/\D/g, ''), 32); // Request thumbnail 1/32 the size of original image
218
218
  const bookmark = {
219
219
  ...nomalizedParams,
220
220
  id: leafNum,
@@ -297,7 +297,7 @@ class IABookmarks extends LitElement {
297
297
  const pageBookmark = this.getBookmark(pageID);
298
298
  const bookmarkState = pageBookmark ? 'filled' : 'hollow';
299
299
  // eslint-disable-next-line
300
- const pageData = this.bookreader._models.book.getPage(pageID);
300
+ const pageData = this.bookreader.book.getPage(pageID);
301
301
  const { isViewable } = pageData;
302
302
 
303
303
  if (!isViewable) { return; }
@@ -417,7 +417,7 @@ class IABookmarks extends LitElement {
417
417
 
418
418
  bookmarkSelected({ detail }) {
419
419
  const { leafNum } = detail.bookmark;
420
- this.bookreader.jumpToPage(`${this.bookreader.getPageNum(`${leafNum}`.replace(/\D/g, ''))}`);
420
+ this.bookreader.jumpToPage(`${this.bookreader.book.getPageNum(`${leafNum}`.replace(/\D/g, ''))}`);
421
421
  this.activeBookmarkID = leafNum;
422
422
  }
423
423
 
@@ -2,16 +2,46 @@ import { html } from 'lit';
2
2
  import '@internetarchive/icon-dl/icon-dl';
3
3
  import './downloads';
4
4
 
5
+ const menuBase = {
6
+ pdf: {
7
+ type: 'Encrypted Adobe PDF',
8
+ url: '#',
9
+ note: 'PDF files contain high quality images of pages.',
10
+ },
11
+ lcppdf: {
12
+ type: 'Get LCP PDF',
13
+ url: '#',
14
+ note: 'PDF files contain high quality images of pages.',
15
+ },
16
+ lcpepub: {
17
+ type: 'Get LCP ePub',
18
+ url: '#',
19
+ note: 'ePub files are smaller in size, but may contain errors.',
20
+ },
21
+ epub: {
22
+ type: 'Encrypted Adobe ePub',
23
+ url: '#',
24
+ note: 'ePub files are smaller in size, but may contain errors.',
25
+ },
26
+ };
27
+
28
+ const publicMenuBase = {
29
+ pdf: "PDF",
30
+ epub: "ePub",
31
+ lcppdf: "LCP PDF",
32
+ lcpepub: "LCP ePub",
33
+ };
34
+
5
35
  export default class DownloadsProvider {
6
36
 
7
37
  constructor({ bookreader }) {
8
38
  this.icon = html`<ia-icon-dl style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon-dl>`;
9
- this.label = 'Read offline';
39
+ this.label = 'Downloadable files';
10
40
  this.menuDetails = '';
11
41
  this.downloads = [];
12
42
  this.id = 'downloads';
13
43
  this.component = '';
14
- this.isBookProtected = bookreader?.options?.protected ?? false;
44
+ this.isBookProtected = bookreader?.options?.isProtected || false;
15
45
  }
16
46
 
17
47
  update(downloadTypes) {
@@ -24,21 +54,23 @@ export default class DownloadsProvider {
24
54
  }
25
55
 
26
56
  /**
27
- * Restructures available download type data for the renderer
28
- * Sets global `downloads`
57
+ * Generates Download Menu Info for available types
58
+ * sets global `downloads`
29
59
  * @param availableTypes
30
60
  */
31
61
  computeAvailableTypes(availableTypes = []) {
32
62
  const menuData = availableTypes.reduce((found, incoming = []) => {
33
63
  const [ type = '', link = '' ] = incoming;
34
- if (!type) return found;
35
- let formattedType = type.toLowerCase();
36
- if ((formattedType === 'pdf' || formattedType === 'epub') && this.isBookProtected) {
37
- formattedType = `adobe${formattedType}`;
64
+ const formattedType = type.toLowerCase();
65
+ const downloadOption = menuBase[formattedType] || null;
66
+
67
+ if (downloadOption) {
68
+ const menuButtonText = this.isBookProtected ? menuBase[formattedType].type : publicMenuBase[formattedType];
69
+ const menuInfo = Object.assign({}, downloadOption, { url: link, type: menuButtonText});
70
+ found.push(menuInfo);
38
71
  }
39
- found[formattedType] = link;
40
72
  return found;
41
- }, {});
73
+ }, []);
42
74
 
43
75
  this.downloads = menuData;
44
76
  }
@@ -1,6 +1,5 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import buttonStyles from '../assets/button-base.js';
3
-
4
3
  export class IABookDownloads extends LitElement {
5
4
  static get properties() {
6
5
  return {
@@ -19,26 +18,6 @@ export class IABookDownloads extends LitElement {
19
18
  this.isBookProtected = false;
20
19
  }
21
20
 
22
- render() {
23
- return html`
24
- ${this.header}
25
- ${this.loanExpiryMessage}
26
- ${this.renderDownloadOptions()}
27
- `;
28
- }
29
-
30
- get header() {
31
- if (!this.renderHeader) {
32
- return nothing;
33
- }
34
- return html`
35
- <header>
36
- <h3>Read offline</h3>
37
- ${this.formatsCount}
38
- </header>
39
- `;
40
- }
41
-
42
21
  get formatsCount() {
43
22
  const count = this.downloads.length;
44
23
  return count ? html`<p>${count} format${count > 1 ? 's' : ''}</p>` : html``;
@@ -51,80 +30,65 @@ export class IABookDownloads extends LitElement {
51
30
  }
52
31
 
53
32
  renderDownloadOptions() {
54
- const downloadOptions = [];
55
- ['pdf', 'epub', 'lcppdf', 'lcpepub', 'adobepdf', 'adobeepub'].forEach(format => {
56
- if (this.downloads[format]) {
57
- downloadOptions.push(this.downloadOption(format, this.downloads[format]));
58
- }
59
- });
60
- return html`
61
- <ul>
62
- ${downloadOptions}
63
- </ul>
64
- `;
65
- }
66
-
67
- downloadOption(format, link) {
68
- if (/^adobe/.test(format)) {
69
- return html`
33
+ return this.downloads.map(option => (
34
+ html`
70
35
  <li>
71
- <a
72
- href="${link}"
73
- data-event-click-tracking="BookReader|Download-${format}"
74
- >${this.menuText[format].linkText}</a>
75
- ${this.menuText[format].message ?? nothing}
36
+ <a class="ia-button link primary" href="${option.url}">Get ${option.type}</a>
37
+ ${option.note ? html`<p>${option.note}</p>` : html``}
76
38
  </li>
77
- `;
39
+ `
40
+ ));
41
+ }
42
+
43
+ /**
44
+ * checks if downloads list contains an LCP option
45
+ * @return {boolean}
46
+ */
47
+ get hasLCPOption() {
48
+ const regex = /^(LCP)/g;
49
+ const lcpAvailable = this.downloads.some(option => option.type?.match(regex));
50
+ return lcpAvailable;
51
+ }
52
+
53
+ get header() {
54
+ if (!this.renderHeader) {
55
+ return nothing;
78
56
  }
79
57
  return html`
80
- <li>
81
- <a class="ia-button link primary"
82
- href="${link}"
83
- data-event-click-tracking="BookReader|Download-${format}"
84
- >${this.menuText[format].linkText}</a>
85
- ${this.menuText[format].message ? html`<p>${this.menuText[format].message}</p>` : nothing}
86
- </li>
58
+ <header>
59
+ <h3>Downloadable files</h3>
60
+ ${this.formatsCount}
61
+ </header>
87
62
  `;
88
63
  }
89
64
 
90
- get menuText() {
91
- return {
92
- pdf: {
93
- linkText: 'Get high-resolution PDF',
94
- message: html``,
95
- },
96
- epub: {
97
- linkText: 'Get text-based ebook',
98
- message: html`Smaller size. May contain some errors.`,
99
- },
100
- lcppdf: {
101
- linkText: 'Get high-resolution PDF',
102
- message: this.lcpNote,
103
- },
104
- lcpepub: {
105
- linkText: 'Get text-based ebook',
106
- message: html`Smaller size. May contain some errors. ${this.lcpNote}`,
107
- },
108
- adobepdf: {
109
- linkText:'Get legacy Adobe DRM PDF version here.',
110
- message: this.adobeNote,
111
- },
112
- adobeepub: {
113
- linkText: 'Get legacy Adobe DRM EPUB version here.',
114
- message: this.adobeNote,
115
- },
116
- };
117
- };
118
-
119
- get lcpNote() {
65
+ get accessProtectedBook() {
120
66
  return html`
121
- Requires having an LCP-compatible e-reader installed like Aldiko Next (<a href="https://apps.apple.com/us/app/aldiko-next/id1476410111">iOS</a>, <a href="https://play.google.com/store/apps/details?id=com.aldiko.android">Android</a>) or <a href="https://www.edrlab.org/software/thorium-reader/">Thorium</a>.
67
+ <p>To access downloaded books, you need Adobe-compliant software on your device. The Internet Archive will administer this loan, but Adobe may also collect some information.</p>
68
+ <a class="ia-button external primary" href="https://www.adobe.com/solutions/ebook/digital-editions/download.html" rel="noopener noreferrer" target="_blank">Install Adobe Digital Editions</a>
122
69
  `;
123
70
  }
124
71
 
125
- get adobeNote() {
72
+ get installSimplyEAldikoThoriumMsg() {
73
+ return html`
74
+ <p>For LCP downloads, make sure you have SimplyE or Aldiko Next installed on mobile or Thorium on desktop.</p>
75
+ <ul>
76
+ <li><a href="https://librarysimplified.org/simplye/" rel="noopener noreferrer nofollow" target="_blank">Install SimplyE</a></li>
77
+ <li><a href="https://www.demarque.com/en-aldiko" rel="noopener noreferrer nofollow" target="_blank">Install Aldiko</a></li>
78
+ <li><a href="https://www.edrlab.org/software/thorium-reader/" rel="noopener noreferrer nofollow" target="_blank">Install Thorium</a></li>
79
+ </ul>
80
+ `;
81
+ }
82
+
83
+ render() {
126
84
  return html`
127
- Requires a compatible e-reader like <a hef="https://www.adobe.com/solutions/ebook/digital-editions.html">Adobe Digital Editions</a>.
85
+ ${this.header}
86
+ ${this.loanExpiryMessage}
87
+ <ul>${this.renderDownloadOptions()}</ul>
88
+ ${this.hasLCPOption
89
+ ? this.installSimplyEAldikoThoriumMsg
90
+ : (this.isBookProtected ? this.accessProtectedBook : nothing)
91
+ }
128
92
  `;
129
93
  }
130
94
 
@@ -104,7 +104,7 @@ export default class VolumesProvider {
104
104
  if (!window.archive_analytics) {
105
105
  return;
106
106
  }
107
- window.archive_analytics?.send_event_no_sampling(
107
+ window.archive_analytics?.send_event(
108
108
  'BookReader',
109
109
  `VolumesSort|${orderBy}`,
110
110
  window.location.path,
@@ -26,39 +26,10 @@ export class BookModel {
26
26
 
27
27
  /** @type {{width: number, height: number}} memoize storage */
28
28
  this._medianPageSize = null;
29
- /** @deprecated @type {{width: number, height: number}} memoize storage */
30
- this._medianPageSizePixels = null;
31
29
  /** @type {[PageData[], number]} */
32
30
  this._getDataFlattenedCached = null;
33
31
  }
34
32
 
35
- /**
36
- * @deprecated Use getMedianPageSizeInches
37
- * Memoized
38
- * @return {{width: number, height: number}}
39
- */
40
- getMedianPageSize() {
41
- if (this._medianPageSizePixels) {
42
- return this._medianPageSizePixels;
43
- }
44
-
45
- // A little expensive but we just do it once
46
- const widths = [];
47
- const heights = [];
48
- for (let i = 0; i < this.getNumLeafs(); i++) {
49
- widths.push(this.getPageWidth(i));
50
- heights.push(this.getPageHeight(i));
51
- }
52
-
53
- widths.sort();
54
- heights.sort();
55
- this._medianPageSizePixels = {
56
- width: widths[Math.floor(widths.length / 2)],
57
- height: heights[Math.floor(heights.length / 2)]
58
- };
59
- return this._medianPageSizePixels;
60
- }
61
-
62
33
  /** Get median width/height of page in inches. Memoized for performance. */
63
34
  getMedianPageSizeInches() {
64
35
  if (this._medianPageSize) {
@@ -205,7 +205,7 @@ export class Mode1UpLit extends LitElement {
205
205
  this.throttledUpdateRenderedPages();
206
206
  this.br.displayedIndices = this.visiblePages.map(p => p.index);
207
207
  this.br.updateFirstIndex(this.br.displayedIndices[0]);
208
- this.br.updateNavIndexThrottled();
208
+ this.br._components.navbar.updateNavIndexThrottled();
209
209
  }
210
210
  if (changedProps.has('scale')) {
211
211
  const oldVal = changedProps.get('scale');
@@ -90,7 +90,6 @@ export class Mode2Up {
90
90
 
91
91
  this.displayedIndices = [this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR];
92
92
  this.br.displayedIndices = this.displayedIndices;
93
- this.br.updateToolbarZoom(this.br.reduce);
94
93
  this.br.trigger('pageChanged');
95
94
  }
96
95
 
@@ -118,7 +117,7 @@ export class Mode2Up {
118
117
 
119
118
  // Prepare view with new center to minimize visual glitches
120
119
  const drawNewSpread = true;
121
- this.prepareTwoPageView(oldCenter.percentageX, oldCenter.percentageY, drawNewSpread);
120
+ this.prepare(oldCenter.percentageX, oldCenter.percentageY, drawNewSpread);
122
121
  }
123
122
 
124
123
  /**
@@ -150,7 +149,7 @@ export class Mode2Up {
150
149
  * @param {number} centerPercentageY
151
150
  * @param {Boolean} drawNewSpread
152
151
  */
153
- prepareTwoPageView(centerPercentageX, centerPercentageY, drawNewSpread = false) {
152
+ prepare(centerPercentageX, centerPercentageY, drawNewSpread = false) {
154
153
  // Some decisions about two page view:
155
154
  //
156
155
  // Both pages will be displayed at the same height, even if they were different physical/scanned
@@ -241,7 +240,6 @@ export class Mode2Up {
241
240
  this.br.displayedIndices = [];
242
241
 
243
242
  this.drawLeafs();
244
- this.br.updateToolbarZoom(this.br.reduce);
245
243
  this.br.updateBrClasses();
246
244
 
247
245
  this.smoothZoomer = this.smoothZoomer || new ModeSmoothZoom(this);
@@ -391,7 +389,7 @@ export class Mode2Up {
391
389
 
392
390
  // Leaf edges
393
391
  this.br.twoPage.edgeWidth = spreadSize.totalLeafEdgeWidth; // The combined width of both edges
394
- this.br.twoPage.leafEdgeWidthL = this.br.leafEdgeWidth(this.br.twoPage.currentIndexL);
392
+ this.br.twoPage.leafEdgeWidthL = this.leafEdgeWidth(this.br.twoPage.currentIndexL);
395
393
  this.br.twoPage.leafEdgeWidthR = this.br.twoPage.edgeWidth - this.br.twoPage.leafEdgeWidthL;
396
394
 
397
395
 
@@ -528,21 +526,6 @@ export class Mode2Up {
528
526
  return spreadSize.reduce;
529
527
  }
530
528
 
531
- /**
532
- * Returns true if the pages extend past the edge of the view
533
- * @deprecated slated for deprecation by v5.0.0
534
- * @return {boolean}
535
- */
536
- isZoomedIn() {
537
- let isZoomedIn = false;
538
- if (this.br.twoPage.autofit != 'auto') {
539
- if (this.br.reduce < this.getAutofitReduce()) {
540
- isZoomedIn = true;
541
- }
542
- }
543
- return isZoomedIn;
544
- }
545
-
546
529
  calculateReductionFactors() {
547
530
  this.br.twoPage.reductionFactors = this.br.reductionFactors.concat([
548
531
  {
@@ -553,14 +536,6 @@ export class Mode2Up {
553
536
  this.br.twoPage.reductionFactors.sort(this.br._reduceSort);
554
537
  }
555
538
 
556
- /**
557
- * Set the cursor for two page view
558
- * @deprecated Since version 4.3.3. Will be deleted in version 5.0
559
- */
560
- setCursor() {
561
- console.warn('Call to deprecated method, Mode2Up.setCursor. No-op.');
562
- }
563
-
564
539
  /**
565
540
  * @param {Number|null} index to flip back one spread, pass index=null
566
541
  */
@@ -584,7 +559,7 @@ export class Mode2Up {
584
559
  if (prev.pageSide == 'R') index--;
585
560
  }
586
561
 
587
- this.br.updateNavIndexThrottled(index);
562
+ this.br._components.navbar.updateNavIndexThrottled(index);
588
563
 
589
564
  const previousIndices = this.book.getSpreadIndices(index);
590
565
 
@@ -614,8 +589,8 @@ export class Mode2Up {
614
589
  this.br.refs.$brContainer.addClass("BRpageFlipping");
615
590
  const leftLeaf = this.br.twoPage.currentIndexL;
616
591
 
617
- const oldLeafEdgeWidthL = this.br.leafEdgeWidth(this.br.twoPage.currentIndexL);
618
- const newLeafEdgeWidthL = this.br.leafEdgeWidth(newIndexL);
592
+ const oldLeafEdgeWidthL = this.leafEdgeWidth(this.br.twoPage.currentIndexL);
593
+ const newLeafEdgeWidthL = this.leafEdgeWidth(newIndexL);
619
594
  const leafEdgeTmpW = oldLeafEdgeWidthL - newLeafEdgeWidthL;
620
595
 
621
596
  const currWidthL = this.getPageWidth(leftLeaf);
@@ -782,7 +757,7 @@ export class Mode2Up {
782
757
  }
783
758
  if (index > this.br.lastDisplayableIndex()) return;
784
759
 
785
- this.br.updateNavIndexThrottled(index);
760
+ this.br._components.navbar.updateNavIndexThrottled(index);
786
761
 
787
762
  this.br.animating = true;
788
763
 
@@ -808,9 +783,9 @@ export class Mode2Up {
808
783
  flipRightToLeft(newIndexL, newIndexR) {
809
784
  this.br.refs.$brContainer.addClass("BRpageFlipping");
810
785
 
811
- const oldLeafEdgeWidthL = this.br.leafEdgeWidth(this.br.twoPage.currentIndexL);
786
+ const oldLeafEdgeWidthL = this.leafEdgeWidth(this.br.twoPage.currentIndexL);
812
787
  const oldLeafEdgeWidthR = this.br.twoPage.edgeWidth - oldLeafEdgeWidthL;
813
- const newLeafEdgeWidthL = this.br.leafEdgeWidth(newIndexL);
788
+ const newLeafEdgeWidthL = this.leafEdgeWidth(newIndexL);
814
789
  const newLeafEdgeWidthR = this.br.twoPage.edgeWidth - newLeafEdgeWidthL;
815
790
 
816
791
  const leafEdgeTmpW = oldLeafEdgeWidthR - newLeafEdgeWidthR;
@@ -177,7 +177,7 @@ export class ModeThumb {
177
177
  // shift viewModeOrder after clicking on thumbsnail leaf
178
178
  const nextModeID = this.br.viewModeOrder.shift();
179
179
  this.br.viewModeOrder.push(nextModeID);
180
- this.br.updateViewModeButton($('.viewmode'), 'twopg', 'Two-page view');
180
+ this.br._components.navbar.updateViewModeButton($('.viewmode'), 'twopg', 'Two-page view');
181
181
 
182
182
  this.br.trigger(EVENTS.fragmentChange);
183
183
  event.stopPropagation();
@@ -215,8 +215,6 @@ export class ModeThumb {
215
215
 
216
216
  // highlight current page
217
217
  this.br.$('.pagediv' + this.br.currentIndex()).addClass('BRpagedivthumb_highlight');
218
-
219
- this.br.updateToolbarZoom(this.br.reduce);
220
218
  }
221
219
 
222
220
  /**
@@ -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
  });
@@ -248,16 +251,16 @@ export class Navbar {
248
251
  getNavPageNumString(index) {
249
252
  const { br } = this;
250
253
  // Accessible index starts at 0 (alas) so we add 1 to make human
251
- const pageNum = br.getPageNum(index);
252
- const pageType = br.getPageProp(index, 'pageType');
253
- 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();
254
257
 
255
258
  if (!this.maxPageNum) {
256
259
  // Calculate Max page num (used for pagination display)
257
260
  let maxPageNum = 0;
258
261
  let pageNumVal;
259
262
  for (let i = 0; i < numLeafs; i++) {
260
- pageNumVal = br.getPageNum(i);
263
+ pageNumVal = br.book.getPageNum(i);
261
264
  if (!isNaN(pageNumVal) && pageNumVal > maxPageNum) {
262
265
  maxPageNum = pageNumVal;
263
266
  }
@@ -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
  */
@@ -216,7 +189,7 @@ export class Toolbar {
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) {
@@ -331,7 +304,7 @@ 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
@@ -351,7 +324,7 @@ 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">