@internetarchive/bookreader 5.0.0-25-02 → 5.0.0-26

Sign up to get free protection for your applications and to get access to all the features.
@@ -29,6 +29,10 @@
29
29
  <script type="module" src="../BookReader/bookreader-component-bundle.js"></script>
30
30
 
31
31
  <link rel="stylesheet" href="BookReaderDemo.css"/>
32
+
33
+ <!-- IA scripts -->
34
+ <script src="https://archive.org/bookreader/BookReaderJSIA.js"></script>
35
+
32
36
  </head>
33
37
 
34
38
  <body>
@@ -59,16 +63,94 @@
59
63
  <script id="pageUrl" type="text/javascript"></script>
60
64
 
61
65
  <script>
62
- var ocaid = location.href.match(/ocaid=([^&#]+)/i)[1];
66
+ // gather params here
67
+ const urlParams = new URLSearchParams(window.location.search);
68
+
69
+ const ocaid = urlParams.get('ocaid');
70
+ const openFullImmersionTheater = urlParams.get('view') === 'theater';
71
+ const ui = urlParams.get('ui');
72
+ const autoflip = urlParams.get('autoflip');
73
+ const searchTerm = urlParams.get('q');
63
74
 
64
75
  // Override options coming from IA
65
76
  BookReader.optionOverrides.imagesBaseURL = '/BookReader/images/';
66
77
 
78
+ const initializeBookReader = (brManifest) => {
79
+ console.log('initializeBookReader', brManifest);
80
+ const br = new BookReader();
81
+ const iaBookManifestUrl = '';
82
+
83
+ const customAutoflipParams = {
84
+ autoflip: !!autoflip,
85
+ flipSpeed: urlParams.flipSpeed || 2000,
86
+ flipDelay: urlParams.flipDelay || 5000
87
+ };
88
+
89
+ const options = {
90
+ el: '#BookReader',
91
+ /* Url plugin - IA uses History mode for URL */
92
+ // commenting these out as demo uses hash mode
93
+ // keeping them here for reference
94
+ // urlHistoryBasePath: `/details/{$ocaid}/`,
95
+ // resumeCookiePath: `/details/{$ocaid}/`,
96
+ // urlMode: 'history',
97
+ // Only reflect these params onto the URL
98
+ // urlTrackedParams: ['page', 'search', 'mode'],
99
+ /* End url plugin */
100
+ enableBookTitleLink: false,
101
+ bookUrlText: null,
102
+ startFullscreen: urlParams.view === 'theater',
103
+ initialSearchTerm: searchTerm ? searchTerm : '',
104
+ // leaving this option commented out bc we change given user agent on archive.org
105
+ // onePage: { autofit: <?=json_encode($this->ios ? 'width' : 'auto')?> },
106
+ showToolbar: false,
107
+ /* Multiple volumes */
108
+ // To show multiple volumes:
109
+ enableMultipleBooks: false, // turn this on
110
+ multipleBooksList: [], // populate this // TODO: get sample blob and tie into demo
111
+ /* End multiple volumes */
112
+ };
113
+
114
+ // we want to show item as embedded when ?ui=embed is in URI
115
+ if (ui === 'embed') {
116
+ options.mode = 1;
117
+ options.ui = 'embed';
118
+ }
119
+
120
+ // we expect this at the global level
121
+ BookReaderJSIAinit(brManifest.data, { ...options });
122
+
123
+ if (customAutoflipParams.autoflip) {
124
+ br.autoToggle(customAutoflipParams);
125
+ }
126
+ }
127
+
128
+ const fetchBookManifestAndInitializeBookreader = async (iaMetadata) => {
129
+ const {
130
+ metadata: {
131
+ identifier
132
+ },
133
+ } = iaMetadata;
134
+
135
+ const locator =`https://archive.org/bookreader/BookReaderJSLocate.php?format=json&subPrefix=&id=${identifier}`;
136
+ // Todo: move from `locator` to create `iaManifestUrl` url from `iaMetadata`
137
+ // so we can support multiple volumes
138
+ // const iaManifestUrl = `https://${server}/BookReader/BookReaderJSIA.php?format=jsonp&itemPath=${dir}&id=${identifier}`;
139
+
140
+ const manifest = await fetch(locator)
141
+ .then(response => response.json())
142
+
143
+ initializeBookReader(manifest);
144
+ }
145
+
67
146
  // Temp; Circumvent bug in BookReaderJSIA code
68
147
  window.Sentry = null;
69
-
70
- var script_url = 'https://archive.org/bookreader/BookReaderJSLocate.php?subPrefix=&id=' + ocaid;
71
- document.getElementById('pageUrl').src = script_url;
148
+ window.logError = function(e) {
149
+ console.error(e);
150
+ };
151
+ fetch(`https://archive.org/metadata/${ocaid}`)
152
+ .then(response => response.json())
153
+ .then(iaMetadata => fetchBookManifestAndInitializeBookreader(iaMetadata));
72
154
  </script>
73
155
 
74
156
  </body>
package/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 5.0.0-26
2
+ Fix: read aloud play/pause button @nsharma123
3
+ Dev: strict keyboard shortcuts @mc2
4
+ Dev: update IA demo page @iisa
5
+
1
6
  # 5.0.0-24
2
7
  Fix: book-nav side panel zoom out @mc2
3
8
  Dev: refactor zoom code @mc2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-25-02",
3
+ "version": "5.0.0-26",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -463,6 +463,7 @@ class IABookmarks extends LitElement {
463
463
  return html`
464
464
  <button
465
465
  class="ia-button primary"
466
+ tabindex="-1"
466
467
  ?disabled=${this.shouldEnableAddBookmarkButton}
467
468
  @click=${this.addBookmark}>
468
469
  Add bookmark
package/src/BookReader.js CHANGED
@@ -264,6 +264,15 @@ BookReader.prototype.setup = function(options) {
264
264
  useSrcSet: this.options.useSrcSet,
265
265
  reduceSet: this.reduceSet,
266
266
  });
267
+
268
+ /**
269
+ * Flag if BookReader has "focus" for keyboard shortcuts
270
+ * Initially true, set to false when:
271
+ * - BookReader scrolled out of view
272
+ * Set to true when:
273
+ * - BookReader scrolled into view
274
+ */
275
+ this.hasKeyFocus = true;
267
276
  };
268
277
 
269
278
  /**
@@ -662,87 +671,116 @@ BookReader.prototype.resize = function() {
662
671
  };
663
672
 
664
673
  /**
665
- * Binds keyboard event listeners
674
+ * Binds keyboard and keyboard focus event listeners
666
675
  */
667
- BookReader.prototype.setupKeyListeners = function() {
668
- var self = this;
676
+ BookReader.prototype.setupKeyListeners = function () {
669
677
 
670
- var KEY_PGUP = 33;
671
- var KEY_PGDOWN = 34;
672
- var KEY_END = 35;
673
- var KEY_HOME = 36;
674
-
675
- var KEY_LEFT = 37;
676
- var KEY_UP = 38;
677
- var KEY_RIGHT = 39;
678
- var KEY_DOWN = 40;
679
- // The minus(-) and equal(=) keys have different mappings for different browsers
680
- var KEY_MINUS = 189; // Chrome
681
- var KEY_MINUS_F = 173; // Firefox
682
- var KEY_NUMPAD_SUBTRACT = 109;
683
- var KEY_EQUAL = 187; // Chrome
684
- var KEY_EQUAL_F = 61; // Firefox
685
- var KEY_NUMPAD_ADD = 107;
686
-
687
- // We use document here instead of window to avoid a bug in jQuery on IE7
688
- $(document).on("keydown", function(e) {
689
-
690
- // Keyboard navigation
691
- switch (e.keyCode) {
692
- case KEY_PGUP:
693
- case KEY_UP:
694
- // In 1up mode page scrolling is handled by browser
695
- if (!utils.isInputActive() && self.constMode2up == self.mode) {
696
- e.preventDefault();
697
- self.prev();
698
- }
678
+ // Keyboard focus by BookReader in viewport
679
+ //
680
+ // Intersection observer and callback sets BookReader keyboard
681
+ // "focus" flag off when the BookReader is not in the viewport.
682
+ if (window.IntersectionObserver) {
683
+ const observer = new IntersectionObserver((entries) => {
684
+ entries.forEach((entry) => {
685
+ if (entry.intersectionRatio === 0) {
686
+ this.hasKeyFocus = false;
687
+ } else {
688
+ this.hasKeyFocus = true;
689
+ }
690
+ });
691
+ }, {
692
+ root: null,
693
+ rootMargin: '0px',
694
+ threshold: [0, 0.05, 1],
695
+ });
696
+ observer.observe(this.refs.$br[0]);
697
+ }
698
+
699
+ // Keyboard listeners
700
+ document.addEventListener('keydown', (e) => {
701
+
702
+ // Ignore if BookReader "focus" flag not set
703
+ if (!this.hasKeyFocus) {
704
+ return;
705
+ }
706
+
707
+ // Ignore if modifiers are active.
708
+ if (e.getModifierState('Control') ||
709
+ e.getModifierState('Alt') ||
710
+ e.getModifierState('Meta') ||
711
+ e.getModifierState('Win') /* hack for IE */) {
712
+ return;
713
+ }
714
+
715
+ // Ignore in input elements
716
+ if (utils.isInputActive()) {
717
+ return;
718
+ }
719
+
720
+ // KeyboardEvent code values:
721
+ // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
722
+ switch (e.key) {
723
+
724
+ // Page navigation
725
+ case "Home":
726
+ e.preventDefault();
727
+ this.first();
699
728
  break;
700
- case KEY_DOWN:
701
- case KEY_PGDOWN:
702
- if (!utils.isInputActive() && self.constMode2up == self.mode) {
703
- e.preventDefault();
704
- self.next();
705
- }
729
+ case "End":
730
+ e.preventDefault();
731
+ this.last();
706
732
  break;
707
- case KEY_END:
708
- if (!utils.isInputActive()) {
733
+ case "ArrowDown":
734
+ case "PageDown":
735
+ case "Down": // hack for IE and old Gecko
736
+ // In 1up and thumb mode page scrolling handled by browser
737
+ if (this.constMode2up === this.mode) {
709
738
  e.preventDefault();
710
- self.last();
739
+ this.next();
711
740
  }
712
741
  break;
713
- case KEY_HOME:
714
- if (!utils.isInputActive()) {
742
+ case "ArrowUp":
743
+ case "PageUp":
744
+ case "Up": // hack for IE and old Gecko
745
+ // In 1up and thumb mode page scrolling handled by browser
746
+ if (this.constMode2up === this.mode) {
715
747
  e.preventDefault();
716
- self.first();
748
+ this.prev();
717
749
  }
718
750
  break;
719
- case KEY_LEFT:
720
- if (!utils.isInputActive() && self.constModeThumb != self.mode) {
751
+ case "ArrowLeft":
752
+ case "Left": // hack for IE and old Gecko
753
+ // No y-scrolling in thumb mode
754
+ if (this.constModeThumb != this.mode) {
721
755
  e.preventDefault();
722
- self.left();
756
+ this.left();
723
757
  }
724
758
  break;
725
- case KEY_RIGHT:
726
- if (!utils.isInputActive() && self.constModeThumb != self.mode) {
759
+ case "ArrowRight":
760
+ case "Right": // hack for IE and old Gecko
761
+ // No y-scrolling in thumb mode
762
+ if (this.constModeThumb != this.mode) {
727
763
  e.preventDefault();
728
- self.right();
764
+ this.right();
729
765
  }
730
766
  break;
731
- case KEY_MINUS:
732
- case KEY_MINUS_F:
733
- case KEY_NUMPAD_SUBTRACT:
734
- if (!utils.isInputActive()) {
735
- e.preventDefault();
736
- self.zoom(-1);
737
- }
767
+ // Zoom
768
+ case '-':
769
+ case 'Subtract':
770
+ e.preventDefault();
771
+ this.zoom(-1);
738
772
  break;
739
- case KEY_EQUAL:
740
- case KEY_EQUAL_F:
741
- case KEY_NUMPAD_ADD:
742
- if (!utils.isInputActive()) {
743
- e.preventDefault();
744
- self.zoom(+1);
745
- }
773
+ case '+':
774
+ case '=':
775
+ case 'Add':
776
+ e.preventDefault();
777
+ this.zoom(1);
778
+ break;
779
+ // Fullscreen
780
+ case 'F':
781
+ case 'f':
782
+ e.preventDefault();
783
+ this.toggleFullscreen();
746
784
  break;
747
785
  }
748
786
  });
@@ -0,0 +1,190 @@
1
+ import BookReader from '@/src/BookReader.js';
2
+ import * as utils from '@/src/BookReader/utils.js';
3
+
4
+ let br;
5
+ beforeAll(() => {
6
+ document.body.innerHTML = '<div id="BookReader">';
7
+ br = new BookReader();
8
+ });
9
+
10
+ afterEach(() => {
11
+ jest.clearAllMocks();
12
+ });
13
+
14
+ /**
15
+ * Only run init() once. Otherwise multiple EventListeners will be added.
16
+ */
17
+ test('Initialzation enables IntersectionObserver and defaults', () => {
18
+ const observe = jest.fn();
19
+ window.IntersectionObserver = jest.fn(() => ({
20
+ observe
21
+ }));
22
+ br.init();
23
+ expect(br.hasKeyFocus).toBe(true);
24
+ expect(observe).toHaveBeenCalledTimes(1);
25
+ });
26
+
27
+ describe('Keyboard shortcuts turned off', () => {
28
+
29
+ test('Focus flag disables', () => {
30
+ br.next = jest.fn();
31
+ br.hasKeyFocus = false;
32
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'ArrowDown'});
33
+ keyEvent.preventDefault = jest.fn();
34
+ document.dispatchEvent(keyEvent);
35
+ expect(br.next).toHaveBeenCalledTimes(0);
36
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(0);
37
+ // Must reset for following tests
38
+ br.hasKeyFocus = true;
39
+ });
40
+
41
+ test('Input active disables', () => {
42
+ // eslint-disable-next-line no-import-assign
43
+ utils.isInputActive = jest.fn(() => true);
44
+ br.next = jest.fn();
45
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'ArrowDown'});
46
+ keyEvent.preventDefault = jest.fn();
47
+ document.dispatchEvent(keyEvent);
48
+ expect(br.next).toHaveBeenCalledTimes(0);
49
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(0);
50
+ // Must reset for following tests
51
+ utils.isInputActive.mockReset();
52
+ });
53
+
54
+ });
55
+
56
+ describe('Keyboard shortcuts', () => {
57
+
58
+ test('Home key', () => {
59
+ br.first = jest.fn();
60
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'Home'});
61
+ keyEvent.preventDefault = jest.fn();
62
+ document.dispatchEvent(keyEvent);
63
+ expect(br.first).toHaveBeenCalledTimes(1);
64
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
65
+ });
66
+
67
+ test('End key', () => {
68
+ br.last = jest.fn();
69
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'End'});
70
+ keyEvent.preventDefault = jest.fn();
71
+ document.dispatchEvent(keyEvent);
72
+ expect(br.last).toHaveBeenCalledTimes(1);
73
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
74
+ });
75
+
76
+ test('ArrowDown key', () => {
77
+ br.mode = br.constMode2up;
78
+ br.next = jest.fn();
79
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'ArrowDown'});
80
+ keyEvent.preventDefault = jest.fn();
81
+ document.dispatchEvent(keyEvent);
82
+ expect(br.next).toHaveBeenCalledTimes(1);
83
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
84
+ });
85
+
86
+ test('PageDown key', () => {
87
+ br.mode = br.constMode2up;
88
+ br.next = jest.fn();
89
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'PageDown'});
90
+ keyEvent.preventDefault = jest.fn();
91
+ document.dispatchEvent(keyEvent);
92
+ expect(br.next).toHaveBeenCalledTimes(1);
93
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
94
+ });
95
+
96
+ test('ArrowUp key', () => {
97
+ br.mode = br.constMode2up;
98
+ br.prev = jest.fn();
99
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'ArrowUp'});
100
+ keyEvent.preventDefault = jest.fn();
101
+ document.dispatchEvent(keyEvent);
102
+ expect(br.prev).toHaveBeenCalledTimes(1);
103
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
104
+ });
105
+
106
+ test('PageUp key', () => {
107
+ br.mode = br.constMode2up;
108
+ br.prev = jest.fn();
109
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'PageUp'});
110
+ keyEvent.preventDefault = jest.fn();
111
+ document.dispatchEvent(keyEvent);
112
+ expect(br.prev).toHaveBeenCalledTimes(1);
113
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
114
+ });
115
+
116
+ test('ArrowLeft key', () => {
117
+ br.mode = br.constMode2up;
118
+ br.left = jest.fn();
119
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'ArrowLeft'});
120
+ keyEvent.preventDefault = jest.fn();
121
+ document.dispatchEvent(keyEvent);
122
+ expect(br.left).toHaveBeenCalledTimes(1);
123
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
124
+ });
125
+
126
+ test('ArrowRight key', () => {
127
+ br.mode = br.constMode2up;
128
+ br.right = jest.fn();
129
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'ArrowRight'});
130
+ keyEvent.preventDefault = jest.fn();
131
+ document.dispatchEvent(keyEvent);
132
+ expect(br.right).toHaveBeenCalledTimes(1);
133
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
134
+ });
135
+
136
+ test('Subtract key', () => {
137
+ br.zoom = jest.fn();
138
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'Subtract'});
139
+ keyEvent.preventDefault = jest.fn();
140
+ document.dispatchEvent(keyEvent);
141
+ expect(br.zoom).toHaveBeenCalledTimes(1);
142
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
143
+ });
144
+
145
+ test('- key', () => {
146
+ br.zoom = jest.fn();
147
+ const keyEvent = new KeyboardEvent('keydown', {'key': '-'});
148
+ keyEvent.preventDefault = jest.fn();
149
+ document.dispatchEvent(keyEvent);
150
+ expect(br.zoom).toHaveBeenCalledTimes(1);
151
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
152
+ });
153
+
154
+ test('Add key', () => {
155
+ br.zoom = jest.fn();
156
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'Add'});
157
+ keyEvent.preventDefault = jest.fn();
158
+ document.dispatchEvent(keyEvent);
159
+ expect(br.zoom).toHaveBeenCalledTimes(1);
160
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
161
+ });
162
+
163
+ test('+ key', () => {
164
+ br.zoom = jest.fn();
165
+ const keyEvent = new KeyboardEvent('keydown', {'key': '+'});
166
+ keyEvent.preventDefault = jest.fn();
167
+ document.dispatchEvent(keyEvent);
168
+ expect(br.zoom).toHaveBeenCalledTimes(1);
169
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
170
+ });
171
+
172
+ test('= key', () => {
173
+ br.zoom = jest.fn();
174
+ const keyEvent = new KeyboardEvent('keydown', {'key': '='});
175
+ keyEvent.preventDefault = jest.fn();
176
+ document.dispatchEvent(keyEvent);
177
+ expect(br.zoom).toHaveBeenCalledTimes(1);
178
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
179
+ });
180
+
181
+ test('F key', () => {
182
+ br.toggleFullscreen = jest.fn();
183
+ const keyEvent = new KeyboardEvent('keydown', {'key': 'F'});
184
+ keyEvent.preventDefault = jest.fn();
185
+ document.dispatchEvent(keyEvent);
186
+ expect(br.toggleFullscreen).toHaveBeenCalledTimes(1);
187
+ expect(keyEvent.preventDefault).toHaveBeenCalledTimes(1);
188
+ });
189
+
190
+ });