@internetarchive/bookreader 5.0.0-32-shadydom3 → 5.0.0-34

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,5 @@
1
1
  /* global BookReader, BookReaderJSIAinit */
2
+ import { extraVolOptions, custvolumesManifest } from './ia-multiple-volumes-manifest.js';
2
3
 
3
4
  /**
4
5
  * This is how Internet Archive loads bookreader
@@ -75,6 +76,18 @@ const initializeBookReader = (brManifest) => {
75
76
  }
76
77
  };
77
78
 
79
+ window.initializeBookReader = initializeBookReader;
80
+
81
+ const multiVolume = document.querySelector('#multi-volume');
82
+ multiVolume.addEventListener('click', () => {
83
+ // remove everything
84
+ $('#BookReader').empty();
85
+ delete window.br;
86
+ // and re-mount with a new book
87
+ BookReaderJSIAinit(custvolumesManifest, extraVolOptions);
88
+ });
89
+
90
+
78
91
  const fetchBookManifestAndInitializeBookreader = async (iaMetadata) => {
79
92
  document.querySelector('input[name="itemMD"]').checked = true;
80
93
  iaBookReader.item = iaMetadata;
@@ -38,9 +38,6 @@
38
38
  <!-- IA scripts -->
39
39
  <script src="https://archive.org/bookreader/BookReaderJSIA.js"></script>
40
40
 
41
- <!-- IA fetch demo -->
42
- <script type="module" src="IADemoBr.js"></script>
43
-
44
41
  </head>
45
42
 
46
43
  <body>
@@ -80,6 +77,9 @@
80
77
  <p>Features behind signed in gate: Bookmarks</p>
81
78
  <p>Logged In Status: <span id="logged-in-status">Logged Out</span></p>
82
79
  </div>
80
+ <div class="demo">
81
+ <button id="multi-volume">Multiple books</button>
82
+ </div>
83
83
  <div class="demo">
84
84
  <button id="start-fs">Start at Fullscreen</button>
85
85
  </div>
@@ -124,5 +124,8 @@
124
124
  placeholder.innerHTML = 'Dependencies are complete, bookreader has loaded';
125
125
  });
126
126
  </script>
127
+
128
+ <!-- IA fetch demo -->
129
+ <script type="module" src="IADemoBr.js"></script>
127
130
  </body>
128
131
  </html>
@@ -0,0 +1,170 @@
1
+ /* eslint-disable no-useless-escape */
2
+ const extraVolOptions = {
3
+ "isBeta": false,
4
+ "el": "#BookReader",
5
+ "enableBookTitleLink": false,
6
+ "bookUrlText": null,
7
+ "startFullscreen": false,
8
+ "initialSearchTerm": null,
9
+ "onePage": {
10
+ "autofit": "auto"
11
+ },
12
+ "showToolbar": false,
13
+ "autoResize": false,
14
+ "enableFSLogoShortcut": true,
15
+ "enableBookmarks": true,
16
+ "enableMultipleBooks": true,
17
+ "purchase_url": "",
18
+ "multipleBooksList": {
19
+ "by_subprefix": {
20
+ "book1/GPORFP": {
21
+ "url_path": "/details/SubBookTest",
22
+ "file_subprefix": "book1/GPORFP",
23
+ "title": "book1/GPORFP.pdf",
24
+ "file_source": "/book1/GPORFP_jp2.zip",
25
+ "orig_sort": 0
26
+ },
27
+ "subdir/book2/brewster_kahle_internet_archive": {
28
+ "url_path": "/details/SubBookTest/subdir/book2/brewster_kahle_internet_archive",
29
+ "file_subprefix": "subdir/book2/brewster_kahle_internet_archive",
30
+ "title": "subdir/book2/brewster_kahle_internet_archive.pdf",
31
+ "file_source": "/subdir/book2/brewster_kahle_internet_archive_jp2.zip",
32
+ "orig_sort": 1
33
+ },
34
+ "subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume": {
35
+ "url_path": "/details/SubBookTest/subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume",
36
+ "file_subprefix": "subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume",
37
+ "title": "subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume.pdf",
38
+ "file_source": "/subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume_jp2.zip",
39
+ "orig_sort": 2
40
+ }
41
+ },
42
+ "main_dir": "/2/items/SubBookTest"
43
+ }
44
+ };
45
+ const custvolumesManifest = {
46
+ "data": {
47
+ "streamOnly": false,
48
+ "isRestricted": false,
49
+ "id": "SubBookTest",
50
+ "subPrefix": "book1/GPORFP",
51
+ "olHost": "https://openlibrary.org",
52
+ "bookUrl": "/details/SubBookTest",
53
+ "downloadUrls": [
54
+ [
55
+ "PDF",
56
+ "//archive.org/download/SubBookTest/book1/GPORFP.pdf"
57
+ ],
58
+ [
59
+ "ePub",
60
+ "//archive.org/download/SubBookTest/book1/GPORFP.epub"
61
+ ],
62
+ [
63
+ "Plain Text",
64
+ "//archive.org/download/SubBookTest/book1/GPORFP_djvu.txt"
65
+ ],
66
+ [
67
+ "DAISY",
68
+ "//archive.org/download/SubBookTest/book1/GPORFP_daisy.zip"
69
+ ],
70
+ [
71
+ "Kindle",
72
+ "//archive.org/download/SubBookTest/book1/GPORFP.mobi"
73
+ ]
74
+ ]
75
+ },
76
+ "brOptions": {
77
+ "bookId": "SubBookTest",
78
+ "bookPath": "/2/items/SubBookTest/book1/GPORFP",
79
+ "imageFormat": "jp2",
80
+ "server": "ia800304.us.archive.org",
81
+ "subPrefix": "book1/GPORFP",
82
+ "zip": "/2/items/SubBookTest/book1/GPORFP_jp2.zip",
83
+ "bookTitle": "Test with sub-dirs",
84
+ "ppi": "600",
85
+ "defaultStartLeaf": 0,
86
+ "pageProgression": "lr",
87
+ "vars": {
88
+ "bookId": "SubBookTest",
89
+ "bookPath": "/2/items/SubBookTest/book1/GPORFP",
90
+ "server": "ia800304.us.archive.org",
91
+ "subPrefix": "book1/GPORFP"
92
+ },
93
+ "plugins": {
94
+ "textSelection": {
95
+ "enabled": true,
96
+ "singlePageDjvuXmlUrl": "https://{{server}}/BookReader/BookReaderGetTextWrapper.php?path={{bookPath|urlencode}}_djvu.xml&mode=djvu_xml&page={{pageIndex}}"
97
+ }
98
+ },
99
+ "data": [
100
+ [
101
+ {
102
+ "width": 5213,
103
+ "height": 6566,
104
+ "uri": "https://ia800304.us.archive.org/BookReader/BookReaderImages.php?zip=/2/items/SubBookTest/book1/GPORFP_jp2.zip&file=GPORFP_jp2/GPORFP_0000.jp2&id=SubBookTest",
105
+ "leafNum": 0,
106
+ "uri_2": {
107
+ "link": "https://archive.org/download/SubBookTest/book1/GPORFP_jp2.zip/GPORFP_jp2%2FGPORFP_0000.jp2",
108
+ "base_params": "ext=jpg"
109
+ },
110
+ "pageType": "Title",
111
+ "pageSide": "R"
112
+ }
113
+ ]
114
+ ]
115
+ },
116
+ "lendingInfo": {
117
+ "lendingStatus": null,
118
+ "userid": 0,
119
+ "isAdmin": false,
120
+ "isArchiveOrgLending": false,
121
+ "isOpenLibraryLending": false,
122
+ "isLendingRequired": false,
123
+ "isBrowserBorrowable": false,
124
+ "isPrintDisabledOnly": false,
125
+ "isAvailable": false,
126
+ "isAvailableForBrowsing": false,
127
+ "userHasBorrowed": false,
128
+ "userHasBrowsed": false,
129
+ "userOnWaitingList": false,
130
+ "userHoldIsReady": false,
131
+ "userIsPrintDisabled": false,
132
+ "shouldProtectImages": false,
133
+ "daysLeftOnLoan": 0,
134
+ "secondsLeftOnLoan": 0,
135
+ "loansUrl": "",
136
+ "bookUrl": "",
137
+ "loanCount": 0,
138
+ "totalWaitlistCount": 0,
139
+ "userWaitlistPosition": -1,
140
+ "maxLoans": 10,
141
+ "loanRecord": [
142
+
143
+ ]
144
+ },
145
+ "metadata": {
146
+ "identifier": "SubBookTest",
147
+ "title": "Test with sub-dirs",
148
+ "mediatype": "texts",
149
+ "collection": [
150
+ "opensource",
151
+ "community"
152
+ ],
153
+ "publicdate": "2009-07-08 23:10:10",
154
+ "addeddate": "2009-07-08 23:09:09",
155
+ "uploader": "mang@archive.org",
156
+ "updatedate": "2009-07-08 23:10:24",
157
+ "updater": "mang@archive",
158
+ "language": "English",
159
+ "identifier-access": "http://www.archive.org/details/SubBookTest",
160
+ "identifier-ark": "ark:/13960/t08w3w21h",
161
+ "ppi": "600",
162
+ "ocr": "ABBYY FineReader 8.0",
163
+ "repub_state": "4",
164
+ "noindex": "true",
165
+ "curation": "[curator]validator@archive.org[/curator][date]20140402011645[/date][comment]checked for malware[/comment]",
166
+ "backup_location": "ia903603_14"
167
+ }
168
+ };
169
+
170
+ export { custvolumesManifest, extraVolOptions };
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ # 5.0.0-34
2
+ Dev: udpate test dependencies @cdrini
3
+ Fix: Update hyphen stitching regex to include dangling "¬" @cdrini
4
+ Fix: pop open multiple files menu at proper width @iisa
5
+
6
+ # 5.0.0-33
7
+ Fix: restricted books get cover image @iisa
8
+
9
+ # 5.0.0-32
10
+ Fix: fetch bookmarks from service when logged in @iisa
11
+ Fix: adjust css to help shadydom render properly @iisa
1
12
  # 5.0.0-31
