@internetarchive/bookreader 5.0.0-45 → 5.0.0-46-no-right-click
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.
- package/BookReader/BookReader.js +33352 -2
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +11 -11
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReaderDemo/demo-internetarchive.html +5 -0
- package/package.json +2 -2
- package/src/BookNavigator/book-navigator.js +23 -0
- package/src/BookReader/Mode2Up.js +7 -0
- package/src/util/manifestGenerator.js +0 -0
- package/tests/jest/BookNavigator/book-navigator.test.js +130 -40
- package/tests/jest/BookReader/Mode2Up.test.js +23 -0
@@ -126,6 +126,11 @@
|
|
126
126
|
const placeholder = document.querySelector('#placeholder');
|
127
127
|
placeholder.innerHTML = 'Dependencies are complete, bookreader has loaded';
|
128
128
|
});
|
129
|
+
|
130
|
+
// analytics stub
|
131
|
+
window.archive_analytics = {
|
132
|
+
send_event_no_sampling: (category, action, label) => console.log('~~~ NO SAMPLE EVENT CALLED: ', { category, action, label }),
|
133
|
+
}
|
129
134
|
</script>
|
130
135
|
|
131
136
|
<!-- IA fetch demo -->
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@internetarchive/bookreader",
|
3
|
-
"version": "5.0.0-
|
3
|
+
"version": "5.0.0-46-no-right-click",
|
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.
|
49
|
+
"@open-wc/testing-helpers": "^2.1.3",
|
50
50
|
"@types/jest": "^27.5.1",
|
51
51
|
"@webcomponents/webcomponentsjs": "^2.6.0",
|
52
52
|
"babel-loader": "8.2.5",
|
@@ -443,6 +443,29 @@ export class BookNavigator extends LitElement {
|
|
443
443
|
this.downloadableTypes = downloadURLs;
|
444
444
|
this.bookIsRestricted = isRestricted;
|
445
445
|
});
|
446
|
+
window.addEventListener('contextmenu', (e) => this.manageContextMenuVisibility(e), { capture: true });
|
447
|
+
}
|
448
|
+
|
449
|
+
/** Display an element's context menu */
|
450
|
+
manageContextMenuVisibility(e) {
|
451
|
+
if (window.archive_analytics) {
|
452
|
+
window.archive_analytics?.send_event_no_sampling(
|
453
|
+
'BookReader',
|
454
|
+
`contextmenu-${this.bookIsRestricted ? 'restricted' : 'unrestricted'}`,
|
455
|
+
e.target.classList.value
|
456
|
+
);
|
457
|
+
}
|
458
|
+
if (!this.bookIsRestricted) {
|
459
|
+
return;
|
460
|
+
}
|
461
|
+
|
462
|
+
const imagePane = e.target.classList.value.match(/BRscreen|BRpageimage/g);
|
463
|
+
if (!imagePane) {
|
464
|
+
return;
|
465
|
+
}
|
466
|
+
|
467
|
+
e.preventDefault();
|
468
|
+
return false;
|
446
469
|
}
|
447
470
|
|
448
471
|
loadSharedObserver() {
|
@@ -843,6 +843,13 @@ export class Mode2Up {
|
|
843
843
|
this.pageContainers[this.br.twoPage.currentIndexR].$container.animate({width: '0px'}, speed, 'easeInSine', () => {
|
844
844
|
this.br.$('BRgutter').css({left: `${gutter - this.br.twoPage.bookSpineDivWidth * 0.5}px`});
|
845
845
|
$(this.br.leafEdgeTmp).animate({left: `${gutter - newWidthL - leafEdgeTmpW}px`}, speed, 'easeOutSine');
|
846
|
+
|
847
|
+
// Ensure the new left leaf is right-positioned before animating its width.
|
848
|
+
// Otherwise, it animates in the wrong direction.
|
849
|
+
this.pageContainers[newIndexL].$container.css({
|
850
|
+
right: `${$twoPageViewEl.prop('clientWidth') - gutter}px`,
|
851
|
+
left: ''
|
852
|
+
});
|
846
853
|
this.pageContainers[newIndexL].$container.animate({width: `${newWidthL}px`}, speed, 'easeOutSine', () => {
|
847
854
|
this.pageContainers[newIndexR].$container.css('zIndex', 2);
|
848
855
|
|
File without changes
|
@@ -13,8 +13,9 @@ import VolumesProvider from '@/src/BookNavigator/volumes/volumes-provider.js';
|
|
13
13
|
import { ModalManager } from '@internetarchive/modal-manager';
|
14
14
|
import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
|
15
15
|
import '@/src/BookNavigator/book-navigator.js';
|
16
|
+
import { sleep } from '@/src/BookReader/utils';
|
16
17
|
|
17
|
-
const promise0 = () => new Promise(res => setTimeout(res, 0));
|
18
|
+
const promise0 = () => new Promise((res) => setTimeout(res, 0));
|
18
19
|
|
19
20
|
const container = (sharedObserver = null) => {
|
20
21
|
const itemStub = {
|
@@ -22,7 +23,7 @@ const container = (sharedObserver = null) => {
|
|
22
23
|
identifier: 'foo',
|
23
24
|
creator: 'bar',
|
24
25
|
title: 'baz',
|
25
|
-
}
|
26
|
+
},
|
26
27
|
};
|
27
28
|
const modalMgr = new ModalManager();
|
28
29
|
return html`
|
@@ -32,18 +33,18 @@ const container = (sharedObserver = null) => {
|
|
32
33
|
.sharedObserver=${sharedObserver || new SharedResizeObserver()}
|
33
34
|
.modal=${modalMgr}
|
34
35
|
>
|
35
|
-
<div slot=
|
36
|
-
<div id=
|
37
|
-
<p class=
|
36
|
+
<div slot='main'>
|
37
|
+
<div id='BookReader'></div>
|
38
|
+
<p class='visible-in-reader'>now showing</p>
|
38
39
|
<\div>
|
39
40
|
</book-navigator>
|
40
41
|
`;
|
41
42
|
};
|
42
43
|
|
43
44
|
window.ResizeObserver = class ResizeObserver {
|
44
|
-
observe = sinon.fake()
|
45
|
-
unobserve = sinon.fake()
|
46
|
-
disconnect = sinon.fake()
|
45
|
+
observe = sinon.fake();
|
46
|
+
unobserve = sinon.fake();
|
47
|
+
disconnect = sinon.fake();
|
47
48
|
};
|
48
49
|
|
49
50
|
afterEach(() => {
|
@@ -52,11 +53,11 @@ afterEach(() => {
|
|
52
53
|
const body = document.querySelector('body');
|
53
54
|
body.innerHTML = '';
|
54
55
|
sinon.restore();
|
56
|
+
window.archive_analytics = null;
|
55
57
|
});
|
56
58
|
|
57
|
-
|
58
59
|
describe('<book-navigator>', () => {
|
59
|
-
describe(
|
60
|
+
describe('How it loads', () => {
|
60
61
|
describe('Attaches BookReader listeners before `br.init` is called', () => {
|
61
62
|
test('binds global event listeners', async () => {
|
62
63
|
const el = fixtureSync(container());
|
@@ -71,7 +72,7 @@ describe('<book-navigator>', () => {
|
|
71
72
|
jumpToIndex: sinon.fake(),
|
72
73
|
options: { enableMultipleBooks: false }, // for multipleBooks
|
73
74
|
el: '#BookReader',
|
74
|
-
refs: {}
|
75
|
+
refs: {},
|
75
76
|
};
|
76
77
|
|
77
78
|
const sharedObserver = new SharedResizeObserver();
|
@@ -85,9 +86,11 @@ describe('<book-navigator>', () => {
|
|
85
86
|
|
86
87
|
expect(brStub.resize.callCount).toEqual(0);
|
87
88
|
|
88
|
-
window.dispatchEvent(
|
89
|
-
|
90
|
-
|
89
|
+
window.dispatchEvent(
|
90
|
+
new CustomEvent('BookReader:PostInit', {
|
91
|
+
detail: { props: brStub },
|
92
|
+
})
|
93
|
+
);
|
91
94
|
await elementUpdated(el);
|
92
95
|
|
93
96
|
expect(el.emitLoadingStatusUpdate.callCount).toEqual(1);
|
@@ -120,13 +123,15 @@ describe('<book-navigator>', () => {
|
|
120
123
|
const itemImage = fixtureSync(el.itemImage);
|
121
124
|
expect(itemImage).toBeInstanceOf(HTMLImageElement);
|
122
125
|
expect(itemImage.getAttribute('class')).toEqual('cover-img');
|
123
|
-
expect(itemImage.getAttribute('src')).toEqual(
|
126
|
+
expect(itemImage.getAttribute('src')).toEqual(
|
127
|
+
'https://https://foo.archive.org/services/img/foo'
|
128
|
+
);
|
124
129
|
});
|
125
130
|
});
|
126
131
|
describe('Menu/Layer Provider', () => {
|
127
132
|
describe('Connecting with a provider:', () => {
|
128
133
|
// loads Providers with base shared resources
|
129
|
-
test('We load 3 Sub Menus by default', async() => {
|
134
|
+
test('We load 3 Sub Menus by default', async () => {
|
130
135
|
const el = fixtureSync(container());
|
131
136
|
const $brContainer = document.createElement('div');
|
132
137
|
const brStub = {
|
@@ -135,8 +140,8 @@ describe('<book-navigator>', () => {
|
|
135
140
|
jumpToIndex: sinon.fake(),
|
136
141
|
options: {},
|
137
142
|
refs: {
|
138
|
-
$brContainer
|
139
|
-
}
|
143
|
+
$brContainer,
|
144
|
+
},
|
140
145
|
};
|
141
146
|
el.bookreader = brStub;
|
142
147
|
await el.elementUpdated;
|
@@ -151,10 +156,12 @@ describe('<book-navigator>', () => {
|
|
151
156
|
expect(el.menuProviders.share).toBeInstanceOf(SharingProvider);
|
152
157
|
|
153
158
|
expect(defaultMenus).toContain('visualAdjustments');
|
154
|
-
expect(el.menuProviders.visualAdjustments).toBeInstanceOf(
|
159
|
+
expect(el.menuProviders.visualAdjustments).toBeInstanceOf(
|
160
|
+
VisualAdjustmentsProvider
|
161
|
+
);
|
155
162
|
});
|
156
163
|
describe('Loading Sub Menus By Plugin Flags', () => {
|
157
|
-
test('Search: uses `enableSearch` flag', async() => {
|
164
|
+
test('Search: uses `enableSearch` flag', async () => {
|
158
165
|
const el = fixtureSync(container());
|
159
166
|
const $brContainer = document.createElement('div');
|
160
167
|
const brStub = {
|
@@ -163,8 +170,8 @@ describe('<book-navigator>', () => {
|
|
163
170
|
jumpToIndex: sinon.fake(),
|
164
171
|
options: { enableSearch: true },
|
165
172
|
refs: {
|
166
|
-
$brContainer
|
167
|
-
}
|
173
|
+
$brContainer,
|
174
|
+
},
|
168
175
|
};
|
169
176
|
el.bookreader = brStub;
|
170
177
|
await el.elementUpdated;
|
@@ -176,9 +183,9 @@ describe('<book-navigator>', () => {
|
|
176
183
|
expect(el.menuProviders.search).toBeInstanceOf(SearchProvider);
|
177
184
|
|
178
185
|
// also adds a menu shortcut
|
179
|
-
expect(el.menuShortcuts.find(m => m.id === 'search')).toBeDefined();
|
186
|
+
expect(el.menuShortcuts.find((m) => m.id === 'search')).toBeDefined();
|
180
187
|
});
|
181
|
-
test('Volumes/Multiple Books: uses `enableMultipleBooks` flag', async() => {
|
188
|
+
test('Volumes/Multiple Books: uses `enableMultipleBooks` flag', async () => {
|
182
189
|
const el = fixtureSync(container());
|
183
190
|
const $brContainer = document.createElement('div');
|
184
191
|
const brStub = {
|
@@ -189,13 +196,13 @@ describe('<book-navigator>', () => {
|
|
189
196
|
enableMultipleBooks: true,
|
190
197
|
multipleBooksList: {
|
191
198
|
by_subprefix: {
|
192
|
-
fooSubprefix: 'beep'
|
193
|
-
}
|
194
|
-
}
|
199
|
+
fooSubprefix: 'beep',
|
200
|
+
},
|
201
|
+
},
|
195
202
|
},
|
196
203
|
refs: {
|
197
|
-
$brContainer
|
198
|
-
}
|
204
|
+
$brContainer,
|
205
|
+
},
|
199
206
|
};
|
200
207
|
el.bookreader = brStub;
|
201
208
|
await el.elementUpdated;
|
@@ -207,7 +214,9 @@ describe('<book-navigator>', () => {
|
|
207
214
|
expect(el.menuProviders.volumes).toBeInstanceOf(VolumesProvider);
|
208
215
|
|
209
216
|
// also adds a menu shortcut
|
210
|
-
expect(
|
217
|
+
expect(
|
218
|
+
el.menuShortcuts.find((m) => m.id === 'volumes')
|
219
|
+
).toBeDefined();
|
211
220
|
});
|
212
221
|
});
|
213
222
|
test('keeps track of base shared resources for providers in: `baseProviderConfig`', () => {
|
@@ -315,7 +324,9 @@ describe('<book-navigator>', () => {
|
|
315
324
|
console.log();
|
316
325
|
sidePanelConfig = e.detail;
|
317
326
|
});
|
318
|
-
const toggleSearchMenuEvent = new Event(
|
327
|
+
const toggleSearchMenuEvent = new Event(
|
328
|
+
'BookReader:ToggleSearchMenu'
|
329
|
+
);
|
319
330
|
window.dispatchEvent(toggleSearchMenuEvent);
|
320
331
|
|
321
332
|
await elementUpdated(el);
|
@@ -327,15 +338,15 @@ describe('<book-navigator>', () => {
|
|
327
338
|
});
|
328
339
|
});
|
329
340
|
|
330
|
-
describe('Resizing',() => {
|
341
|
+
describe('Resizing', () => {
|
331
342
|
test('keeps track of `brWidth` and `brHeight`', async () => {
|
332
343
|
const el = fixtureSync(container());
|
333
344
|
const brStub = {
|
334
345
|
resize: sinon.fake(),
|
335
346
|
options: {},
|
336
347
|
refs: {
|
337
|
-
$brContainer: document.createElement('div')
|
338
|
-
}
|
348
|
+
$brContainer: document.createElement('div'),
|
349
|
+
},
|
339
350
|
};
|
340
351
|
el.bookreader = brStub;
|
341
352
|
await elementUpdated(el);
|
@@ -345,9 +356,9 @@ describe('<book-navigator>', () => {
|
|
345
356
|
const mockResizeEvent = {
|
346
357
|
contentRect: {
|
347
358
|
height: 500,
|
348
|
-
width: 900
|
359
|
+
width: 900,
|
349
360
|
},
|
350
|
-
target: el.mainBRContainer
|
361
|
+
target: el.mainBRContainer,
|
351
362
|
};
|
352
363
|
el.handleResize(mockResizeEvent);
|
353
364
|
|
@@ -364,8 +375,8 @@ describe('<book-navigator>', () => {
|
|
364
375
|
resize: sinon.fake(),
|
365
376
|
options: {},
|
366
377
|
refs: {
|
367
|
-
$brContainer: document.createElement('div')
|
368
|
-
}
|
378
|
+
$brContainer: document.createElement('div'),
|
379
|
+
},
|
369
380
|
};
|
370
381
|
|
371
382
|
el.bookreader = brStub;
|
@@ -470,7 +481,7 @@ describe('<book-navigator>', () => {
|
|
470
481
|
|
471
482
|
expect(el.closeFullscreen.callCount).toEqual(1);
|
472
483
|
});
|
473
|
-
test('removes Fullscreen shortcut when leaving fullscreen', async() => {
|
484
|
+
test('removes Fullscreen shortcut when leaving fullscreen', async () => {
|
474
485
|
const el = fixtureSync(container());
|
475
486
|
const brStub = {
|
476
487
|
isFullscreen: () => false,
|
@@ -489,7 +500,7 @@ describe('<book-navigator>', () => {
|
|
489
500
|
expect(el.menuShortcuts.length).toEqual(0);
|
490
501
|
expect(el.emitMenuShortcutsUpdated.callCount).toEqual(1);
|
491
502
|
});
|
492
|
-
test('Event: Listens for `BookReader:FullscreenToggled', async() => {
|
503
|
+
test('Event: Listens for `BookReader:FullscreenToggled', async () => {
|
493
504
|
const el = fixtureSync(container());
|
494
505
|
const brStub = {
|
495
506
|
isFullscreen: () => true,
|
@@ -513,4 +524,83 @@ describe('<book-navigator>', () => {
|
|
513
524
|
expect(el.manageFullScreenBehavior.callCount).toEqual(1);
|
514
525
|
});
|
515
526
|
});
|
527
|
+
describe('Handles Restricted Books', () => {
|
528
|
+
describe('contextMenu is prevented when book is restricted', () => {
|
529
|
+
it('watches on `div.BRscreen`', async () => {
|
530
|
+
window.archive_analytics = { send_event_no_sampling: sinon.fake() };
|
531
|
+
|
532
|
+
const el = fixtureSync(container());
|
533
|
+
const brStub = {
|
534
|
+
options: { restricted: true },
|
535
|
+
};
|
536
|
+
|
537
|
+
el.bookreader = brStub;
|
538
|
+
el.bookIsRestricted = true;
|
539
|
+
|
540
|
+
const elSpy = sinon.spy(el.manageContextMenuVisibility);
|
541
|
+
await el.elementUpdated;
|
542
|
+
|
543
|
+
expect(window.archive_analytics.send_event_no_sampling.called).toEqual(
|
544
|
+
false
|
545
|
+
);
|
546
|
+
expect(elSpy.called).toEqual(false);
|
547
|
+
|
548
|
+
const body = document.querySelector('body');
|
549
|
+
|
550
|
+
const divBRscreen = document.createElement('div');
|
551
|
+
divBRscreen.classList.add('BRscreen');
|
552
|
+
body.appendChild(divBRscreen);
|
553
|
+
|
554
|
+
const contextMenuEvent = new Event('contextmenu', { bubbles: true });
|
555
|
+
|
556
|
+
// Set spy on contextMenuEvent to check if `preventDefault` is called
|
557
|
+
const preventDefaultSpy = sinon.spy(contextMenuEvent, 'preventDefault');
|
558
|
+
expect(preventDefaultSpy.called).toEqual(false);
|
559
|
+
|
560
|
+
divBRscreen.dispatchEvent(contextMenuEvent);
|
561
|
+
|
562
|
+
// analytics fires
|
563
|
+
expect(window.archive_analytics.send_event_no_sampling.called).toEqual(
|
564
|
+
true
|
565
|
+
);
|
566
|
+
// we prevent default
|
567
|
+
expect(preventDefaultSpy.called).toEqual(true);
|
568
|
+
});
|
569
|
+
it('watches on `img.BRpageimage`', async () => {
|
570
|
+
window.archive_analytics = { send_event_no_sampling: sinon.fake() };
|
571
|
+
|
572
|
+
const el = fixtureSync(container());
|
573
|
+
const brStub = {
|
574
|
+
options: { restricted: true },
|
575
|
+
};
|
576
|
+
|
577
|
+
el.bookreader = brStub;
|
578
|
+
el.bookIsRestricted = true;
|
579
|
+
|
580
|
+
await el.elementUpdated;
|
581
|
+
|
582
|
+
expect(window.archive_analytics.send_event_no_sampling.called).toEqual(
|
583
|
+
false
|
584
|
+
);
|
585
|
+
|
586
|
+
const body = document.querySelector('body');
|
587
|
+
// const element stub for img.BRpageimage
|
588
|
+
const imgBRpageimage = document.createElement('img');
|
589
|
+
imgBRpageimage.classList.add('BRpageimage');
|
590
|
+
body.appendChild(imgBRpageimage);
|
591
|
+
const contextMenuEvent = new Event('contextmenu', { bubbles: true });
|
592
|
+
|
593
|
+
// Set spy on contextMenuEvent to check if `preventDefault` is called
|
594
|
+
const preventDefaultSpy = sinon.spy(contextMenuEvent, 'preventDefault');
|
595
|
+
expect(preventDefaultSpy.called).toEqual(false);
|
596
|
+
|
597
|
+
imgBRpageimage.dispatchEvent(contextMenuEvent);
|
598
|
+
|
599
|
+
// analytics fires
|
600
|
+
expect(window.archive_analytics.send_event_no_sampling.called).toEqual(true);
|
601
|
+
// we prevent default
|
602
|
+
expect(preventDefaultSpy.called).toEqual(true);
|
603
|
+
});
|
604
|
+
});
|
605
|
+
});
|
516
606
|
});
|
@@ -47,6 +47,29 @@ describe('zoom', () => {
|
|
47
47
|
});
|
48
48
|
});
|
49
49
|
|
50
|
+
describe('page flip directions', () => {
|
51
|
+
test('animates the left page in the correct direction', () => {
|
52
|
+
const br = new BookReader({ data: SAMPLE_DATA });
|
53
|
+
br.init();
|
54
|
+
|
55
|
+
const fake = sinon.fake();
|
56
|
+
const fakeAnimWithCB = sinon.fake.yields();
|
57
|
+
const fakeAnim = sinon.fake((...args) =>
|
58
|
+
typeof args[args.length - 1] === 'function' ? fakeAnimWithCB(...args) : fake
|
59
|
+
);
|
60
|
+
sinon.replace(jQuery.prototype, 'animate', fakeAnim);
|
61
|
+
|
62
|
+
const fakeCSS = sinon.spy(jQuery.prototype, 'css');
|
63
|
+
|
64
|
+
br.next();
|
65
|
+
|
66
|
+
expect(fakeAnimWithCB.callCount).toBe(2);
|
67
|
+
// Find the call to .css() immediately preceding the second animation with a callback (i.e., the left page animation)
|
68
|
+
const preSecondAnimCssCallIndex = fakeCSS.getCalls().findIndex(call => call.calledAfter(fakeAnimWithCB.getCall(1))) - 1;
|
69
|
+
expect(fakeCSS.getCall(preSecondAnimCssCallIndex).args[0].left).toBe('');
|
70
|
+
});
|
71
|
+
});
|
72
|
+
|
50
73
|
describe('prefetch', () => {
|
51
74
|
test('loads nearby pages', () => {
|
52
75
|
const br = new BookReader({ data: SAMPLE_DATA });
|