@internetarchive/bookreader 5.0.0-88-alpha.7 → 5.0.0-88-alpha.9
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/babel.config.js +30 -12
- package/dist/esm/BookNavigator/assets/bookmark-colors.js +4 -0
- package/dist/esm/BookNavigator/assets/button-base.js +4 -0
- package/dist/esm/BookNavigator/assets/ia-logo.js +4 -0
- package/dist/esm/BookNavigator/assets/icon_checkmark.js +8 -0
- package/dist/esm/BookNavigator/assets/icon_close.js +4 -0
- package/dist/esm/BookNavigator/book-navigator.js +612 -0
- package/dist/esm/BookNavigator/bookmarks/bookmark-button.js +35 -0
- package/dist/esm/BookNavigator/bookmarks/bookmark-edit.js +78 -0
- package/dist/esm/BookNavigator/bookmarks/bookmarks-list.js +160 -0
- package/dist/esm/BookNavigator/bookmarks/bookmarks-loginCTA.js +24 -0
- package/dist/esm/BookNavigator/bookmarks/bookmarks-provider.js +55 -0
- package/dist/esm/BookNavigator/bookmarks/ia-bookmarks.js +521 -0
- package/dist/esm/BookNavigator/delete-modal-actions.js +29 -0
- package/dist/esm/BookNavigator/downloads/downloads-provider.js +84 -0
- package/dist/esm/BookNavigator/downloads/downloads.js +69 -0
- package/dist/esm/BookNavigator/search/search-provider.js +238 -0
- package/dist/esm/BookNavigator/search/search-results.js +161 -0
- package/dist/esm/BookNavigator/sharing.js +26 -0
- package/dist/esm/BookNavigator/viewable-files.js +94 -0
- package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments-provider.js +83 -0
- package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments.js +131 -0
- package/dist/esm/BookReader/BookModel.js +575 -0
- package/dist/esm/BookReader/DragScrollable.js +224 -0
- package/dist/esm/BookReader/ImageCache.js +122 -0
- package/dist/esm/BookReader/Mode1Up.js +114 -0
- package/dist/esm/BookReader/Mode1UpLit.js +579 -0
- package/dist/esm/BookReader/Mode2Up.js +106 -0
- package/dist/esm/BookReader/Mode2UpLit.js +1020 -0
- package/dist/esm/BookReader/ModeCoordinateSpace.js +28 -0
- package/dist/esm/BookReader/ModeSmoothZoom.js +318 -0
- package/dist/esm/BookReader/ModeThumb.js +366 -0
- package/dist/esm/BookReader/Navbar/Navbar.js +253 -0
- package/dist/esm/BookReader/PageContainer.js +165 -0
- package/dist/esm/BookReader/ReduceSet.js +27 -0
- package/dist/esm/BookReader/Toolbar/Toolbar.js +242 -0
- package/dist/esm/BookReader/events.js +20 -0
- package/dist/esm/BookReader/options.js +331 -0
- package/dist/esm/BookReader/utils/HTMLDimensionsCacher.js +48 -0
- package/dist/esm/BookReader/utils/ScrollClassAdder.js +31 -0
- package/dist/esm/BookReader/utils/SelectionObserver.js +42 -0
- package/dist/esm/BookReader/utils/classes.js +37 -0
- package/dist/esm/BookReader/utils.js +315 -0
- package/dist/esm/BookReader.js +1827 -0
- package/dist/esm/assets/icons/1up.svg +12 -0
- package/dist/esm/assets/icons/2up.svg +15 -0
- package/dist/esm/assets/icons/advance.svg +26 -0
- package/dist/esm/assets/icons/chevron-right.svg +1 -0
- package/dist/esm/assets/icons/close-circle-dark.svg +1 -0
- package/dist/esm/assets/icons/close-circle.svg +1 -0
- package/dist/esm/assets/icons/fullscreen.svg +17 -0
- package/dist/esm/assets/icons/fullscreen_exit.svg +17 -0
- package/dist/esm/assets/icons/hamburger.svg +15 -0
- package/dist/esm/assets/icons/left-arrow.svg +12 -0
- package/dist/esm/assets/icons/magnify-minus.svg +12 -0
- package/dist/esm/assets/icons/magnify-plus.svg +13 -0
- package/dist/esm/assets/icons/magnify.svg +15 -0
- package/dist/esm/assets/icons/pause.svg +23 -0
- package/dist/esm/assets/icons/play.svg +22 -0
- package/dist/esm/assets/icons/playback-speed.svg +34 -0
- package/dist/esm/assets/icons/read-aloud.svg +22 -0
- package/dist/esm/assets/icons/review.svg +22 -0
- package/dist/esm/assets/icons/thumbnails.svg +17 -0
- package/dist/esm/assets/icons/voice.svg +1 -0
- package/dist/esm/assets/icons/volume-full.svg +22 -0
- package/dist/esm/assets/images/BRicons.png +0 -0
- package/dist/esm/assets/images/BRicons.svg +94 -0
- package/dist/esm/assets/images/BRicons_ia.png +0 -0
- package/dist/esm/assets/images/back_pages.png +0 -0
- package/dist/esm/assets/images/book_bottom_icon.png +0 -0
- package/dist/esm/assets/images/book_down_icon.png +0 -0
- package/dist/esm/assets/images/book_left_icon.png +0 -0
- package/dist/esm/assets/images/book_leftmost_icon.png +0 -0
- package/dist/esm/assets/images/book_right_icon.png +0 -0
- package/dist/esm/assets/images/book_rightmost_icon.png +0 -0
- package/dist/esm/assets/images/book_top_icon.png +0 -0
- package/dist/esm/assets/images/book_up_icon.png +0 -0
- package/dist/esm/assets/images/books_graphic.svg +177 -0
- package/dist/esm/assets/images/booksplit.png +0 -0
- package/dist/esm/assets/images/control_pause_icon.png +0 -0
- package/dist/esm/assets/images/control_play_icon.png +0 -0
- package/dist/esm/assets/images/embed_icon.png +0 -0
- package/dist/esm/assets/images/icon-home-ia.png +0 -0
- package/dist/esm/assets/images/icon_OL-logo-xs.png +0 -0
- package/dist/esm/assets/images/icon_alert-xs.png +0 -0
- package/dist/esm/assets/images/icon_book.svg +12 -0
- package/dist/esm/assets/images/icon_bookmark.svg +12 -0
- package/dist/esm/assets/images/icon_close-pop.png +0 -0
- package/dist/esm/assets/images/icon_download.png +0 -0
- package/dist/esm/assets/images/icon_gear.svg +14 -0
- package/dist/esm/assets/images/icon_hamburger.svg +20 -0
- package/dist/esm/assets/images/icon_home.png +0 -0
- package/dist/esm/assets/images/icon_home.svg +21 -0
- package/dist/esm/assets/images/icon_home_ia.png +0 -0
- package/dist/esm/assets/images/icon_indicator.png +0 -0
- package/dist/esm/assets/images/icon_info.svg +11 -0
- package/dist/esm/assets/images/icon_one_page.svg +8 -0
- package/dist/esm/assets/images/icon_pause.svg +1 -0
- package/dist/esm/assets/images/icon_play.svg +1 -0
- package/dist/esm/assets/images/icon_playback-rate.svg +15 -0
- package/dist/esm/assets/images/icon_return.png +0 -0
- package/dist/esm/assets/images/icon_search_button.svg +8 -0
- package/dist/esm/assets/images/icon_share.svg +9 -0
- package/dist/esm/assets/images/icon_skip-ahead.svg +6 -0
- package/dist/esm/assets/images/icon_skip-back.svg +13 -0
- package/dist/esm/assets/images/icon_speaker.svg +18 -0
- package/dist/esm/assets/images/icon_speaker_open.svg +10 -0
- package/dist/esm/assets/images/icon_thumbnails.svg +12 -0
- package/dist/esm/assets/images/icon_toc.svg +5 -0
- package/dist/esm/assets/images/icon_two_pages.svg +9 -0
- package/dist/esm/assets/images/icon_zoomer.png +0 -0
- package/dist/esm/assets/images/loading.gif +0 -0
- package/dist/esm/assets/images/logo_icon.png +0 -0
- package/dist/esm/assets/images/marker_chap-off.png +0 -0
- package/dist/esm/assets/images/marker_chap-off.svg +11 -0
- package/dist/esm/assets/images/marker_chap-off_ia.png +0 -0
- package/dist/esm/assets/images/marker_chap-on.png +0 -0
- package/dist/esm/assets/images/marker_chap-on.svg +11 -0
- package/dist/esm/assets/images/marker_srch-on.svg +11 -0
- package/dist/esm/assets/images/marker_srchchap-off.png +0 -0
- package/dist/esm/assets/images/marker_srchchap-on.png +0 -0
- package/dist/esm/assets/images/nav_control-dn.png +0 -0
- package/dist/esm/assets/images/nav_control-dn_ia.png +0 -0
- package/dist/esm/assets/images/nav_control-up.png +0 -0
- package/dist/esm/assets/images/nav_control-up_ia.png +0 -0
- package/dist/esm/assets/images/nav_control.png +0 -0
- package/dist/esm/assets/images/one_page_mode_icon.png +0 -0
- package/dist/esm/assets/images/paper-badge.png +0 -0
- package/dist/esm/assets/images/print_icon.png +0 -0
- package/dist/esm/assets/images/progressbar.gif +0 -0
- package/dist/esm/assets/images/right_edges.png +0 -0
- package/dist/esm/assets/images/slider.png +0 -0
- package/dist/esm/assets/images/slider_ia.png +0 -0
- package/dist/esm/assets/images/thumbnail_mode_icon.png +0 -0
- package/dist/esm/assets/images/transparent.png +0 -0
- package/dist/esm/assets/images/two_page_mode_icon.png +0 -0
- package/dist/esm/assets/images/unviewable_page.png +0 -0
- package/dist/esm/assets/images/zoom_in_icon.png +0 -0
- package/dist/esm/assets/images/zoom_out_icon.png +0 -0
- package/dist/esm/css/BookReader.scss +85 -0
- package/dist/esm/css/_BRBookmarks.scss +29 -0
- package/dist/esm/css/_BRComponent.scss +13 -0
- package/dist/esm/css/_BRfloat.scss +197 -0
- package/dist/esm/css/_BRicon.scss +54 -0
- package/dist/esm/css/_BRmain.scss +262 -0
- package/dist/esm/css/_BRnav.scss +354 -0
- package/dist/esm/css/_BRpages.scss +213 -0
- package/dist/esm/css/_BRsearch.scss +268 -0
- package/dist/esm/css/_BRtoolbar.scss +84 -0
- package/dist/esm/css/_BRvendor.scss +5 -0
- package/dist/esm/css/_TextSelection.scss +108 -0
- package/dist/esm/css/_colorbox.scss +52 -0
- package/dist/esm/css/_controls.scss +257 -0
- package/dist/esm/css/_icons.scss +121 -0
- package/dist/esm/ia-bookreader/ia-bookreader.js +141 -0
- package/dist/esm/jquery-wrapper.js +3 -0
- package/dist/esm/plugins/plugin.archive_analytics.js +72 -0
- package/dist/esm/plugins/plugin.autoplay.js +119 -0
- package/dist/esm/plugins/plugin.chapters.js +288 -0
- package/dist/esm/plugins/plugin.iframe.js +44 -0
- package/dist/esm/plugins/plugin.iiif.js +146 -0
- package/dist/esm/plugins/plugin.resume.js +66 -0
- package/dist/esm/plugins/plugin.text_selection.js +621 -0
- package/dist/esm/plugins/plugin.vendor-fullscreen.js +227 -0
- package/dist/esm/plugins/search/plugin.search.js +499 -0
- package/dist/esm/plugins/search/utils.js +42 -0
- package/dist/esm/plugins/search/view.js +360 -0
- package/dist/esm/plugins/tts/AbstractTTSEngine.js +282 -0
- package/dist/esm/plugins/tts/FestivalTTSEngine.js +192 -0
- package/dist/esm/plugins/tts/PageChunk.js +105 -0
- package/dist/esm/plugins/tts/PageChunkIterator.js +155 -0
- package/dist/esm/plugins/tts/WebTTSEngine.js +364 -0
- package/dist/esm/plugins/tts/plugin.tts.js +315 -0
- package/dist/esm/plugins/tts/tooltip_dict.js +14 -0
- package/dist/esm/plugins/tts/utils.js +79 -0
- package/dist/esm/plugins/url/UrlPlugin.js +197 -0
- package/dist/esm/plugins/url/plugin.url.js +212 -0
- package/dist/esm/util/browserSniffing.js +56 -0
- package/dist/esm/util/debouncer.js +25 -0
- package/dist/esm/util/docCookies.js +75 -0
- package/dist/esm/util/strings.js +34 -0
- package/jsconfig.json +1 -0
- package/package.json +13 -6
@@ -0,0 +1,360 @@
|
|
1
|
+
class SearchView {
|
2
|
+
/**
|
3
|
+
* @param {object} params
|
4
|
+
* @param {object} params.br The BookReader instance
|
5
|
+
* @param {function} params.cancelSearch callback when a user wants to cancel search
|
6
|
+
*
|
7
|
+
* @event BookReader:SearchResultsCleared - when the search results nav gets cleared
|
8
|
+
* @event BookReader:ToggleSearchMenu - when search results menu should toggle
|
9
|
+
*/
|
10
|
+
constructor(_ref) {
|
11
|
+
var {
|
12
|
+
br,
|
13
|
+
searchCancelledCallback = () => {}
|
14
|
+
} = _ref;
|
15
|
+
this.br = br;
|
16
|
+
this.matches = [];
|
17
|
+
this.cacheDOMElements();
|
18
|
+
this.cancelSearch = searchCancelledCallback;
|
19
|
+
}
|
20
|
+
init() {
|
21
|
+
this.bindEvents();
|
22
|
+
}
|
23
|
+
cacheDOMElements() {
|
24
|
+
this.dom = {};
|
25
|
+
// Search input within the top toolbar. Will be removed once the mobile menu is replaced.
|
26
|
+
this.dom.toolbarSearch = this.buildToolbarSearch();
|
27
|
+
}
|
28
|
+
|
29
|
+
/**
|
30
|
+
* @param {string} query
|
31
|
+
*/
|
32
|
+
setQuery(query) {
|
33
|
+
this.br.$('[name="query"]').val(query);
|
34
|
+
}
|
35
|
+
emptyMatches() {
|
36
|
+
this.matches = [];
|
37
|
+
}
|
38
|
+
removeResultPins() {
|
39
|
+
this.br.$('.BRnavpos .BRsearch').remove();
|
40
|
+
}
|
41
|
+
clearSearchFieldAndResults() {
|
42
|
+
var dispatchEventWhenComplete = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
43
|
+
this.br.removeSearchResults();
|
44
|
+
this.removeResultPins();
|
45
|
+
this.emptyMatches();
|
46
|
+
this.setQuery('');
|
47
|
+
this.teardownSearchNavigation();
|
48
|
+
if (dispatchEventWhenComplete) {
|
49
|
+
this.br.trigger('SearchResultsCleared');
|
50
|
+
}
|
51
|
+
}
|
52
|
+
toggleSidebar() {
|
53
|
+
this.br.trigger('ToggleSearchMenu');
|
54
|
+
}
|
55
|
+
renderSearchNavigation() {
|
56
|
+
var selector = 'BRsearch-navigation';
|
57
|
+
$('.BRnav').before("\n <div class=\"".concat(selector, "\">\n <button class=\"toggle-sidebar\">\n <h4>\n <span class=\"icon icon-search\"></span> Results\n </h4>\n </button>\n <div class=\"pagination\">\n <button class=\"prev\" title=\"Previous result\"><span class=\"icon icon-chevron hflip\"></span></button>\n <span data-id=\"resultsCount\">").concat(this.resultsPosition(), "</span>\n <button class=\"next\" title=\"Next result\"><span class=\"icon icon-chevron\"></button>\n </div>\n <button class=\"clear\" title=\"Clear search results\">\n <span class=\"icon icon-close\"></span>\n </button>\n </div>\n "));
|
58
|
+
this.dom.searchNavigation = $(".".concat(selector));
|
59
|
+
}
|
60
|
+
resultsPosition() {
|
61
|
+
var positionMessage = "".concat(this.matches.length, " result").concat(this.matches.length === 1 ? '' : 's');
|
62
|
+
if (~this.currentMatchIndex) {
|
63
|
+
positionMessage = "".concat(this.currentMatchIndex + 1, " / ").concat(this.matches.length);
|
64
|
+
}
|
65
|
+
return positionMessage;
|
66
|
+
}
|
67
|
+
bindSearchNavigationEvents() {
|
68
|
+
if (!this.dom.searchNavigation) {
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
var namespace = 'searchNavigation';
|
72
|
+
this.dom.searchNavigation.on("click.".concat(namespace), '.clear', this.clearSearchFieldAndResults.bind(this)).on("click.".concat(namespace), '.prev', this.showPrevResult.bind(this)).on("click.".concat(namespace), '.next', this.showNextResult.bind(this)).on("click.".concat(namespace), '.toggle-sidebar', this.toggleSidebar.bind(this)).on("click.".concat(namespace), false);
|
73
|
+
}
|
74
|
+
showPrevResult() {
|
75
|
+
if (this.currentMatchIndex === 0) {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
if (this.br.mode === this.br.constModeThumb) {
|
79
|
+
this.br.switchMode(this.br.constMode1up);
|
80
|
+
}
|
81
|
+
if (!~this.currentMatchIndex) {
|
82
|
+
this.currentMatchIndex = this.getClosestMatchIndex((start, end, comparator) => end[0] > comparator) + 1;
|
83
|
+
}
|
84
|
+
this.br.$('.BRnavline .BRsearch').eq(--this.currentMatchIndex).click();
|
85
|
+
this.updateResultsPosition();
|
86
|
+
this.updateSearchNavigationButtons();
|
87
|
+
}
|
88
|
+
showNextResult() {
|
89
|
+
if (this.currentMatchIndex + 1 === this.matches.length) {
|
90
|
+
return;
|
91
|
+
}
|
92
|
+
if (this.br.mode === this.br.constModeThumb) {
|
93
|
+
this.br.switchMode(this.br.constMode1up);
|
94
|
+
}
|
95
|
+
if (!~this.currentMatchIndex) {
|
96
|
+
this.currentMatchIndex = this.getClosestMatchIndex((start, end, comparator) => start[start.length - 1] > comparator) - 1;
|
97
|
+
}
|
98
|
+
this.br.$('.BRnavline .BRsearch').eq(++this.currentMatchIndex).click();
|
99
|
+
this.updateResultsPosition();
|
100
|
+
this.updateSearchNavigationButtons();
|
101
|
+
}
|
102
|
+
|
103
|
+
/**
|
104
|
+
* Obtains closest match based on the logical comparison function passed in.
|
105
|
+
* When the comparison function returns true, the starting (left) half of the
|
106
|
+
* matches array is used in the binary split, else the ending (right) half is
|
107
|
+
* used. A recursive call is made to perform the same split and comparison
|
108
|
+
* on the winning half of the matches. This is traditionally known as binary
|
109
|
+
* search (https://en.wikipedia.org/wiki/Binary_search_algorithm), and in
|
110
|
+
* most cases (medium to large search result arrays) should outperform
|
111
|
+
* traversing the array from start to finish. In the case of small arrays,
|
112
|
+
* the speed difference is negligible.
|
113
|
+
*
|
114
|
+
* @param {function} comparisonFn
|
115
|
+
* @return {number} matchIndex
|
116
|
+
*/
|
117
|
+
getClosestMatchIndex(comparisonFn) {
|
118
|
+
var matchPages = this.matches.map(m => m.par[0].page);
|
119
|
+
var currentPage = this.br.currentIndex() + 1;
|
120
|
+
var closestTo = (pool, comparator) => {
|
121
|
+
if (pool.length === 1) {
|
122
|
+
return pool[0];
|
123
|
+
}
|
124
|
+
var start = pool.slice(0, pool.length / 2);
|
125
|
+
var end = pool.slice(pool.length / 2);
|
126
|
+
return closestTo(comparisonFn(start, end, comparator) ? start : end, comparator);
|
127
|
+
};
|
128
|
+
var closestPage = closestTo(matchPages, currentPage);
|
129
|
+
return this.matches.indexOf(this.matches.find(m => m.par[0].page === closestPage));
|
130
|
+
}
|
131
|
+
updateResultsPosition() {
|
132
|
+
if (!this.dom.searchNavigation) return;
|
133
|
+
this.dom.searchNavigation.find('[data-id=resultsCount]').text(this.resultsPosition());
|
134
|
+
}
|
135
|
+
updateSearchNavigationButtons() {
|
136
|
+
if (!this.dom.searchNavigation) return;
|
137
|
+
this.dom.searchNavigation.find('.prev').attr('disabled', !this.currentMatchIndex);
|
138
|
+
this.dom.searchNavigation.find('.next').attr('disabled', this.currentMatchIndex + 1 === this.matches.length);
|
139
|
+
}
|
140
|
+
teardownSearchNavigation() {
|
141
|
+
if (!this.dom.searchNavigation) {
|
142
|
+
this.dom.searchNavigation = $('.BRsearch-navigation');
|
143
|
+
}
|
144
|
+
if (!this.dom.searchNavigation.length) {
|
145
|
+
return;
|
146
|
+
}
|
147
|
+
this.dom.searchNavigation.off('.searchNavigation').remove();
|
148
|
+
this.dom.searchNavigation = null;
|
149
|
+
this.br.resize();
|
150
|
+
}
|
151
|
+
setCurrentMatchIndex() {
|
152
|
+
var matchingSearchResult;
|
153
|
+
if (this.br.mode === this.br.constModeThumb) {
|
154
|
+
this.currentMatchIndex = -1;
|
155
|
+
return;
|
156
|
+
}
|
157
|
+
if (this.br.mode === this.br.constMode2up) {
|
158
|
+
matchingSearchResult = this.find2upMatchingSearchResult();
|
159
|
+
} else {
|
160
|
+
matchingSearchResult = this.find1upMatchingSearchResult();
|
161
|
+
}
|
162
|
+
this.currentMatchIndex = this.matches.indexOf(matchingSearchResult);
|
163
|
+
}
|
164
|
+
find1upMatchingSearchResult() {
|
165
|
+
return this.matches.find(m => this.br.currentIndex() === m.par[0].page - 1);
|
166
|
+
}
|
167
|
+
find2upMatchingSearchResult() {
|
168
|
+
return this.matches.find(m => this.br._isIndexDisplayed(m.par[0].page - 1));
|
169
|
+
}
|
170
|
+
updateSearchNavigation() {
|
171
|
+
if (!this.matches.length) {
|
172
|
+
return;
|
173
|
+
}
|
174
|
+
this.setCurrentMatchIndex();
|
175
|
+
this.updateResultsPosition();
|
176
|
+
this.updateSearchNavigationButtons();
|
177
|
+
}
|
178
|
+
|
179
|
+
/**
|
180
|
+
* @param {boolean} bool
|
181
|
+
*/
|
182
|
+
togglePinsFor(bool) {
|
183
|
+
var pinsVisibleState = bool ? 'visible' : 'hidden';
|
184
|
+
this.br.refs.$BRfooter.find('.BRsearch').css({
|
185
|
+
visibility: pinsVisibleState
|
186
|
+
});
|
187
|
+
}
|
188
|
+
buildToolbarSearch() {
|
189
|
+
var toolbarSearch = document.createElement('span');
|
190
|
+
toolbarSearch.classList.add('BRtoolbarSection', 'BRtoolbarSectionSearch');
|
191
|
+
toolbarSearch.innerHTML = "\n <form class=\"BRbooksearch desktop\">\n <input type=\"search\" name=\"query\" class=\"BRsearchInput\" value=\"\" placeholder=\"Search inside\"/>\n <button type=\"submit\" class=\"BRsearchSubmit\">\n <img src=\"".concat(this.br.imagesBaseURL, "icon_search_button.svg\" />\n </button>\n </form>\n ");
|
192
|
+
return toolbarSearch;
|
193
|
+
}
|
194
|
+
|
195
|
+
/**
|
196
|
+
* @param {array} matches
|
197
|
+
*/
|
198
|
+
renderPins(matches) {
|
199
|
+
matches.forEach(match => {
|
200
|
+
var pageIndex = this.br.book.leafNumToIndex(match.par[0].page);
|
201
|
+
var uiStringSearch = "Search result"; // i18n
|
202
|
+
var percentThrough = this.br.constructor.util.cssPercentage(pageIndex, this.br.book.getNumLeafs() - 1);
|
203
|
+
var html = match.html;
|
204
|
+
if (html.length > 200) {
|
205
|
+
var start = Math.max(0, html.indexOf('<mark>') - 100);
|
206
|
+
if (start != 0) {
|
207
|
+
html = '…' + match.html.substring(start)
|
208
|
+
// Make sure at word boundary though
|
209
|
+
.replace(/^\S+/, '');
|
210
|
+
}
|
211
|
+
}
|
212
|
+
// draw marker
|
213
|
+
$('<div>').addClass('BRsearch').css({
|
214
|
+
left: percentThrough
|
215
|
+
}).attr('title', uiStringSearch).append("\n <div class=\"BRquery\">\n <main>".concat(html, "</main>\n <footer>Page ").concat(match.displayPageNumber, "</footer>\n </div>\n ")).appendTo(this.br.$('.BRnavline')).on("mouseenter", event => {
|
216
|
+
// remove from other markers then turn on just for this
|
217
|
+
// XXX should be done when nav slider moves
|
218
|
+
var marker = event.currentTarget;
|
219
|
+
var tooltip = marker.querySelector('.BRquery');
|
220
|
+
var tooltipOffset = tooltip.getBoundingClientRect();
|
221
|
+
var targetOffset = marker.getBoundingClientRect();
|
222
|
+
var boxSizeAdjust = parseInt(getComputedStyle(tooltip).paddingLeft) * 2;
|
223
|
+
if (tooltipOffset.x - boxSizeAdjust < 0) {
|
224
|
+
tooltip.style.setProperty('transform', "translateX(-".concat(targetOffset.left - boxSizeAdjust, "px)"));
|
225
|
+
}
|
226
|
+
$('.BRsearch,.BRchapter').removeClass('front');
|
227
|
+
$(event.target).addClass('front');
|
228
|
+
}).on("mouseleave", event => $(event.target).removeClass('front')).on("click", () => {
|
229
|
+
this.br._searchPluginGoToResult(match.matchIndex);
|
230
|
+
});
|
231
|
+
});
|
232
|
+
}
|
233
|
+
|
234
|
+
/**
|
235
|
+
* @param {boolean} bool
|
236
|
+
*/
|
237
|
+
toggleSearchPending(bool) {
|
238
|
+
if (bool) {
|
239
|
+
this.br.showProgressPopup("Search results will appear below...", () => this.progressPopupClosed());
|
240
|
+
} else {
|
241
|
+
this.br.removeProgressPopup();
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
/**
|
246
|
+
* Primary callback when user cancels search popup
|
247
|
+
*/
|
248
|
+
progressPopupClosed() {
|
249
|
+
this.toggleSearchPending();
|
250
|
+
this.cancelSearch();
|
251
|
+
}
|
252
|
+
renderErrorModal() {
|
253
|
+
var textIsProcessing = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
254
|
+
var errorDetails = "".concat(!textIsProcessing ? 'The text may still be processing. ' : '', "Please try again.");
|
255
|
+
this.renderModalMessage("\n Sorry, there was an error with your search.\n <br />\n ".concat(errorDetails, "\n "));
|
256
|
+
this.delayModalRemovalFor(4000);
|
257
|
+
}
|
258
|
+
renderBookNotIndexedModal() {
|
259
|
+
this.renderModalMessage("\n <p>\n This book hasn't been indexed for searching yet.\n We've just started indexing it, so search should be available soon.\n <br />\n Please try again later. Thanks!\n </p>\n ");
|
260
|
+
this.delayModalRemovalFor(5000);
|
261
|
+
}
|
262
|
+
renderResultsEmptyModal() {
|
263
|
+
this.renderModalMessage('No matches were found.');
|
264
|
+
this.delayModalRemovalFor(2000);
|
265
|
+
}
|
266
|
+
|
267
|
+
/**
|
268
|
+
* @param {string} messageHTML The innerHTML string used to popupate the modal contents
|
269
|
+
*/
|
270
|
+
renderModalMessage(messageHTML) {
|
271
|
+
var modal = document.createElement('div');
|
272
|
+
modal.classList.add('BRprogresspopup', 'search_modal');
|
273
|
+
modal.innerHTML = messageHTML;
|
274
|
+
document.querySelector(this.br.el).append(modal);
|
275
|
+
}
|
276
|
+
|
277
|
+
/**
|
278
|
+
* @param {number} timeoutMS
|
279
|
+
*/
|
280
|
+
delayModalRemovalFor(timeoutMS) {
|
281
|
+
setTimeout(this.br.removeProgressPopup.bind(this.br), timeoutMS);
|
282
|
+
}
|
283
|
+
|
284
|
+
/**
|
285
|
+
* @param {Event} e
|
286
|
+
*/
|
287
|
+
submitHandler(e) {
|
288
|
+
e.preventDefault();
|
289
|
+
var query = e.target.querySelector('[name="query"]').value;
|
290
|
+
if (!query.length) {
|
291
|
+
return false;
|
292
|
+
}
|
293
|
+
this.br.search(query);
|
294
|
+
this.emptyMatches();
|
295
|
+
this.toggleSearchPending(true);
|
296
|
+
return false;
|
297
|
+
}
|
298
|
+
|
299
|
+
/**
|
300
|
+
* @param {Event} e
|
301
|
+
* @param {object} properties
|
302
|
+
* @param {object} properties.results
|
303
|
+
* @param {object} properties.options
|
304
|
+
*/
|
305
|
+
handleSearchCallback(e, _ref2) {
|
306
|
+
var {
|
307
|
+
results,
|
308
|
+
options
|
309
|
+
} = _ref2;
|
310
|
+
this.matches = results.matches;
|
311
|
+
this.setCurrentMatchIndex();
|
312
|
+
this.teardownSearchNavigation();
|
313
|
+
this.renderSearchNavigation();
|
314
|
+
this.bindSearchNavigationEvents();
|
315
|
+
this.renderPins(results.matches);
|
316
|
+
this.toggleSearchPending(false);
|
317
|
+
if (options.goToFirstResult) {
|
318
|
+
$(document).one('BookReader:pageChanged', () => {
|
319
|
+
this.br.resize();
|
320
|
+
});
|
321
|
+
} else {
|
322
|
+
this.br.resize();
|
323
|
+
}
|
324
|
+
}
|
325
|
+
handleSearchStarted() {
|
326
|
+
this.emptyMatches();
|
327
|
+
this.br.removeSearchHilites();
|
328
|
+
this.removeResultPins();
|
329
|
+
this.toggleSearchPending(true);
|
330
|
+
this.teardownSearchNavigation();
|
331
|
+
this.setQuery(this.br.searchTerm);
|
332
|
+
}
|
333
|
+
|
334
|
+
/**
|
335
|
+
* Event listener for: `BookReader:SearchCallbackError`
|
336
|
+
* @param {CustomEvent} event
|
337
|
+
*/
|
338
|
+
handleSearchCallbackError() {
|
339
|
+
var _event$detail;
|
340
|
+
var event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
341
|
+
this.toggleSearchPending(false);
|
342
|
+
var isIndexed = event === null || event === void 0 || (_event$detail = event.detail) === null || _event$detail === void 0 || (_event$detail = _event$detail.props) === null || _event$detail === void 0 || (_event$detail = _event$detail.results) === null || _event$detail === void 0 ? void 0 : _event$detail.indexed;
|
343
|
+
this.renderErrorModal(isIndexed);
|
344
|
+
}
|
345
|
+
handleSearchCallbackBookNotIndexed() {
|
346
|
+
this.toggleSearchPending(false);
|
347
|
+
this.renderBookNotIndexedModal();
|
348
|
+
}
|
349
|
+
handleSearchCallbackEmpty() {
|
350
|
+
this.toggleSearchPending(false);
|
351
|
+
this.renderResultsEmptyModal();
|
352
|
+
}
|
353
|
+
bindEvents() {
|
354
|
+
var namespace = 'BookReader:';
|
355
|
+
window.addEventListener("".concat(namespace, "SearchCallbackError"), this.handleSearchCallbackError.bind(this));
|
356
|
+
$(document).on("".concat(namespace, "SearchCallback"), this.handleSearchCallback.bind(this)).on("".concat(namespace, "SearchStarted"), this.handleSearchStarted.bind(this)).on("".concat(namespace, "SearchCallbackBookNotIndexed"), this.handleSearchCallbackBookNotIndexed.bind(this)).on("".concat(namespace, "SearchCallbackEmpty"), this.handleSearchCallbackEmpty.bind(this)).on("".concat(namespace, "pageChanged"), this.updateSearchNavigation.bind(this));
|
357
|
+
this.dom.toolbarSearch.querySelector('form').addEventListener('submit', this.submitHandler.bind(this));
|
358
|
+
}
|
359
|
+
}
|
360
|
+
export default SearchView;
|
@@ -0,0 +1,282 @@
|
|
1
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
2
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
3
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
4
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
5
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
6
|
+
import PageChunkIterator from './PageChunkIterator.js';
|
7
|
+
import { hasLocalStorage } from './utils.js';
|
8
|
+
/** @typedef {import('./utils.js').ISO6391} ISO6391 */
|
9
|
+
/** @typedef {import('./PageChunk.js')} PageChunk */
|
10
|
+
|
11
|
+
/**
|
12
|
+
* @export
|
13
|
+
* @typedef {Object} TTSEngineOptions
|
14
|
+
* @property {String} server
|
15
|
+
* @property {String} bookPath
|
16
|
+
* @property {ISO6391} bookLanguage
|
17
|
+
* @property {Function} onLoadingStart
|
18
|
+
* @property {Function} onLoadingComplete
|
19
|
+
* @property {Function} onDone called when the entire book is done
|
20
|
+
* @property {function(PageChunk): PromiseLike} beforeChunkPlay will delay the playing of the chunk
|
21
|
+
* @property {function(PageChunk): void} afterChunkPlay fires once a chunk has fully finished
|
22
|
+
*/
|
23
|
+
|
24
|
+
/**
|
25
|
+
* @typedef {Object} AbstractTTSSound
|
26
|
+
* @property {PageChunk} chunk
|
27
|
+
* @property {boolean} loaded
|
28
|
+
* @property {number} rate
|
29
|
+
* @property {SpeechSynthesisVoice} voice
|
30
|
+
* @property {(callback: Function) => void} load
|
31
|
+
* @property {() => PromiseLike} play
|
32
|
+
* @property {() => Promise} stop
|
33
|
+
* @property {() => void} pause
|
34
|
+
* @property {() => void} resume
|
35
|
+
* @property {() => void} finish force the sound to 'finish'
|
36
|
+
* @property {number => void} setPlaybackRate
|
37
|
+
* @property {SpeechSynthesisVoice => void} setVoice
|
38
|
+
**/
|
39
|
+
|
40
|
+
/** Handling bookreader's text-to-speech */
|
41
|
+
export default class AbstractTTSEngine {
|
42
|
+
/**
|
43
|
+
* @protected
|
44
|
+
* @param {TTSEngineOptions} options
|
45
|
+
*/
|
46
|
+
constructor(options) {
|
47
|
+
_defineProperty(this, "updateBestVoice", () => {
|
48
|
+
this.voice = AbstractTTSEngine.getBestBookVoice(this.getVoices(), this.opts.bookLanguage);
|
49
|
+
});
|
50
|
+
this.playing = false;
|
51
|
+
this.paused = false;
|
52
|
+
this.opts = options;
|
53
|
+
/** @type {PageChunkIterator} */
|
54
|
+
this._chunkIterator = null;
|
55
|
+
/** @type {AbstractTTSSound} */
|
56
|
+
this.activeSound = null;
|
57
|
+
this.playbackRate = 1;
|
58
|
+
/** Events we can bind to */
|
59
|
+
this.events = $({});
|
60
|
+
/** @type {SpeechSynthesisVoice} */
|
61
|
+
this.voice = null;
|
62
|
+
// Listen for voice changes (fired by subclasses)
|
63
|
+
this.events.on('voiceschanged', this.updateBestVoice);
|
64
|
+
this.events.trigger('voiceschanged');
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* @abstract
|
69
|
+
* @return {boolean}
|
70
|
+
*/
|
71
|
+
static isSupported() {
|
72
|
+
throw new Error("Unimplemented abstract class");
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* @abstract
|
77
|
+
* @return {SpeechSynthesisVoice[]}
|
78
|
+
*/
|
79
|
+
getVoices() {
|
80
|
+
throw new Error("Unimplemented abstract class");
|
81
|
+
}
|
82
|
+
|
83
|
+
/** @abstract */
|
84
|
+
init() {
|
85
|
+
return null;
|
86
|
+
}
|
87
|
+
/**
|
88
|
+
* @param {number} leafIndex
|
89
|
+
* @param {number} numLeafs total number of leafs in the current book
|
90
|
+
*/
|
91
|
+
start(leafIndex, numLeafs) {
|
92
|
+
this.playing = true;
|
93
|
+
this.paused = false;
|
94
|
+
this.opts.onLoadingStart();
|
95
|
+
this._chunkIterator = new PageChunkIterator(numLeafs, leafIndex, {
|
96
|
+
server: this.opts.server,
|
97
|
+
bookPath: this.opts.bookPath,
|
98
|
+
pageBufferSize: 5
|
99
|
+
});
|
100
|
+
this.step();
|
101
|
+
this.events.trigger('start');
|
102
|
+
}
|
103
|
+
stop() {
|
104
|
+
if (this.activeSound) this.activeSound.stop();
|
105
|
+
this.playing = false;
|
106
|
+
this.paused = true;
|
107
|
+
this._chunkIterator = null;
|
108
|
+
this.activeSound = null;
|
109
|
+
this.events.trigger('stop');
|
110
|
+
}
|
111
|
+
|
112
|
+
/** @public */
|
113
|
+
pause() {
|
114
|
+
var fireEvent = !this.paused && this.activeSound;
|
115
|
+
this.paused = true;
|
116
|
+
if (this.activeSound) this.activeSound.pause();
|
117
|
+
if (fireEvent) this.events.trigger('pause');
|
118
|
+
}
|
119
|
+
|
120
|
+
/** @public */
|
121
|
+
resume() {
|
122
|
+
var fireEvent = this.paused && this.activeSound;
|
123
|
+
this.paused = false;
|
124
|
+
if (this.activeSound) this.activeSound.resume();
|
125
|
+
if (fireEvent) this.events.trigger('resume');
|
126
|
+
}
|
127
|
+
togglePlayPause() {
|
128
|
+
if (this.paused) this.resume();else this.pause();
|
129
|
+
}
|
130
|
+
|
131
|
+
/** @public */
|
132
|
+
jumpForward() {
|
133
|
+
if (this.activeSound) this.activeSound.finish();
|
134
|
+
}
|
135
|
+
|
136
|
+
/** @public */
|
137
|
+
jumpBackward() {
|
138
|
+
var _this = this;
|
139
|
+
return _asyncToGenerator(function* () {
|
140
|
+
yield Promise.all([_this.activeSound.stop(), _this._chunkIterator.decrement().then(() => _this._chunkIterator.decrement())]);
|
141
|
+
_this.step();
|
142
|
+
})();
|
143
|
+
}
|
144
|
+
|
145
|
+
/** @param {string} voiceURI */
|
146
|
+
setVoice(voiceURI) {
|
147
|
+
// if the user actively selects a voice, don't re-choose best voice anymore
|
148
|
+
// MS Edge fires voices changed randomly very often
|
149
|
+
this.events.off('voiceschanged', this.updateBestVoice);
|
150
|
+
this.voice = this.getVoices().find(voice => voice.voiceURI === voiceURI);
|
151
|
+
// if the current book has a language set, store the selected voice with the book language as a suffix
|
152
|
+
if (this.opts.bookLanguage && hasLocalStorage()) {
|
153
|
+
localStorage.setItem("BRtts-voice-".concat(this.opts.bookLanguage), this.voice.voiceURI);
|
154
|
+
}
|
155
|
+
if (this.activeSound) this.activeSound.setVoice(this.voice);
|
156
|
+
}
|
157
|
+
|
158
|
+
/** @param {number} newRate */
|
159
|
+
setPlaybackRate(newRate) {
|
160
|
+
this.playbackRate = newRate;
|
161
|
+
if (this.activeSound) this.activeSound.setPlaybackRate(newRate);
|
162
|
+
}
|
163
|
+
|
164
|
+
/** @private */
|
165
|
+
step() {
|
166
|
+
var _this2 = this;
|
167
|
+
return _asyncToGenerator(function* () {
|
168
|
+
var chunk = yield _this2._chunkIterator.next();
|
169
|
+
if (chunk == PageChunkIterator.AT_END) {
|
170
|
+
_this2.stop();
|
171
|
+
_this2.opts.onDone();
|
172
|
+
return;
|
173
|
+
}
|
174
|
+
_this2.opts.onLoadingStart();
|
175
|
+
var sound = _this2.createSound(chunk);
|
176
|
+
sound.chunk = chunk;
|
177
|
+
sound.rate = _this2.playbackRate;
|
178
|
+
sound.voice = _this2.voice;
|
179
|
+
sound.load(() => _this2.opts.onLoadingComplete());
|
180
|
+
_this2.opts.onLoadingComplete();
|
181
|
+
yield _this2.opts.beforeChunkPlay(chunk);
|
182
|
+
if (!_this2.playing) return;
|
183
|
+
var playPromise = yield _this2.playSound(sound).then(() => _this2.opts.afterChunkPlay(sound.chunk));
|
184
|
+
if (_this2.paused) _this2.pause();
|
185
|
+
yield playPromise;
|
186
|
+
if (_this2.playing) return _this2.step();
|
187
|
+
})();
|
188
|
+
}
|
189
|
+
|
190
|
+
/**
|
191
|
+
* @abstract
|
192
|
+
* @param {PageChunk} chunk
|
193
|
+
* @return {AbstractTTSSound}
|
194
|
+
*/
|
195
|
+
createSound(chunk) {
|
196
|
+
throw new Error("Unimplemented abstract class");
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* @param {AbstractTTSSound} sound
|
201
|
+
* @return {PromiseLike} promise called once playing finished
|
202
|
+
*/
|
203
|
+
playSound(sound) {
|
204
|
+
this.activeSound = sound;
|
205
|
+
if (!this.activeSound.loaded) this.opts.onLoadingStart();
|
206
|
+
return this.activeSound.play();
|
207
|
+
}
|
208
|
+
|
209
|
+
/** Convenience wrapper for {@see AbstractTTSEngine.getBestVoice} */
|
210
|
+
getBestVoice() {
|
211
|
+
return AbstractTTSEngine.getBestBookVoice(this.getVoices(), this.opts.bookLanguage);
|
212
|
+
}
|
213
|
+
|
214
|
+
/**
|
215
|
+
* @private
|
216
|
+
* Find the best voice to use given the available voices, the book language, and the user's
|
217
|
+
* languages.
|
218
|
+
* @param {SpeechSynthesisVoice[]} voices
|
219
|
+
* @param {ISO6391} bookLanguage
|
220
|
+
* @param {string[]} userLanguages languages in BCP47 format (e.g. en-US). Ordered by preference.
|
221
|
+
* @return {SpeechSynthesisVoice | undefined}
|
222
|
+
*/
|
223
|
+
static getBestBookVoice(voices, bookLanguage) {
|
224
|
+
var userLanguages = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : navigator.languages;
|
225
|
+
var bookLangVoices = voices.filter(v => v.lang.startsWith(bookLanguage));
|
226
|
+
// navigator.languages browser support isn't great yet, so get just 1 language otherwise
|
227
|
+
// Sample navigator.languages: ["en-CA", "fr-CA", "fr", "en-US", "en", "de-DE", "de"]
|
228
|
+
userLanguages = userLanguages || (navigator.language ? [navigator.language] : []);
|
229
|
+
|
230
|
+
// user languages that match the book language
|
231
|
+
var matchingUserLangs = userLanguages.filter(lang => lang.startsWith(bookLanguage));
|
232
|
+
|
233
|
+
// First try to find the last chosen voice from localStorage for the current book language
|
234
|
+
return AbstractTTSEngine.getMatchingStoredVoice(bookLangVoices, bookLanguage)
|
235
|
+
// Try to find voices that intersect these two sets
|
236
|
+
|| AbstractTTSEngine.getMatchingVoice(matchingUserLangs, bookLangVoices)
|
237
|
+
// no user languages match the books; let's return the best voice for the book language
|
238
|
+
|| bookLangVoices.find(v => v.default) || bookLangVoices[0]
|
239
|
+
// No voices match the book language? let's find a voice in the user's language
|
240
|
+
// and ignore book lang
|
241
|
+
|| AbstractTTSEngine.getMatchingVoice(userLanguages, voices)
|
242
|
+
// C'mon! Ok, just read with whatever we got!
|
243
|
+
|| voices.find(v => v.default) || voices[0];
|
244
|
+
}
|
245
|
+
|
246
|
+
/**
|
247
|
+
* @private
|
248
|
+
* Get the voice last selected by the user for the book language from localStorage.
|
249
|
+
* Returns undefined if no voice is stored or found.
|
250
|
+
* @param {SpeechSynthesisVoice[]} voices browser voices to choose from
|
251
|
+
* @param {ISO6391} bookLanguage book language to look for
|
252
|
+
* @return {SpeechSynthesisVoice | undefined}
|
253
|
+
*/
|
254
|
+
static getMatchingStoredVoice(voices, bookLanguage) {
|
255
|
+
var storedVoice = hasLocalStorage() && localStorage.getItem("BRtts-voice-".concat(bookLanguage));
|
256
|
+
return storedVoice ? voices.find(v => v.voiceURI === storedVoice) : undefined;
|
257
|
+
}
|
258
|
+
|
259
|
+
/**
|
260
|
+
* @private
|
261
|
+
* Get the best voice that matches one of the BCP47 languages (order by preference)
|
262
|
+
* @param {string[]} languages in BCP 47 format (e.g. 'en-US', or 'en'); ordered by preference
|
263
|
+
* @param {SpeechSynthesisVoice[]} voices voices to choose from
|
264
|
+
* @return {SpeechSynthesisVoice | undefined}
|
265
|
+
*/
|
266
|
+
static getMatchingVoice(languages, voices) {
|
267
|
+
var _loop = function _loop(lang) {
|
268
|
+
// Chrome Android was returning voice languages like `en_US` instead of `en-US`
|
269
|
+
var matchingVoices = voices.filter(v => v.lang.replace('_', '-').startsWith(lang));
|
270
|
+
if (matchingVoices.length) {
|
271
|
+
return {
|
272
|
+
v: matchingVoices.find(v => v.default) || matchingVoices[0]
|
273
|
+
};
|
274
|
+
}
|
275
|
+
},
|
276
|
+
_ret;
|
277
|
+
for (var lang of languages) {
|
278
|
+
_ret = _loop(lang);
|
279
|
+
if (_ret) return _ret.v;
|
280
|
+
}
|
281
|
+
}
|
282
|
+
}
|