@internetarchive/bookreader 5.0.0-91 → 5.0.0-92
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 +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +2 -2
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReader/plugins/plugin.archive_analytics.js +1 -1
- package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
- package/BookReader/plugins/plugin.autoplay.js +1 -1
- package/BookReader/plugins/plugin.autoplay.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +2 -2
- package/BookReader/plugins/plugin.chapters.js.map +1 -1
- package/BookReader/plugins/plugin.iiif.js +1 -1
- package/BookReader/plugins/plugin.iiif.js.map +1 -1
- package/BookReader/plugins/plugin.resume.js +1 -1
- package/BookReader/plugins/plugin.resume.js.map +1 -1
- package/BookReader/plugins/plugin.search.js +1 -1
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +1 -1
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReaderDemo/IADemoBr.js +29 -1
- package/BookReaderDemo/ia-multiple-volumes-manifest.js +0 -1
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/BookNavigator/book-navigator.js +5 -2
- package/src/BookNavigator/search/search-provider.js +13 -7
- package/src/BookNavigator/sharing.js +1 -1
- package/src/BookReader/Toolbar/Toolbar.js +5 -0
- package/src/BookReader/options.js +9 -7
- package/src/BookReader.js +31 -15
- package/src/BookReaderPlugin.js +8 -0
- package/src/plugins/plugin.text_selection.js +3 -1
- package/src/plugins/search/plugin.search.js +330 -376
- package/src/plugins/search/view.js +13 -9
- package/tests/e2e/helpers/mockSearch.js +1 -1
- package/tests/jest/BookNavigator/book-navigator.test.js +8 -3
- package/tests/jest/BookNavigator/search/search-provider.test.js +16 -4
- package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +1 -1
- package/tests/jest/BookReader.test.js +26 -1
- package/tests/jest/plugins/search/plugin.search.test.js +17 -42
- package/tests/jest/plugins/search/plugin.search.view.test.js +10 -18
- package/tests/jest/plugins/url/plugin.url.test.js +1 -1
@@ -45,6 +45,23 @@ BookReader.optionOverrides.imagesBaseURL = '/BookReader/images/';
|
|
45
45
|
const initializeBookReader = (brManifest) => {
|
46
46
|
console.log('initializeBookReader', brManifest);
|
47
47
|
|
48
|
+
const {bookPath, subPrefix} = brManifest.data.brOptions;
|
49
|
+
let path = bookPath;
|
50
|
+
const subPrefixWithSlash = `/${subPrefix}`;
|
51
|
+
if (bookPath.length - bookPath.lastIndexOf(subPrefixWithSlash) == subPrefixWithSlash.length) {
|
52
|
+
path = bookPath.substr(0, bookPath.length - subPrefixWithSlash.length);
|
53
|
+
}
|
54
|
+
|
55
|
+
const searchInsideUrl = '//{{server}}/fulltext/inside.php?' + [
|
56
|
+
'item_id={{bookId|urlencode}}',
|
57
|
+
'doc={{subPrefix|urlencode}}',
|
58
|
+
'q={{query|urlencode}}',
|
59
|
+
// This endpoint doesn't expect the path to be url encoded
|
60
|
+
`path=${encodeURIComponent(path).replace(/%2F/g, '/')}`,
|
61
|
+
'pre_tag={{preTag|urlencode}}',
|
62
|
+
'post_tag={{postTag|urlencode}}',
|
63
|
+
].join('&');
|
64
|
+
|
48
65
|
const options = {
|
49
66
|
el: '#BookReader',
|
50
67
|
/* Url plugin - IA uses History mode for URL */
|
@@ -59,7 +76,6 @@ const initializeBookReader = (brManifest) => {
|
|
59
76
|
enableBookTitleLink: false,
|
60
77
|
bookUrlText: null,
|
61
78
|
startFullscreen: openFullImmersionTheater,
|
62
|
-
initialSearchTerm: searchTerm ? searchTerm : '',
|
63
79
|
// leaving this option commented out bc we change given user agent on archive.org
|
64
80
|
// onePage: { autofit: <?=json_encode($this->ios ? 'width' : 'auto')?> },
|
65
81
|
showToolbar: getFromUrl('options.showToolbar', 'false') === 'true',
|
@@ -70,6 +86,18 @@ const initializeBookReader = (brManifest) => {
|
|
70
86
|
/* End multiple volumes */
|
71
87
|
enableBookmarks: true, // turn this on
|
72
88
|
enableFSLogoShortcut: true,
|
89
|
+
|
90
|
+
// TMP: To be replaced once BookReaderJSIA is updated to provide
|
91
|
+
// these in the right spot.
|
92
|
+
plugins: {
|
93
|
+
search: {
|
94
|
+
enabled: true,
|
95
|
+
initialSearchTerm: searchTerm ? searchTerm : '',
|
96
|
+
searchInsideUrl,
|
97
|
+
preTag: brManifest.data.brOptions.searchInsidePreTag,
|
98
|
+
postTag: brManifest.data.brOptions.searchInsidePostTag,
|
99
|
+
},
|
100
|
+
},
|
73
101
|
};
|
74
102
|
|
75
103
|
// we want to show item as embedded when ?ui=embed is in URI
|
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# 5.0.0-92
|
2
|
+
- Refactor: Migrate search plugin to BookReaderPlugin system @cdrini
|
3
|
+
- Breaking changes:
|
4
|
+
- Search options are now nested under the plugin (eg `searchInsideUrl` → `options.plugins.search.searchInsideUrl`), and mostly no longer include the `search` prefix; specifically:
|
5
|
+
- `searchInsidePreTag`, `searchInsidePostTag` → `options.plugins.search.preTag` and `postTag`
|
6
|
+
- `searchInsideUrl` is now a `StringWithVars` and supports including `vars` from the root options, e.g. `searchInsideUrl: "https://{{server}}/search_endpoint?q={{query|urlencode}}"` . It also has access to the `preTag` and `postTag` from the options. Note this allows the search inside URL to now be fully customizable from the options, and is no longer hard-coded internally to Internet Archive specific logic.
|
7
|
+
- `initialSearchTerm` → `options.plugins.search.initialSearchTerm`
|
8
|
+
- `searchInsideProtocol` → deprecated in favour of variables in `searchInsideUrl`
|
9
|
+
- Internal search related methods/properties are now nested in the `SearchPlugin` and no longer accessible from the root bookreader object. Notable exception: `br.search` is still made available for convenience.
|
10
|
+
- Moved: `searchResults`, `searchXHR`, `searchView`, `cancelSearchRequest`, `BRSearchCallback`, `updateSearchHilites`, `removeSearchResults`, `removeSearchHilites`, `searchHighlightVisible`
|
11
|
+
|
12
|
+
|
1
13
|
# 5.0.0-91
|
2
14
|
- Refactor: Migrate ChaptersPlugin to BookReaderPlugin system @cdrini
|
3
15
|
- Breaking changes:
|
package/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
 [](https://codecov.io/gh/internetarchive/bookreader)
|
4
4
|
|
5
|
-
**Disclaimer: BookReader v5 is currently in beta. It
|
5
|
+
**Disclaimer: BookReader v5 is currently in beta. It is stable enough for production use and is actively deployed on archive.org. Future updates while in v5 beta may introduce breaking changes to public BookReader APIs, although these will be noted in the CHANGELOG.**
|
6
6
|
|
7
7
|
|
8
8
|
<p align="center">
|
package/package.json
CHANGED
@@ -10,6 +10,7 @@ import BookmarksProvider from './bookmarks/bookmarks-provider.js';
|
|
10
10
|
import SharingProvider from './sharing.js';
|
11
11
|
import ViewableFilesProvider from './viewable-files.js';
|
12
12
|
import iaLogo from './assets/ia-logo.js';
|
13
|
+
/** @typedef {import('@/src/BookReader.js').default} BookReader */
|
13
14
|
|
14
15
|
const events = {
|
15
16
|
menuUpdated: 'menuUpdated',
|
@@ -183,7 +184,9 @@ export class BookNavigator extends LitElement {
|
|
183
184
|
providers.downloads = new DownloadProvider(this.baseProviderConfig);
|
184
185
|
}
|
185
186
|
|
186
|
-
|
187
|
+
// Note plugins will never be null-ish in runtime, but some of the unit tests
|
188
|
+
// stub BR with a nullish value there.
|
189
|
+
if (this.bookreader.options.plugins?.search?.enabled) {
|
187
190
|
providers.search = new SearchProvider({
|
188
191
|
...this.baseProviderConfig,
|
189
192
|
/**
|
@@ -193,7 +196,7 @@ export class BookNavigator extends LitElement {
|
|
193
196
|
*/
|
194
197
|
onProviderChange: (brInstance = null, searchUpdates = {}) => {
|
195
198
|
if (brInstance) {
|
196
|
-
|
199
|
+
/** @type {BookReader} refresh br instance reference */
|
197
200
|
this.bookreader = brInstance;
|
198
201
|
}
|
199
202
|
|
@@ -1,7 +1,10 @@
|
|
1
|
+
// @ts-check
|
1
2
|
import { html, nothing } from 'lit';
|
2
3
|
import '@internetarchive/icon-search/icon-search';
|
3
4
|
import './search-results';
|
4
5
|
/** @typedef {import('@/src/plugins/search/plugin.search.js').SearchInsideMatch} SearchInsideMatch */
|
6
|
+
/** @typedef {import('@/src/plugins/search/plugin.search.js').SearchInsideResults} SearchInsideResults */
|
7
|
+
/** @typedef {import('@/src/BookReader.js').default} BookReader */
|
5
8
|
|
6
9
|
let searchState = {
|
7
10
|
query: '',
|
@@ -99,11 +102,14 @@ export default class SearchProvider {
|
|
99
102
|
this.bookreader.search(searchState.query);
|
100
103
|
}
|
101
104
|
|
105
|
+
/**
|
106
|
+
* @param {CustomEvent<{props: {instance: BookReader, results: SearchInsideResults}}>} event
|
107
|
+
*/
|
102
108
|
onSearchRequestError(event, errorType = 'default') {
|
103
|
-
const { detail: { props
|
104
|
-
const { instance
|
109
|
+
const { detail: { props } } = event;
|
110
|
+
const { instance, results } = props;
|
105
111
|
if (instance) {
|
106
|
-
|
112
|
+
/** @type {BookReader} keep bookreader instance reference up-to-date */
|
107
113
|
this.bookreader = instance;
|
108
114
|
}
|
109
115
|
const errorMessages = {
|
@@ -114,7 +120,7 @@ export default class SearchProvider {
|
|
114
120
|
};
|
115
121
|
|
116
122
|
const messageToShow = errorMessages[errorType] ?? errorMessages.default;
|
117
|
-
searchState.query =
|
123
|
+
searchState.query = results?.q || '';
|
118
124
|
searchState.results = [];
|
119
125
|
searchState.resultsCount = 0;
|
120
126
|
searchState.queryInProgress = false;
|
@@ -137,7 +143,7 @@ export default class SearchProvider {
|
|
137
143
|
}
|
138
144
|
|
139
145
|
searchCanceledInMenu() {
|
140
|
-
this.bookreader
|
146
|
+
this.bookreader._plugins.search.cancelSearchRequest();
|
141
147
|
}
|
142
148
|
|
143
149
|
onSearchResultsCleared() {
|
@@ -149,7 +155,7 @@ export default class SearchProvider {
|
|
149
155
|
errorMessage: '',
|
150
156
|
};
|
151
157
|
this.updateMenu({ openMenu: false });
|
152
|
-
this.bookreader
|
158
|
+
this.bookreader._plugins.search.searchView.clearSearchFieldAndResults(false);
|
153
159
|
if (this.bookreader.urlPlugin) {
|
154
160
|
this.updateSearchInUrl();
|
155
161
|
}
|
@@ -198,6 +204,6 @@ export default class SearchProvider {
|
|
198
204
|
* @param {{ detail: {match: SearchInsideMatch} }} param0
|
199
205
|
*/
|
200
206
|
onSearchResultsClicked({ detail }) {
|
201
|
-
this.bookreader.
|
207
|
+
this.bookreader._plugins.search.jumpToMatch(detail.match.matchIndex);
|
202
208
|
}
|
203
209
|
}
|
@@ -10,7 +10,7 @@ export default class SharingProvider {
|
|
10
10
|
}) {
|
11
11
|
const { identifier, creator, title } = item?.metadata;
|
12
12
|
const creatorToUse = Array.isArray(creator) ? creator[0] : creator;
|
13
|
-
const subPrefix = bookreader.
|
13
|
+
const subPrefix = bookreader.subPrefix || '';
|
14
14
|
const label = `Share this book`;
|
15
15
|
this.icon = html`${iauxShareIcon}`;
|
16
16
|
this.label = label;
|
@@ -59,6 +59,11 @@ export class Toolbar {
|
|
59
59
|
$titleSectionEl.append(br.bookUrlText || br.bookTitle);
|
60
60
|
}
|
61
61
|
|
62
|
+
// Call _bindNavigationHandlers on the plugins
|
63
|
+
for (const plugin of Object.values(br._plugins)) {
|
64
|
+
plugin._configureToolbar(br.refs.$BRtoolbar);
|
65
|
+
}
|
66
|
+
|
62
67
|
// const $hamburger = br.refs.$BRtoolbar.find('BRtoolbarHamburger');
|
63
68
|
return br.refs.$BRtoolbar;
|
64
69
|
}
|
@@ -141,19 +141,21 @@ export const DEFAULT_OPTIONS = {
|
|
141
141
|
* but going forward we'll keep them here.
|
142
142
|
**/
|
143
143
|
plugins: {
|
144
|
-
/** @type {import('../plugins/plugin.archive_analytics.js').ArchiveAnalyticsPlugin['options']}*/
|
144
|
+
/** @type {Partial<import('../plugins/plugin.archive_analytics.js').ArchiveAnalyticsPlugin['options'>]}*/
|
145
145
|
archiveAnalytics: null,
|
146
|
-
/** @type {import('../plugins/plugin.autoplay.js').AutoplayPlugin['options']}*/
|
146
|
+
/** @type {Partial<import('../plugins/plugin.autoplay.js').AutoplayPlugin['options'>]}*/
|
147
147
|
autoplay: null,
|
148
|
-
/** @type {import('../plugins/plugin.chapters.js').ChaptersPlugin['options']} */
|
148
|
+
/** @type {Partial<import('../plugins/plugin.chapters.js').ChaptersPlugin['options']>} */
|
149
149
|
chapters: null,
|
150
|
-
/** @type {import('../plugins/plugin.iiif.js').IiifPlugin['options']} */
|
150
|
+
/** @type {Partial<import('../plugins/plugin.iiif.js').IiifPlugin['options']>} */
|
151
151
|
iiif: null,
|
152
|
-
/** @type {import('../plugins/plugin.resume.js').ResumePlugin['options']} */
|
152
|
+
/** @type {Partial<import('../plugins/plugin.resume.js').ResumePlugin['options']>} */
|
153
153
|
resume: null,
|
154
|
-
/** @type {import('../plugins/plugin.
|
154
|
+
/** @type {Partial<import('../plugins/search/plugin.search.js').SearchPlugin['options']>} */
|
155
|
+
search: null,
|
156
|
+
/** @type {Partial<import('../plugins/plugin.text_selection.js').TextSelectionPlugin['options']>} */
|
155
157
|
textSelection: null,
|
156
|
-
/** @type {import('../plugins/tts/plugin.tts.js').TtsPlugin['options']} */
|
158
|
+
/** @type {Partial<import('../plugins/tts/plugin.tts.js').TtsPlugin['options']>} */
|
157
159
|
tts: null,
|
158
160
|
},
|
159
161
|
|
package/src/BookReader.js
CHANGED
@@ -74,6 +74,8 @@ BookReader.PLUGINS = {
|
|
74
74
|
chapters: null,
|
75
75
|
/** @type {typeof import('./plugins/plugin.resume.js').ResumePlugin | null}*/
|
76
76
|
resume: null,
|
77
|
+
/** @type {typeof import('./plugins/search/plugin.search.js').SearchPlugin | null}*/
|
78
|
+
search: null,
|
77
79
|
/** @type {typeof import('./plugins/plugin.text_selection.js').TextSelectionPlugin | null}*/
|
78
80
|
textSelection: null,
|
79
81
|
/** @type {typeof import('./plugins/tts/plugin.tts.js').TtsPlugin | null}*/
|
@@ -121,11 +123,22 @@ BookReader.prototype.setup = function(options) {
|
|
121
123
|
/** @type {import('@/src/BookNavigator/book-navigator.js').BookNavigator} */
|
122
124
|
this.shell;
|
123
125
|
|
126
|
+
// Base server used by some api calls
|
127
|
+
/** @deprecated */
|
128
|
+
this.bookId = options.bookId;
|
129
|
+
/** @deprecated */
|
130
|
+
this.server = options.server;
|
131
|
+
/** @deprecated */
|
132
|
+
this.subPrefix = options.subPrefix;
|
133
|
+
/** @deprecated */
|
134
|
+
this.bookPath = options.bookPath;
|
135
|
+
|
124
136
|
// Construct the usual plugins first to get type hints
|
125
137
|
this._plugins = {
|
126
138
|
archiveAnalytics: BookReader.PLUGINS.archiveAnalytics ? new BookReader.PLUGINS.archiveAnalytics(this) : null,
|
127
139
|
autoplay: BookReader.PLUGINS.autoplay ? new BookReader.PLUGINS.autoplay(this) : null,
|
128
140
|
chapters: BookReader.PLUGINS.chapters ? new BookReader.PLUGINS.chapters(this) : null,
|
141
|
+
search: BookReader.PLUGINS.search ? new BookReader.PLUGINS.search(this) : null,
|
129
142
|
resume: BookReader.PLUGINS.resume ? new BookReader.PLUGINS.resume(this) : null,
|
130
143
|
textSelection: BookReader.PLUGINS.textSelection ? new BookReader.PLUGINS.textSelection(this) : null,
|
131
144
|
tts: BookReader.PLUGINS.tts ? new BookReader.PLUGINS.tts(this) : null,
|
@@ -154,12 +167,14 @@ BookReader.prototype.setup = function(options) {
|
|
154
167
|
}
|
155
168
|
}
|
156
169
|
|
170
|
+
if (this._plugins.search?.options.enabled) {
|
171
|
+
// Expose the search method for convenience / backward compat
|
172
|
+
this.search = this._plugins.search.search.bind(this._plugins.search);
|
173
|
+
}
|
174
|
+
|
157
175
|
/** @type {number} @deprecated some past iterations set this */
|
158
176
|
this.numLeafs = undefined;
|
159
177
|
|
160
|
-
/** Overridden by plugin.search.js */
|
161
|
-
this.enableSearch = false;
|
162
|
-
|
163
178
|
/**
|
164
179
|
* Store viewModeOrder states
|
165
180
|
* @var {boolean}
|
@@ -447,23 +462,24 @@ BookReader.prototype.initParams = function() {
|
|
447
462
|
}
|
448
463
|
|
449
464
|
// Check for Search plugin
|
450
|
-
if (this.options.
|
465
|
+
if (this._plugins.search?.options.enabled) {
|
466
|
+
const sp = this._plugins.search;
|
451
467
|
// Go to first result only if no default or URL page
|
452
|
-
|
468
|
+
sp.options.goToFirstResult = !params.pageFound;
|
453
469
|
|
454
470
|
// If initialSearchTerm not set
|
455
|
-
if (!
|
471
|
+
if (!sp.initialSearchTerm) {
|
456
472
|
// Look for any term in URL
|
457
473
|
if (params.search) {
|
458
474
|
// Old style: /search/[term]
|
459
|
-
|
460
|
-
|
475
|
+
sp.options.initialSearchTerm = params.search;
|
476
|
+
sp.searchTerm = params.search;
|
461
477
|
} else {
|
462
478
|
// If we have a query string: q=[term]
|
463
479
|
const searchParams = new URLSearchParams(this.readQueryString());
|
464
480
|
const searchTerm = searchParams.get('q');
|
465
481
|
if (searchTerm) {
|
466
|
-
|
482
|
+
sp.options.initialSearchTerm = utils.decodeURIComponentPlus(searchTerm);
|
467
483
|
}
|
468
484
|
}
|
469
485
|
}
|
@@ -644,7 +660,7 @@ BookReader.prototype.init = function() {
|
|
644
660
|
}
|
645
661
|
|
646
662
|
// If not searching, set to allow on-going fragment changes
|
647
|
-
if (!this.options.initialSearchTerm) {
|
663
|
+
if (!this._plugins.search?.options.initialSearchTerm) {
|
648
664
|
this.suppressFragmentChange = false;
|
649
665
|
}
|
650
666
|
|
@@ -1308,7 +1324,7 @@ BookReader.prototype.updateFirstIndex = function(
|
|
1308
1324
|
// If there's an initial search we stop suppressing global URL changes
|
1309
1325
|
// when local suppression ends
|
1310
1326
|
// This seems to correctly handle multiple calls during mode/1up
|
1311
|
-
if (this.options.initialSearchTerm && !suppressFragmentChange) {
|
1327
|
+
if (this._plugins.search.options.initialSearchTerm && !suppressFragmentChange) {
|
1312
1328
|
this.suppressFragmentChange = false;
|
1313
1329
|
}
|
1314
1330
|
|
@@ -1637,8 +1653,8 @@ BookReader.prototype.updateFromParams = function(params) {
|
|
1637
1653
|
// process /search
|
1638
1654
|
// @deprecated for urlMode 'history'
|
1639
1655
|
// Continues to work for urlMode 'hash'
|
1640
|
-
if (this.
|
1641
|
-
if (this.searchTerm !== params.search) {
|
1656
|
+
if (this._plugins.search?.enabled && 'undefined' != typeof(params.search)) {
|
1657
|
+
if (this._plugins.search.searchTerm !== params.search) {
|
1642
1658
|
this.$('.BRsearchInput').val(params.search);
|
1643
1659
|
}
|
1644
1660
|
}
|
@@ -1842,8 +1858,8 @@ BookReader.prototype.paramsFromCurrent = function() {
|
|
1842
1858
|
params.view = fullscreenView;
|
1843
1859
|
}
|
1844
1860
|
// Search
|
1845
|
-
if (this.
|
1846
|
-
params.search = this.searchTerm;
|
1861
|
+
if (this._plugins.search?.enabled) {
|
1862
|
+
params.search = this._plugins.search.searchTerm;
|
1847
1863
|
}
|
1848
1864
|
|
1849
1865
|
return params;
|
package/src/BookReaderPlugin.js
CHANGED
@@ -34,6 +34,14 @@ export class BookReaderPlugin {
|
|
34
34
|
_configurePageContainer(pageContainer) {
|
35
35
|
}
|
36
36
|
|
37
|
+
/**
|
38
|
+
* @abstract
|
39
|
+
* @protected
|
40
|
+
* @param {JQuery<HTMLElement>} $toolbarElement
|
41
|
+
*/
|
42
|
+
_configureToolbar($toolbarElement) {
|
43
|
+
}
|
44
|
+
|
37
45
|
/** @abstract @protected */
|
38
46
|
_bindNavigationHandlers() {}
|
39
47
|
|
@@ -265,7 +265,9 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
265
265
|
const $textLayer = $container.find('.BRtextLayer');
|
266
266
|
if (!$textLayer.length) return;
|
267
267
|
$textLayer.each((i, s) => this.defaultMode(s));
|
268
|
-
this.
|
268
|
+
if (!this.br.protected) {
|
269
|
+
this.interceptCopy($container);
|
270
|
+
}
|
269
271
|
}
|
270
272
|
|
271
273
|
/**
|