@internetarchive/bookreader 5.0.0-46-alpha2 → 5.0.0-46-no-right-click
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/ia-bookreader-bundle.js +31 -31
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReader/jquery-1.10.1.js +2 -0
- package/BookReader/jquery-1.10.1.js.LICENSE.txt +24 -0
- package/BookReader/plugins/plugin.search.js +1 -1
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReaderDemo/IADemoBr.js +13 -17
- package/BookReaderDemo/demo-advanced.html +1 -1
- package/BookReaderDemo/demo-autoplay.html +1 -1
- package/BookReaderDemo/demo-embed-iframe-src.html +1 -1
- package/BookReaderDemo/demo-fullscreen-mobile.html +1 -1
- package/BookReaderDemo/demo-fullscreen.html +1 -1
- package/BookReaderDemo/demo-iiif.html +1 -1
- package/BookReaderDemo/demo-internetarchive.html +1 -3
- package/BookReaderDemo/demo-multiple.html +1 -1
- package/BookReaderDemo/demo-preview-pages.html +1 -1
- package/BookReaderDemo/demo-simple.html +1 -1
- package/BookReaderDemo/demo-vendor-fullscreen.html +1 -1
- package/BookReaderDemo/immersion-1up.html +1 -1
- package/BookReaderDemo/immersion-mode.html +1 -1
- package/BookReaderDemo/toggle_controls.html +1 -1
- package/BookReaderDemo/view_mode.html +1 -1
- package/BookReaderDemo/viewmode-cycle.html +1 -1
- package/CHANGELOG.md +0 -12
- package/package.json +7 -7
- package/src/BookNavigator/bookmarks/ia-bookmarks.js +1 -15
- package/src/BookNavigator/search/a-search-result.js +4 -12
- package/src/plugins/search/view.js +5 -10
- package/src/util/manifestGenerator.js +0 -0
- package/tests/jest/BookNavigator/book-navigator.test.js +51 -75
- package/tests/jest/BookNavigator/search/search-results.test.js +1 -33
- package/tests/jest/plugins/search/plugin.search.view.test.js +0 -29
- package/webpack.config.js +1 -1
- package/BookReader/jquery-3.js +0 -2
- package/BookReader/jquery-3.js.LICENSE.txt +0 -24
@@ -1,5 +1,3 @@
|
|
1
|
-
import { escapeHTML } from "../../BookReader/utils.js";
|
2
|
-
|
3
1
|
class SearchView {
|
4
2
|
/**
|
5
3
|
* @param {object} params
|
@@ -15,7 +13,7 @@ class SearchView {
|
|
15
13
|
// Search results are returned as a text blob with the hits wrapped in
|
16
14
|
// triple mustaches. Hits occasionally include text beyond the search
|
17
15
|
// term, so everything within the staches is captured and wrapped.
|
18
|
-
this.matcher = new RegExp('{{{(.+?)}}}', '
|
16
|
+
this.matcher = new RegExp('{{{(.+?)}}}', 'g');
|
19
17
|
this.matches = [];
|
20
18
|
this.cacheDOMElements();
|
21
19
|
this.bindEvents();
|
@@ -236,18 +234,15 @@ class SearchView {
|
|
236
234
|
|
237
235
|
const percentThrough = this.br.constructor.util.cssPercentage(pageIndex, this.br.getNumLeafs() - 1);
|
238
236
|
|
239
|
-
const
|
240
|
-
const queryStringWithB = escapedQueryString.replace(this.matcher, '<b>$1</b>');
|
237
|
+
const queryStringWithB = queryString.replace(this.matcher, '<b>$1</b>');
|
241
238
|
|
242
239
|
let queryStringWithBTruncated = '';
|
243
240
|
|
244
241
|
if (queryString.length > 100) {
|
245
|
-
queryStringWithBTruncated = queryString
|
246
|
-
|
247
|
-
// If truncating, we must escape *after* truncation occurs (but before wrapping in <b>)
|
248
|
-
queryStringWithBTruncated = escapeHTML(queryStringWithBTruncated)
|
242
|
+
queryStringWithBTruncated = queryString
|
243
|
+
.replace(/^(.{100}[^\s]*).*/, "$1")
|
249
244
|
.replace(this.matcher, '<b>$1</b>')
|
250
|
-
|
245
|
+
+ '...';
|
251
246
|
}
|
252
247
|
|
253
248
|
// draw marker
|
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,
|
@@ -591,40 +602,5 @@ describe('<book-navigator>', () => {
|
|
591
602
|
expect(preventDefaultSpy.called).toEqual(true);
|
592
603
|
});
|
593
604
|
});
|
594
|
-
it('Allows unrestricted books access to context menu', async () => {
|
595
|
-
window.archive_analytics = { send_event_no_sampling: sinon.fake() };
|
596
|
-
|
597
|
-
const el = fixtureSync(container());
|
598
|
-
const brStub = {
|
599
|
-
options: { restricted: false },
|
600
|
-
};
|
601
|
-
|
602
|
-
el.bookreader = brStub;
|
603
|
-
el.bookIsRestricted = false;
|
604
|
-
|
605
|
-
await el.elementUpdated;
|
606
|
-
|
607
|
-
expect(window.archive_analytics.send_event_no_sampling.called).toEqual(
|
608
|
-
false
|
609
|
-
);
|
610
|
-
|
611
|
-
const body = document.querySelector('body');
|
612
|
-
// const element stub for img.BRpageimage
|
613
|
-
const imgBRpageimage = document.createElement('img');
|
614
|
-
imgBRpageimage.classList.add('not-targeted-element');
|
615
|
-
body.appendChild(imgBRpageimage);
|
616
|
-
const contextMenuEvent = new Event('contextmenu', { bubbles: true });
|
617
|
-
|
618
|
-
// Set spy on contextMenuEvent to check if `preventDefault` is called
|
619
|
-
const preventDefaultSpy = sinon.spy(contextMenuEvent, 'preventDefault');
|
620
|
-
expect(preventDefaultSpy.called).toEqual(false);
|
621
|
-
|
622
|
-
imgBRpageimage.dispatchEvent(contextMenuEvent);
|
623
|
-
|
624
|
-
// analytics fires
|
625
|
-
expect(window.archive_analytics.send_event_no_sampling.called).toEqual(true);
|
626
|
-
// we do not prevent default
|
627
|
-
expect(preventDefaultSpy.called).toEqual(false);
|
628
|
-
});
|
629
605
|
});
|
630
606
|
});
|
@@ -46,25 +46,6 @@ const results = [{
|
|
46
46
|
}],
|
47
47
|
}];
|
48
48
|
|
49
|
-
const resultWithScript = [{
|
50
|
-
text: `foo bar <script>const msg = 'test' + ' failure'; document.write(msg);</script> {{{${searchQuery}}}} baz`,
|
51
|
-
cover: '//placehold.it/30x44',
|
52
|
-
title: 'Book title',
|
53
|
-
displayPageNumber: 'Page 24',
|
54
|
-
par: [{
|
55
|
-
boxes: [{
|
56
|
-
r: 2672, b: 792, t: 689, page: 24, l: 2424,
|
57
|
-
}],
|
58
|
-
b: 1371,
|
59
|
-
t: 689,
|
60
|
-
page_width: 3658,
|
61
|
-
r: 3192,
|
62
|
-
l: 428,
|
63
|
-
page_height: 5357,
|
64
|
-
page: 24,
|
65
|
-
}],
|
66
|
-
}];
|
67
|
-
|
68
49
|
describe('<ia-book-search-results>', () => {
|
69
50
|
afterEach(() => {
|
70
51
|
sinon.restore();
|
@@ -107,20 +88,7 @@ describe('<ia-book-search-results>', () => {
|
|
107
88
|
sinon.replace(IABookSearchResults.prototype, 'createRenderRoot', function createRenderRoot() { return this; });
|
108
89
|
const el = await fixture(container(results));
|
109
90
|
|
110
|
-
|
111
|
-
// So query the DOM for the match instead.
|
112
|
-
const match = el.querySelector('book-search-result mark');
|
113
|
-
expect(match?.textContent).toEqual(searchQuery);
|
114
|
-
});
|
115
|
-
|
116
|
-
test('renders results that contain sanitized HTML tags', async () => {
|
117
|
-
sinon.replace(IABookSearchResults.prototype, 'createRenderRoot', function createRenderRoot() { return this; });
|
118
|
-
// A result whose text contains a <script> tag that will insert 'test failure' into the element if not sanitized
|
119
|
-
const el = await fixture(container(resultWithScript));
|
120
|
-
|
121
|
-
const match = el.querySelector('book-search-result mark');
|
122
|
-
expect(match?.textContent).toEqual(searchQuery);
|
123
|
-
expect(el.innerHTML).not.toContain('test failure');
|
91
|
+
expect(el.innerHTML).toContain(`<mark>${searchQuery}</mark>`);
|
124
92
|
});
|
125
93
|
|
126
94
|
test('renders results that contain an optional cover image', async () => {
|
@@ -27,27 +27,6 @@ const results = {
|
|
27
27
|
}]
|
28
28
|
}]
|
29
29
|
};
|
30
|
-
const resultWithScript = {
|
31
|
-
ia: "adventuresofoli00dick",
|
32
|
-
q: "child",
|
33
|
-
indexed: true,
|
34
|
-
page_count: 644,
|
35
|
-
body_length: 666,
|
36
|
-
leaf0_missing: false,
|
37
|
-
matches: [{
|
38
|
-
text: 'foo bar <script>alert(1);</script> {{{keyword}}} baz',
|
39
|
-
par: [{
|
40
|
-
boxes: [{r: 1221, b: 2121, t: 2075, page: 37, l: 1107}],
|
41
|
-
b: 2535,
|
42
|
-
t: 1942,
|
43
|
-
page_width: 1790,
|
44
|
-
r: 1598,
|
45
|
-
l: 50,
|
46
|
-
page_height: 2940,
|
47
|
-
page: 37
|
48
|
-
}]
|
49
|
-
}]
|
50
|
-
};
|
51
30
|
beforeEach(() => {
|
52
31
|
$.ajax = jest.fn().mockImplementation(() => {
|
53
32
|
// return from:
|
@@ -102,14 +81,6 @@ describe('View: Plugin: Search', () => {
|
|
102
81
|
expect(searchResultsNav.querySelector('.prev')).toBeDefined();
|
103
82
|
expect(searchResultsNav.querySelector('.next')).toBeDefined();
|
104
83
|
});
|
105
|
-
test('disallows xss from search results', () => {
|
106
|
-
br.init();
|
107
|
-
const event = new CustomEvent(`${namespace}SearchCallback`);
|
108
|
-
const options = { goToFirstResult: false };
|
109
|
-
br.searchView.handleSearchCallback(event, { results: resultWithScript, options });
|
110
|
-
|
111
|
-
expect(br.searchView.dom.searchNavigation.parent().html()).not.toContain('<script>alert(1);</script>');
|
112
|
-
});
|
113
84
|
|
114
85
|
describe('Click events handlers', () => {
|
115
86
|
it('triggers custom event when toggling side menu', () => {
|