2
13
  Fix: modal loads into dom from `<ia-bookreader>` @iisa
3
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-32-shadydom3",
3
+ "version": "5.0.0-34",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -45,41 +45,41 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@babel/core": "7.15.0",
48
- "@babel/eslint-parser": "^7.15.7",
48
+ "@babel/eslint-parser": "^7.16.5",
49
49
  "@babel/plugin-proposal-class-properties": "^7.14.5",
50
50
  "@babel/plugin-proposal-decorators": "^7.14.5",
51
51
  "@babel/preset-env": "7.15.0",
52
52
  "@open-wc/testing": "^2.5.33",
53
53
  "@open-wc/testing-karma": "^4.0.9",
54
- "@types/jest": "27.0.1",
54
+ "@types/jest": "^27.4.0",
55
55
  "@webcomponents/webcomponentsjs": "^2.6.0",
56
56
  "babel-loader": "8.2.2",
57
- "codecov": "3.8.3",
57
+ "codecov": "^3.8.3",
58
58
  "concurrently": "6.0.2",
59
59
  "core-js": "3.16.2",
60
60
  "cpx2": "3.0.0",
61
61
  "eslint": "^7.32.0",
62
- "eslint-plugin-no-jquery": "^2.6.0",
63
- "eslint-plugin-testcafe": "0.2.1",
62
+ "eslint-plugin-no-jquery": "^2.7.0",
63
+ "eslint-plugin-testcafe": "^0.2.1",
64
64
  "hammerjs": "^2.0.8",
