@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.
@@ -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-45",
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.2",
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="main">
36
- <div id="BookReader"></div>
37
- <p class="visible-in-reader">now showing</p>
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("How it loads", () => {
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(new CustomEvent('BookReader:PostInit', {
89
- detail: { props: brStub }
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('https://https://foo.archive.org/services/img/foo');
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(VisualAdjustmentsProvider);
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(el.menuShortcuts.find(m => m.id === 'volumes')).toBeDefined();
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('BookReader:ToggleSearchMenu');
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 });