@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.
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/bookreader-component-bundle.js +1 -1
- package/BookReader/bookreader-component-bundle.js.map +1 -1
- package/BookReaderDemo/demo-internetarchive.html +86 -4
- package/CHANGELOG.md +5 -0
- package/package.json +1 -1
- package/src/BookNavigator/bookmarks/ia-bookmarks.js +1 -0
- package/src/BookReader.js +102 -64
- package/tests/jest/BookReader.keyboard.test.js +190 -0
@@ -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
|
-
|
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
|
-
|
71
|
-
|
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
package/package.json
CHANGED
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
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
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
|
701
|
-
|
702
|
-
|
703
|
-
e.preventDefault();
|
704
|
-
self.next();
|
705
|
-
}
|
729
|
+
case "End":
|
730
|
+
e.preventDefault();
|
731
|
+
this.last();
|
706
732
|
break;
|
707
|
-
case
|
708
|
-
|
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
|
-
|
739
|
+
this.next();
|
711
740
|
}
|
712
741
|
break;
|
713
|
-
case
|
714
|
-
|
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
|
-
|
748
|
+
this.prev();
|
717
749
|
}
|
718
750
|
break;
|
719
|
-
case
|
720
|
-
|
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
|
-
|
756
|
+
this.left();
|
723
757
|
}
|
724
758
|
break;
|
725
|
-
case
|
726
|
-
|
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
|
-
|
764
|
+
this.right();
|
729
765
|
}
|
730
766
|
break;
|
731
|
-
|
732
|
-
case
|
733
|
-
case
|
734
|
-
|
735
|
-
|
736
|
-
self.zoom(-1);
|
737
|
-
}
|
767
|
+
// Zoom
|
768
|
+
case '-':
|
769
|
+
case 'Subtract':
|
770
|
+
e.preventDefault();
|
771
|
+
this.zoom(-1);
|
738
772
|
break;
|
739
|
-
case
|
740
|
-
case
|
741
|
-
case
|
742
|
-
|
743
|
-
|
744
|
-
|
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
|
+
});
|