65
65
  "http-server": "0.12.3",
66
66
  "iso-language-codes": "1.1.0",
67
- "jest": "27.0.6",
67
+ "jest": "^27.4.7",
68
68
  "jquery": "1.11.3",
69
69
  "jquery-colorbox": "1.6.4",
70
70
  "jquery-ui": "1.12.1",
71
71
  "jquery-ui-touch-punch": "0.2.3",
72
72
  "jquery.browser": "0.1.0",
73
73
  "jquery.mmenu": "5.6.5",
74
- "karma-coverage": "^2.0.3",
74
+ "karma-coverage": "^2.1.0",
75
75
  "live-server": "1.2.1",
76
76
  "node-fetch": "2.6.1",
77
77
  "regenerator-runtime": "0.13.9",
78
78
  "sass": "1.38.2",
79
- "sinon": "11.1.2",
79
+ "sinon": "^12.0.1",
80
80
  "soundmanager2": "2.97.20170602",
81
81
  "svgo": "2.4.0",
82
- "testcafe": "^1.16.1",
82
+ "testcafe": "^1.18.3",
83
83
  "testcafe-browser-provider-browserstack": "^1.13.2-alpha.1",
84
84
  "webpack": "5.51.1",
85
85
  "webpack-cli": "4.8.0"
@@ -3,6 +3,7 @@ import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
3
3
  // eslint-disable-next-line no-unused-vars
4
4
  import { ModalManager } from '@internetarchive/modal-manager';
5
5
  import { css, html, LitElement } from 'lit-element';
6
+ import { nothing } from 'lit-html';
6
7
  import SearchProvider from './search/search-provider.js';
7
8
  import DownloadProvider from './downloads/downloads-provider.js';
8
9
  import VisualAdjustmentProvider from './visual-adjustments/visual-adjustments-provider.js';
@@ -146,6 +147,9 @@ export class BookNavigator extends LitElement {
146
147
  };
147
148
  }
148
149
 
150
+ get isWideEnoughToOpenMenu() {
151
+ return this.brWidth >= 640;
152
+ }
149
153
  /**
150
154
  * Instantiates books submenus & their update callbacks
151
155
  *
@@ -180,8 +184,7 @@ export class BookNavigator extends LitElement {
180
184
  /* refresh br instance reference */
181
185
  this.bookreader = brInstance;
182
186
  }
183
- const wideEnoughToOpenMenu = this.brWidth >= 640;
184
- if (wideEnoughToOpenMenu && !searchUpdates?.searchCanceled) {
187
+ if (this.isWideEnoughToOpenMenu && !searchUpdates?.searchCanceled) {
185
188
  /* open side search menu */
186
189
  setTimeout(() => {
187
190
  this.updateSideMenu('search', 'open');
@@ -213,7 +216,12 @@ export class BookNavigator extends LitElement {
213
216
  this.bookreader = brInstance;
214
217
  }
215
218
  this.updateMenuContents();
216
- this.updateSideMenu('volumes', 'open');
219
+ if (this.isWideEnoughToOpenMenu) {
220
+ /* open side search menu */
221
+ setTimeout(() => {
222
+ this.updateSideMenu('volumes', 'open');
223
+ });
224
+ }
217
225
  }
218
226
  });
219
227
  }
@@ -417,6 +425,7 @@ export class BookNavigator extends LitElement {
417
425
  this.lendingStatus = lendingStatus;
418
426
  this.isAdmin = isAdmin;
419
427
  this.bookReaderCannotLoad = previewType === 'singlePagePreview';
428
+ this.emitLoadingStatusUpdate(true);
420
429
  });
421
430
  window.addEventListener('BRJSIA:PostInit', ({ detail }) => {
422
431
  const { isRestricted, downloadURLs } = detail;
@@ -491,20 +500,20 @@ export class BookNavigator extends LitElement {
491
500
  this.dispatchEvent(event);
492
501
  }
493
502
 
494
- get loadingClass() {
495
- return !this.bookReaderLoaded ? 'loading' : '';
503
+ get itemImage() {
504
+ const identifier = this.itemMD?.metadata.identifier;
505
+ const url = `https://${this.baseHost}/services/img/${identifier}`;
506
+ return html`<img class="cover-img" src=${url} alt="cover image for ${identifier}">`;
496
507
  }
497
508
 
498
- get itemImage() {
499
- const url = `https://${this.baseHost}/services/img/${this.item.metadata.identifier}`;
500
- return html`<img class="cover-img" src=${url} alt="cover image for ${this.item.metadata.identifier}">`;
509
+ get placeholder() {
510
+ return html`<div class="placeholder">${this.itemImage}</div>`;
501
511
  }
502
512
 
503
513
  render() {
504
- const placeholder = this.bookReaderCannotLoad ? this.itemImage : this.loader;
505
- return html`<div id="book-navigator" class="${this.loadingClass}">
506
- ${placeholder}
507
- <slot name="main"></slot>
514
+ return html`<div id="book-navigator__root">
515
+ ${this.bookReaderCannotLoad ? this.placeholder : nothing}
516
+ ${!this.bookReaderCannotLoad ? html`<slot name="main"></slot>` : nothing}
508
517
  </div>
509
518
  `;
510
519
  }
@@ -512,13 +521,20 @@ export class BookNavigator extends LitElement {
512
521
  static get styles() {
513
522
  return css`
514
523
  :host,
515
- #book-navigator,
524
+ #book-navigator__root,
516
525
  slot,
517
526
  slot > * {
518
527
  display: block;
519
528
  height: inherit;
520
529
  width: inherit;
521
530
  }
531
+ .placeholder {
532
+ display: flex;
533
+ align-items: center;
534
+ justify-content: center;
535
+ flex-direction: column;
536
+ margin: 5%;
537
+ }
522
538
  .cover-img {
523
539
  max-height: 300px;
524
540
  }
@@ -16,7 +16,7 @@ export default class BookmarksProvider {
16
16
  signedIn,
17
17
  bookreader,
18
18
  modal,
19
- onProviderChange
19
+ onProviderChange,
20
20
  } = options;
21
21
 
22
22
  const referrerStr = `referer=${encodeURIComponent(location.href)}`;
@@ -142,8 +142,8 @@ class IABookmarks extends LitElement {
142
142
  if (this.displayMode === 'login') {
143
143
  return;
144
144
  }
145
+ this.fetchUserBookmarks();
145
146
  this.setBREventListeners();
146
- this.initializeBookmarks();
147
147
  }
148
148
 
149
149
  updateDisplay() {
@@ -153,6 +153,9 @@ class IABookmarks extends LitElement {
153
153
  }
154
154
 
155
155
  fetchUserBookmarks() {
156
+ if (!this.api.identifier) {
157
+ return;
158
+ }
156
159
  this.fetchBookmarks()
157
160
  .then(() => {
158
161
  this.initializeBookmarks();
@@ -227,7 +230,7 @@ class IABookmarks extends LitElement {
227
230
  }
228
231
 
229
232
  fetchBookmarks() {
230
- return this.api.getAll().then((res) => {
233
+ return this.api.getAll().then(res => res.text()).then((res) => {
231
234
  let response;
232
235
  try {
233
236
  response = JSON.parse(res);
@@ -52,6 +52,10 @@ export class IaBookReader extends LitElement {
52
52
  }
53
53
  }
54
54
 
55
+ get itemNav() {
56
+ return this.shadowRoot.querySelector('ia-item-navigator');
57
+ }
58
+
55
59
  /** Creates modal DOM & attaches to `<body>` */
56
60
  setModalManager() {
57
61
  let modalManager = document.querySelector('modal-manager');
@@ -95,11 +99,10 @@ export class IaBookReader extends LitElement {
95
99
  }
96
100
 
97
101
  if (action === 'open') {
98
- this.itemNav.openShortcut(menuId);
99
- this.openShortcut(menuId);
102
+ this.itemNav?.openShortcut(menuId);
100
103
  } else if (action === 'toggle') {
101
- this.itemNav.openMenu(menuId);
102
- this.itemNav.toggleMenu();
104
+ this.itemNav?.openMenu(menuId);
105
+ this.itemNav?.toggleMenu();
103
106
  }
104
107
  }
105
108
 
@@ -97,7 +97,10 @@ export default class PageChunk {
97
97
  * @return {string}
98
98
  */
99
99
  static _removeDanglingHyphens(text) {
100
- return text.replace(/-\s+/g, '');
100
+ // Some books mis-OCR a dangling hyphen as a ¬ (mathematical not sign) . Since in math
101
+ // the not sign should not appear followed by a space, we think we can safely assume
102
+ // this should be replaced.
103
+ return text.replace(/[-¬]\s+/g, '');
101
104
  }
102
105
  }
103
106
 
@@ -111,11 +111,6 @@ describe('<book-navigator>', () => {
111
111
 
112
112
  it('creates an item image from metadata', async () => {
113
113
  const el = fixtureSync(container());
114
- el.item = {
115
- metadata: { identifier: 'foo' },
116
- };
117
- await elementUpdated(el);
118
-
119
114
  const itemImage = fixtureSync(el.itemImage);
120
115
  expect(itemImage).to.be.instanceOf(HTMLImageElement);
121
116
  expect(itemImage.getAttribute('class')).to.equal('cover-img');
@@ -225,6 +220,18 @@ describe('<book-navigator>', () => {
225
220
 
226
221
  describe('Controlling Menu Side Panel & Shortcuts', () => {
227
222
  describe('Side Menu Panels', () => {
223
+ it('`isWideEnoughToOpenMenu` checks if menu should be open', async () => {
224
+ const el = fixtureSync(container());
225
+ el.brWidth = 300;
226
+ await el.elementUpdated;
227
+
228
+ expect(el.isWideEnoughToOpenMenu).to.equal(false);
229
+
230
+ el.brWidth = 641;
231
+ await el.elementUpdated;
232
+
233
+ expect(el.isWideEnoughToOpenMenu).to.equal(true);
234
+ });
228
235
  describe('Control which side menu to toggle open by using: `this.updateSideMenu`', () => {
229
236
  it('Emits `@updateSideMenu` to signal which menu gets the update', async () => {
230
237
  const el = fixtureSync(container());
@@ -0,0 +1,57 @@
1
+ import {
2
+ html,
3
+ fixtureSync,
4
+ expect,
5
+ fixtureCleanup,
6
+ } from '@open-wc/testing';
7
+ import '../../../../src/BookNavigator/bookmarks/ia-bookmarks.js';
8
+ import sinon from 'sinon';
9
+
10
+ afterEach(() => {
11
+ sinon.restore();
12
+ fixtureCleanup();
13
+ });
14
+
15
+ describe('<ia-bookmarks>', () => {
16
+ it('uses `setup` to start component', async () => {
17
+ const el = fixtureSync(html`<ia-bookmarks></ia-bookmarks>`);
18
+ await el.updateComplete;
19
+
20
+ let fetchHappened = false;
21
+ el.bookreader.bookId = 'foo';
22
+ el.displayMode = 'bookmarks';
23
+
24
+ el.fetchBookmarks = async () => {
25
+ fetchHappened = true;
26
+ return await Promise.resolve();
27
+ };
28
+
29
+ const fetchSpy = sinon.spy(el, 'fetchUserBookmarks');
30
+
31
+ el.setup();
32
+ await el.updateComplete;
33
+
34
+ expect(fetchSpy.callCount).to.equal(1);
35
+ expect(fetchHappened).to.equal(true);
36
+ });
37
+ it('does not fetch user bookmarks if displayMode = login', async () => {
38
+ const el = fixtureSync(html`<ia-bookmarks></ia-bookmarks>`);
39
+ await el.updateComplete;
40
+
41
+ let fetchHappened = false;
42
+ el.displayMode = 'login';
43
+
44
+ el.fetchBookmarks = async () => {
45
+ fetchHappened = true;
46
+ return await Promise.resolve();
47
+ };
48
+
49
+ const fetchSpy = sinon.spy(el, 'fetchUserBookmarks');
50
+
51
+ el.setup();
52
+ await el.updateComplete;
53
+
54
+ expect(fetchSpy.callCount).to.equal(0);
55
+ expect(fetchHappened).to.equal(false);
56
+ });
57
+ });