@internetarchive/bookreader 5.0.0-8-multiple-files → 5.0.0-80
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/.eslintrc.js +17 -15
- package/.github/workflows/node.js.yml +78 -6
- package/.github/workflows/npm-publish.yml +6 -20
- package/.testcaferc.js +10 -0
- package/BookReader/BookReader.css +442 -1393
- package/BookReader/BookReader.js +2 -21564
- package/BookReader/BookReader.js.LICENSE.txt +20 -20
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +1782 -0
- package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +7 -0
- package/BookReader/ia-bookreader-bundle.js.map +1 -0
- package/BookReader/icons/1up.svg +1 -12
- package/BookReader/icons/2up.svg +1 -15
- package/BookReader/icons/advance.svg +3 -26
- package/BookReader/icons/chevron-right.svg +1 -1
- package/BookReader/icons/close-circle-dark.svg +1 -0
- package/BookReader/icons/close-circle.svg +1 -1
- package/BookReader/icons/fullscreen.svg +1 -17
- package/BookReader/icons/fullscreen_exit.svg +1 -17
- package/BookReader/icons/hamburger.svg +1 -15
- package/BookReader/icons/left-arrow.svg +1 -12
- package/BookReader/icons/magnify-minus.svg +1 -16
- package/BookReader/icons/magnify-plus.svg +1 -17
- package/BookReader/icons/magnify.svg +1 -15
- package/BookReader/icons/pause.svg +1 -23
- package/BookReader/icons/play.svg +1 -22
- package/BookReader/icons/playback-speed.svg +1 -34
- package/BookReader/icons/read-aloud.svg +1 -22
- package/BookReader/icons/review.svg +3 -22
- package/BookReader/icons/thumbnails.svg +1 -17
- package/BookReader/icons/voice.svg +1 -0
- package/BookReader/icons/volume-full.svg +1 -22
- package/BookReader/images/BRicons.svg +5 -94
- package/BookReader/images/books_graphic.svg +1 -177
- package/BookReader/images/icon_book.svg +1 -12
- package/BookReader/images/icon_bookmark.svg +1 -12
- package/BookReader/images/icon_gear.svg +1 -14
- package/BookReader/images/icon_hamburger.svg +1 -20
- package/BookReader/images/icon_home.svg +1 -21
- package/BookReader/images/icon_info.svg +1 -11
- package/BookReader/images/icon_one_page.svg +1 -8
- package/BookReader/images/icon_pause.svg +1 -1
- package/BookReader/images/icon_play.svg +1 -1
- package/BookReader/images/icon_playback-rate.svg +1 -15
- package/BookReader/images/icon_search_button.svg +1 -8
- package/BookReader/images/icon_share.svg +1 -9
- package/BookReader/images/icon_skip-ahead.svg +1 -6
- package/BookReader/images/icon_skip-back.svg +2 -13
- package/BookReader/images/icon_speaker.svg +1 -18
- package/BookReader/images/icon_speaker_open.svg +1 -10
- package/BookReader/images/icon_thumbnails.svg +1 -12
- package/BookReader/images/icon_toc.svg +1 -5
- package/BookReader/images/icon_two_pages.svg +1 -9
- package/BookReader/images/marker_chap-off.svg +1 -11
- package/BookReader/images/marker_chap-on.svg +1 -11
- package/BookReader/images/marker_srch-on.svg +1 -11
- package/BookReader/jquery-3.js +2 -0
- package/BookReader/jquery-3.js.LICENSE.txt +24 -0
- package/BookReader/plugins/plugin.archive_analytics.js +1 -172
- package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
- package/BookReader/plugins/plugin.autoplay.js +1 -165
- package/BookReader/plugins/plugin.autoplay.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +22 -301
- package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.chapters.js.map +1 -1
- package/BookReader/plugins/plugin.iframe.js +1 -74
- package/BookReader/plugins/plugin.iframe.js.map +1 -1
- package/BookReader/plugins/plugin.iiif.js +2 -0
- package/BookReader/plugins/plugin.iiif.js.map +1 -0
- package/BookReader/plugins/plugin.resume.js +1 -368
- package/BookReader/plugins/plugin.resume.js.map +1 -1
- package/BookReader/plugins/plugin.search.js +2 -1420
- package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +2 -1080
- package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +2 -9193
- package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/plugins/plugin.url.js +1 -269
- package/BookReader/plugins/plugin.url.js.map +1 -1
- package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -379
- package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
- package/BookReader/webcomponents-bundle.js +3 -0
- package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
- package/BookReader/webcomponents-bundle.js.map +1 -0
- package/BookReaderDemo/BookReaderDemo.css +18 -19
- package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
- package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
- package/BookReaderDemo/BookReaderJSSimple.js +1 -0
- package/BookReaderDemo/IADemoBr.js +147 -0
- package/BookReaderDemo/demo-advanced.html +2 -2
- package/BookReaderDemo/demo-autoplay.html +2 -3
- package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
- package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
- package/BookReaderDemo/demo-fullscreen.html +2 -4
- package/BookReaderDemo/demo-iiif.html +99 -12
- package/BookReaderDemo/demo-internetarchive.html +214 -18
- package/BookReaderDemo/demo-multiple.html +2 -1
- package/BookReaderDemo/demo-preview-pages.html +2 -1
- package/BookReaderDemo/demo-simple.html +2 -1
- package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
- package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
- package/BookReaderDemo/immersion-1up.html +2 -2
- package/BookReaderDemo/immersion-mode.html +2 -4
- package/BookReaderDemo/toggle_controls.html +3 -2
- package/BookReaderDemo/view_mode.html +2 -1
- package/BookReaderDemo/viewmode-cycle.html +2 -3
- package/CHANGELOG.md +545 -33
- package/README.md +14 -1
- package/babel.config.js +20 -0
- package/codecov.yml +6 -0
- package/index.html +4 -1
- package/jsconfig.json +19 -0
- package/netlify.toml +9 -0
- package/package.json +70 -60
- package/renovate.json +52 -0
- package/scripts/preversion.js +0 -1
- package/src/BookNavigator/assets/bookmark-colors.js +1 -1
- package/src/BookNavigator/assets/button-base.js +9 -2
- package/src/BookNavigator/assets/ia-logo.js +17 -0
- package/src/BookNavigator/assets/icon_checkmark.js +1 -1
- package/src/BookNavigator/assets/icon_close.js +1 -1
- package/src/BookNavigator/book-navigator.js +590 -0
- package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
- package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
- package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
- package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
- package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
- package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
- package/src/BookNavigator/delete-modal-actions.js +1 -1
- package/src/BookNavigator/downloads/downloads-provider.js +36 -21
- package/src/BookNavigator/downloads/downloads.js +41 -25
- package/src/BookNavigator/search/search-provider.js +80 -28
- package/src/BookNavigator/search/search-results.js +28 -25
- package/src/BookNavigator/sharing.js +27 -0
- package/src/BookNavigator/viewable-files.js +95 -0
- package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
- package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
- package/src/BookReader/BookModel.js +64 -34
- package/src/BookReader/DragScrollable.js +233 -0
- package/src/BookReader/Mode1Up.js +56 -351
- package/src/BookReader/Mode1UpLit.js +388 -0
- package/src/BookReader/Mode2Up.js +73 -1318
- package/src/BookReader/Mode2UpLit.js +776 -0
- package/src/BookReader/ModeCoordinateSpace.js +29 -0
- package/src/BookReader/ModeSmoothZoom.js +312 -0
- package/src/BookReader/ModeThumb.js +18 -12
- package/src/BookReader/Navbar/Navbar.js +14 -40
- package/src/BookReader/PageContainer.js +81 -6
- package/src/BookReader/ReduceSet.js +1 -1
- package/src/BookReader/Toolbar/Toolbar.js +10 -37
- package/src/BookReader/events.js +2 -3
- package/src/BookReader/options.js +27 -2
- package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
- package/src/BookReader/utils/ScrollClassAdder.js +31 -0
- package/src/BookReader/utils/SelectionObserver.js +45 -0
- package/src/BookReader/utils.js +118 -13
- package/src/BookReader.js +446 -1062
- package/src/assets/icons/close-circle-dark.svg +1 -0
- package/src/assets/icons/magnify-minus.svg +3 -7
- package/src/assets/icons/magnify-plus.svg +3 -7
- package/src/assets/icons/voice.svg +1 -0
- package/src/css/BookReader.scss +1 -17
- package/src/css/_BRBookmarks.scss +1 -1
- package/src/css/_BRComponent.scss +1 -1
- package/src/css/_BRmain.scss +33 -27
- package/src/css/_BRnav.scss +12 -39
- package/src/css/_BRpages.scss +149 -40
- package/src/css/_BRsearch.scss +68 -230
- package/src/css/_BRtoolbar.scss +5 -5
- package/src/css/_TextSelection.scss +87 -27
- package/src/css/_colorbox.scss +2 -2
- package/src/css/_controls.scss +20 -7
- package/src/css/_icons.scss +7 -1
- package/src/ia-bookreader/ia-bookreader.js +224 -0
- package/src/plugins/plugin.archive_analytics.js +3 -3
- package/src/plugins/plugin.autoplay.js +5 -11
- package/src/plugins/plugin.chapters.js +237 -191
- package/src/plugins/plugin.iiif.js +151 -0
- package/src/plugins/plugin.resume.js +3 -3
- package/src/plugins/plugin.text_selection.js +464 -134
- package/src/plugins/plugin.vendor-fullscreen.js +4 -4
- package/src/plugins/search/plugin.search.js +175 -120
- package/src/plugins/search/utils.js +43 -0
- package/src/plugins/search/view.js +64 -202
- package/src/plugins/tts/AbstractTTSEngine.js +71 -40
- package/src/plugins/tts/FestivalTTSEngine.js +13 -14
- package/src/plugins/tts/PageChunk.js +15 -21
- package/src/plugins/tts/PageChunkIterator.js +8 -12
- package/src/plugins/tts/WebTTSEngine.js +87 -71
- package/src/plugins/tts/plugin.tts.js +96 -127
- package/src/plugins/tts/utils.js +15 -25
- package/src/plugins/url/UrlPlugin.js +191 -0
- package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
- package/src/util/browserSniffing.js +22 -0
- package/src/util/docCookies.js +21 -2
- package/tests/e2e/README.md +37 -0
- package/tests/e2e/autoplay.test.js +2 -2
- package/tests/e2e/base.test.js +8 -16
- package/tests/e2e/helpers/base.js +53 -48
- package/tests/e2e/helpers/debug.js +1 -1
- package/tests/e2e/helpers/params.js +17 -0
- package/tests/e2e/helpers/rightToLeft.js +8 -14
- package/tests/e2e/helpers/search.js +73 -0
- package/tests/e2e/models/Navigation.js +20 -37
- package/tests/e2e/rightToLeft.test.js +4 -5
- package/tests/e2e/viewmode.test.js +40 -33
- package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
- package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
- package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
- package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
- package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
- package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
- package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
- package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
- package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +109 -60
- package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
- package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
- package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
- package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
- package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
- package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
- package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
- package/tests/jest/BookReader/Mode2Up.test.js +98 -0
- package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
- package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
- package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
- package/tests/jest/BookReader/ModeThumb.test.js +71 -0
- package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
- package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
- package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
- package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
- package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
- package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
- package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
- package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
- package/tests/jest/BookReader/utils.test.js +229 -0
- package/tests/jest/BookReader.keyboard.test.js +190 -0
- package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
- package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
- package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
- package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
- package/tests/jest/plugins/plugin.chapters.test.js +195 -0
- package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
- package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
- package/tests/jest/plugins/plugin.text_selection.test.js +317 -0
- package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
- package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +26 -47
- package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +39 -6
- package/tests/jest/plugins/search/utils.js +25 -0
- package/tests/jest/plugins/search/utils.test.js +29 -0
- package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +29 -9
- package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
- package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
- package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
- package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
- package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
- package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
- package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
- package/tests/jest/setup.js +3 -0
- package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
- package/tests/jest/util/docCookies.test.js +24 -0
- package/tests/{util → jest/util}/strings.test.js +1 -1
- package/tests/{utils.js → jest/utils.js} +38 -0
- package/webpack.config.js +12 -6
- package/.babelrc +0 -12
- package/.dependabot/config.yml +0 -6
- package/.testcaferc.json +0 -5
- package/BookReader/bookreader-component-bundle.js +0 -14312
- package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
- package/BookReader/bookreader-component-bundle.js.map +0 -1
- package/BookReader/icons/sort-ascending.svg +0 -1
- package/BookReader/icons/sort-descending.svg +0 -1
- package/BookReader/jquery-1.10.1.js +0 -108
- package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
- package/BookReader/plugins/plugin.menu_toggle.js +0 -369
- package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
- package/BookReader/plugins/plugin.mobile_nav.js +0 -335
- package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
- package/BookReaderDemo/IIIFBookReader.js +0 -207
- package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
- package/BookReaderDemo/demo-iiif.js +0 -26
- package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
- package/karma.conf.js +0 -23
- package/src/BookNavigator/BookModel.js +0 -14
- package/src/BookNavigator/BookNavigator.js +0 -452
- package/src/BookNavigator/assets/book-loader.js +0 -27
- package/src/BookNavigator/assets/icon_sort_ascending.js +0 -5
- package/src/BookNavigator/assets/icon_sort_descending.js +0 -5
- package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
- package/src/BookNavigator/search/a-search-result.js +0 -55
- package/src/BookNavigator/volumes/volumes-provider.js +0 -76
- package/src/BookNavigator/volumes/volumes.js +0 -161
- package/src/BookReader/DebugConsole.js +0 -54
- package/src/BookReaderComponent/BookReaderComponent.js +0 -112
- package/src/ItemNavigator/ItemNavigator.js +0 -372
- package/src/ItemNavigator/providers/sharing.js +0 -29
- package/src/assets/icons/sort-ascending.svg +0 -1
- package/src/assets/icons/sort-descending.svg +0 -1
- package/src/css/_MobileNav.scss +0 -194
- package/src/dragscrollable-br.js +0 -261
- package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
- package/src/plugins/plugin.mobile_nav.js +0 -287
- package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
- package/tests/BookReader/DebugConsole.test.js +0 -25
- package/tests/BookReader/Mode1Up.test.js +0 -164
- package/tests/BookReader/Mode2Up.test.js +0 -247
- package/tests/BookReader/utils.test.js +0 -109
- package/tests/e2e/helpers/desktopSearch.js +0 -72
- package/tests/e2e/helpers/mobileSearch.js +0 -85
- package/tests/e2e/ia-production/ia-prod-base.js +0 -17
- package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
- package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
- package/tests/karma/BookNavigator/volumes.test.js +0 -101
- package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
- package/tests/plugins/plugin.chapters.test.js +0 -130
- package/tests/plugins/plugin.mobile_nav.test.js +0 -66
- package/tests/plugins/plugin.text_selection.test.js +0 -203
- package/tests/util/docCookies.test.js +0 -15
|
@@ -1,75 +1,26 @@
|
|
|
1
1
|
class SearchView {
|
|
2
2
|
/**
|
|
3
3
|
* @param {object} params
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @param {object} params.br The BookReader instance
|
|
4
|
+
* @param {object} params.br The BookReader instance
|
|
5
|
+
* @param {function} params.cancelSearch callback when a user wants to cancel search
|
|
7
6
|
*
|
|
8
7
|
* @event BookReader:SearchResultsCleared - when the search results nav gets cleared
|
|
9
8
|
* @event BookReader:ToggleSearchMenu - when search results menu should toggle
|
|
10
9
|
*/
|
|
11
|
-
constructor(
|
|
12
|
-
|
|
13
|
-
console.warn('BookReader::Search - SearchView must be passed a valid CSS selector');
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
this.br = params.br;
|
|
18
|
-
|
|
19
|
-
// Search results are returned as a text blob with the hits wrapped in
|
|
20
|
-
// triple mustaches. Hits occasionally include text beyond the search
|
|
21
|
-
// term, so everything within the staches is captured and wrapped.
|
|
22
|
-
this.matcher = new RegExp('{{{(.+?)}}}', 'g');
|
|
10
|
+
constructor({ br, searchCancelledCallback = () => {} }) {
|
|
11
|
+
this.br = br;
|
|
23
12
|
this.matches = [];
|
|
24
|
-
this.cacheDOMElements(
|
|
13
|
+
this.cacheDOMElements();
|
|
25
14
|
this.bindEvents();
|
|
15
|
+
this.cancelSearch = searchCancelledCallback;
|
|
26
16
|
}
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
* @param {string} selector A selector for the element that the search tray will be rendered in
|
|
30
|
-
*/
|
|
31
|
-
cacheDOMElements(selector) {
|
|
18
|
+
cacheDOMElements() {
|
|
32
19
|
this.dom = {};
|
|
33
|
-
|
|
34
|
-
// The parent search tray in mobile menu
|
|
35
|
-
this.dom.searchTray = this.renderSearchTray(selector);
|
|
36
|
-
// Container for rendered search results
|
|
37
|
-
this.dom.results = this.dom.searchTray.querySelector('[data-id="results"]');
|
|
38
|
-
// Element used to display number of results
|
|
39
|
-
this.dom.resultsCount = this.dom.searchTray.querySelector('[data-id="results_count"]');
|
|
40
|
-
// Search input within the mobile search tray
|
|
41
|
-
this.dom.searchField = this.dom.searchTray.querySelector('[name="query"]');
|
|
42
|
-
// Waiting indicator displayed while waiting for a search request
|
|
43
|
-
this.dom.searchPending = this.dom.searchTray.querySelector('[data-id="searchPending"]');
|
|
44
|
-
// The element added to the mobile menu that is animated into view when
|
|
45
|
-
// the "search" nav item is clicked
|
|
46
|
-
this.dom.mobileSearch = this.buildMobileDrawer();
|
|
47
20
|
// Search input within the top toolbar. Will be removed once the mobile menu is replaced.
|
|
48
21
|
this.dom.toolbarSearch = this.buildToolbarSearch();
|
|
49
22
|
}
|
|
50
23
|
|
|
51
|
-
/**
|
|
52
|
-
* @param {boolean} bool
|
|
53
|
-
*/
|
|
54
|
-
toggleSearchTray(bool = this.dom.searchTray.classList.contains('hidden')) {
|
|
55
|
-
this.dom.searchTray.classList.toggle('hidden', !bool);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* @param {boolean} bool
|
|
60
|
-
*/
|
|
61
|
-
toggleResultsCount(bool) {
|
|
62
|
-
this.dom.resultsCount.classList.toggle('visible', bool);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @param {SearchInsideResults} results
|
|
67
|
-
*/
|
|
68
|
-
updateResultsCount(results) {
|
|
69
|
-
this.dom.resultsCount.innerText = `(${results} result${results != 1 ? 's' : ''})`;
|
|
70
|
-
this.toggleResultsCount(true);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
24
|
/**
|
|
74
25
|
* @param {string} query
|
|
75
26
|
*/
|
|
@@ -78,7 +29,6 @@ class SearchView {
|
|
|
78
29
|
}
|
|
79
30
|
|
|
80
31
|
emptyMatches() {
|
|
81
|
-
this.dom.results.innerHTML = '';
|
|
82
32
|
this.matches = [];
|
|
83
33
|
}
|
|
84
34
|
|
|
@@ -86,48 +36,20 @@ class SearchView {
|
|
|
86
36
|
this.br.$('.BRnavpos .BRsearch').remove();
|
|
87
37
|
}
|
|
88
38
|
|
|
89
|
-
clearSearchFieldAndResults() {
|
|
39
|
+
clearSearchFieldAndResults(dispatchEventWhenComplete = true) {
|
|
90
40
|
this.br.removeSearchResults();
|
|
91
|
-
this.toggleResultsCount(false);
|
|
92
41
|
this.removeResultPins();
|
|
93
42
|
this.emptyMatches();
|
|
94
43
|
this.setQuery('');
|
|
95
44
|
this.teardownSearchNavigation();
|
|
96
|
-
|
|
45
|
+
if (dispatchEventWhenComplete) {
|
|
46
|
+
this.br.trigger('SearchResultsCleared');
|
|
47
|
+
}
|
|
97
48
|
}
|
|
98
49
|
|
|
99
50
|
toggleSidebar() {
|
|
100
51
|
this.br.trigger('ToggleSearchMenu');
|
|
101
52
|
}
|
|
102
|
-
/**
|
|
103
|
-
* @param {string} selector The ID attribute to be used for the search tray
|
|
104
|
-
*/
|
|
105
|
-
renderSearchTray(selector) {
|
|
106
|
-
const searchTray = document.createElement('div');
|
|
107
|
-
searchTray.setAttribute('id', selector.replace(/^#/, ''));
|
|
108
|
-
searchTray.innerHTML = `
|
|
109
|
-
<header>
|
|
110
|
-
<div>
|
|
111
|
-
<h3>Search inside</h3>
|
|
112
|
-
<p data-id="results_count"></p>
|
|
113
|
-
</div>
|
|
114
|
-
<a href="#" class="close"></a>
|
|
115
|
-
</header>
|
|
116
|
-
<form action="" method="get">
|
|
117
|
-
<fieldset>
|
|
118
|
-
<input name="all_files" id="all_files" type="checkbox" />
|
|
119
|
-
<label class="checkbox" for="all_files">Search all files</label>
|
|
120
|
-
<input type="search" name="query" placeholder="Enter a search term" />
|
|
121
|
-
</fieldset>
|
|
122
|
-
</form>
|
|
123
|
-
<div data-id="searchPending" id="search_pending">
|
|
124
|
-
<p>Your search results will appear below</p>
|
|
125
|
-
<div class="loader tc mt20"></div>
|
|
126
|
-
</div>
|
|
127
|
-
<ul data-id="results"></ul>
|
|
128
|
-
`;
|
|
129
|
-
return searchTray;
|
|
130
|
-
}
|
|
131
53
|
|
|
132
54
|
renderSearchNavigation() {
|
|
133
55
|
const selector = 'BRsearch-navigation';
|
|
@@ -215,17 +137,19 @@ class SearchView {
|
|
|
215
137
|
const start = pool.slice(0, pool.length / 2);
|
|
216
138
|
const end = pool.slice(pool.length / 2);
|
|
217
139
|
return closestTo((comparisonFn(start, end, comparator) ? start : end), comparator);
|
|
218
|
-
}
|
|
140
|
+
};
|
|
219
141
|
|
|
220
142
|
const closestPage = closestTo(matchPages, currentPage);
|
|
221
143
|
return this.matches.indexOf(this.matches.find((m) => m.par[0].page === closestPage));
|
|
222
144
|
}
|
|
223
145
|
|
|
224
146
|
updateResultsPosition() {
|
|
147
|
+
if (!this.dom.searchNavigation) return;
|
|
225
148
|
this.dom.searchNavigation.find('[data-id=resultsCount]').text(this.resultsPosition());
|
|
226
149
|
}
|
|
227
150
|
|
|
228
151
|
updateSearchNavigationButtons() {
|
|
152
|
+
if (!this.dom.searchNavigation) return;
|
|
229
153
|
this.dom.searchNavigation.find('.prev').attr('disabled', !this.currentMatchIndex);
|
|
230
154
|
this.dom.searchNavigation.find('.next').attr('disabled', this.currentMatchIndex + 1 === this.matches.length);
|
|
231
155
|
}
|
|
@@ -272,19 +196,6 @@ class SearchView {
|
|
|
272
196
|
this.updateSearchNavigationButtons();
|
|
273
197
|
}
|
|
274
198
|
|
|
275
|
-
/**
|
|
276
|
-
* @param {array} matches
|
|
277
|
-
*/
|
|
278
|
-
renderMatches(matches) {
|
|
279
|
-
const items = matches.map((match) => `
|
|
280
|
-
<li data-page="${match.par[0].page}" data-page-index="${this.br.leafNumToIndex(match.par[0].page)}">
|
|
281
|
-
<h4>Page ${match.par[0].page}</h4>
|
|
282
|
-
<p>${match.text.replace(this.matcher, '<mark>$1</mark>')}</p>
|
|
283
|
-
</li>
|
|
284
|
-
`);
|
|
285
|
-
this.dom.results.innerHTML = items.join('');
|
|
286
|
-
}
|
|
287
|
-
|
|
288
199
|
/**
|
|
289
200
|
* @param {boolean} bool
|
|
290
201
|
*/
|
|
@@ -293,23 +204,6 @@ class SearchView {
|
|
|
293
204
|
this.br.refs.$BRfooter.find('.BRsearch').css({ visibility: pinsVisibleState });
|
|
294
205
|
}
|
|
295
206
|
|
|
296
|
-
buildMobileDrawer() {
|
|
297
|
-
const mobileSearch = document.createElement('li');
|
|
298
|
-
mobileSearch.innerHTML = `
|
|
299
|
-
<span>
|
|
300
|
-
<span class="DrawerIconWrapper">
|
|
301
|
-
<img class="DrawerIcon" src="${this.br.imagesBaseURL}icon_search_button.svg" />
|
|
302
|
-
</span>
|
|
303
|
-
Search
|
|
304
|
-
</span>
|
|
305
|
-
<div data-id="search_slot">
|
|
306
|
-
</div>
|
|
307
|
-
`;
|
|
308
|
-
mobileSearch.querySelector('[data-id="search_slot"]').appendChild(this.dom.searchTray);
|
|
309
|
-
mobileSearch.classList.add('BRmobileMenu__search');
|
|
310
|
-
return mobileSearch;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
207
|
buildToolbarSearch() {
|
|
314
208
|
const toolbarSearch = document.createElement('span');
|
|
315
209
|
toolbarSearch.classList.add('BRtoolbarSection', 'BRtoolbarSectionSearch');
|
|
@@ -329,25 +223,20 @@ class SearchView {
|
|
|
329
223
|
*/
|
|
330
224
|
renderPins(matches) {
|
|
331
225
|
matches.forEach((match) => {
|
|
332
|
-
const
|
|
333
|
-
const pageIndex = this.br.leafNumToIndex(match.par[0].page);
|
|
334
|
-
const pageNumber = this.br.getPageNum(pageIndex);
|
|
226
|
+
const pageIndex = this.br.book.leafNumToIndex(match.par[0].page);
|
|
335
227
|
const uiStringSearch = "Search result"; // i18n
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
.replace(this.matcher, '<b>$1</b>')
|
|
348
|
-
+ '...';
|
|
228
|
+
const percentThrough = this.br.constructor.util.cssPercentage(pageIndex, this.br.book.getNumLeafs() - 1);
|
|
229
|
+
|
|
230
|
+
let html = match.html;
|
|
231
|
+
if (html.length > 200) {
|
|
232
|
+
const start = Math.max(0, html.indexOf('<mark>') - 100);
|
|
233
|
+
if (start != 0) {
|
|
234
|
+
html = '…' + match.html
|
|
235
|
+
.substring(start)
|
|
236
|
+
// Make sure at word boundary though
|
|
237
|
+
.replace(/^\S+/, '');
|
|
238
|
+
}
|
|
349
239
|
}
|
|
350
|
-
|
|
351
240
|
// draw marker
|
|
352
241
|
$('<div>')
|
|
353
242
|
.addClass('BRsearch')
|
|
@@ -357,35 +246,27 @@ class SearchView {
|
|
|
357
246
|
.attr('title', uiStringSearch)
|
|
358
247
|
.append(`
|
|
359
248
|
<div class="BRquery">
|
|
360
|
-
<
|
|
361
|
-
<
|
|
249
|
+
<main>${html}</main>
|
|
250
|
+
<footer>Page ${match.displayPageNumber}</footer>
|
|
362
251
|
</div>
|
|
363
252
|
`)
|
|
364
|
-
.data({ pageIndex })
|
|
365
253
|
.appendTo(this.br.$('.BRnavline'))
|
|
366
|
-
.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
.click(function (event) {
|
|
383
|
-
// closures are nested and deep, using an arrow function breaks references.
|
|
384
|
-
// Todo: update to arrow function & clean up closures
|
|
385
|
-
// to remove `bind` dependency
|
|
386
|
-
this.br._searchPluginGoToResult(+$(event.target).data('pageIndex'));
|
|
387
|
-
this.br.updateSearchHilites();
|
|
388
|
-
}.bind(this));
|
|
254
|
+
.on("mouseenter", (event) => {
|
|
255
|
+
// remove from other markers then turn on just for this
|
|
256
|
+
// XXX should be done when nav slider moves
|
|
257
|
+
const marker = event.currentTarget;
|
|
258
|
+
const tooltip = marker.querySelector('.BRquery');
|
|
259
|
+
const tooltipOffset = tooltip.getBoundingClientRect();
|
|
260
|
+
const targetOffset = marker.getBoundingClientRect();
|
|
261
|
+
const boxSizeAdjust = parseInt(getComputedStyle(tooltip).paddingLeft) * 2;
|
|
262
|
+
if (tooltipOffset.x - boxSizeAdjust < 0) {
|
|
263
|
+
tooltip.style.setProperty('transform', `translateX(-${targetOffset.left - boxSizeAdjust}px)`);
|
|
264
|
+
}
|
|
265
|
+
$('.BRsearch,.BRchapter').removeClass('front');
|
|
266
|
+
$(event.target).addClass('front');
|
|
267
|
+
})
|
|
268
|
+
.on("mouseleave", (event) => $(event.target).removeClass('front'))
|
|
269
|
+
.on("click", () => { this.br._searchPluginGoToResult(match.matchIndex); });
|
|
389
270
|
});
|
|
390
271
|
}
|
|
391
272
|
|
|
@@ -393,20 +274,28 @@ class SearchView {
|
|
|
393
274
|
* @param {boolean} bool
|
|
394
275
|
*/
|
|
395
276
|
toggleSearchPending(bool) {
|
|
396
|
-
this.dom.searchPending.classList.toggle('visible', bool);
|
|
397
277
|
if (bool) {
|
|
398
|
-
this.br.showProgressPopup("Search results will appear below...");
|
|
278
|
+
this.br.showProgressPopup("Search results will appear below...", () => this.progressPopupClosed());
|
|
399
279
|
}
|
|
400
280
|
else {
|
|
401
281
|
this.br.removeProgressPopup();
|
|
402
282
|
}
|
|
403
283
|
}
|
|
404
284
|
|
|
405
|
-
|
|
285
|
+
/**
|
|
286
|
+
* Primary callback when user cancels search popup
|
|
287
|
+
*/
|
|
288
|
+
progressPopupClosed() {
|
|
289
|
+
this.toggleSearchPending();
|
|
290
|
+
this.cancelSearch();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
renderErrorModal(textIsProcessing = false) {
|
|
294
|
+
const errorDetails = `${!textIsProcessing ? 'The text may still be processing. ' : ''}Please try again.`;
|
|
406
295
|
this.renderModalMessage(`
|
|
407
296
|
Sorry, there was an error with your search.
|
|
408
297
|
<br />
|
|
409
|
-
|
|
298
|
+
${errorDetails}
|
|
410
299
|
`);
|
|
411
300
|
this.delayModalRemovalFor(4000);
|
|
412
301
|
}
|
|
@@ -445,14 +334,6 @@ class SearchView {
|
|
|
445
334
|
setTimeout(this.br.removeProgressPopup.bind(this.br), timeoutMS);
|
|
446
335
|
}
|
|
447
336
|
|
|
448
|
-
openMobileMenu() {
|
|
449
|
-
this.br.refs.$mmenu.data('mmenu').open();
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
closeMobileMenu() {
|
|
453
|
-
this.br.refs.$mmenu.data('mmenu').close();
|
|
454
|
-
}
|
|
455
|
-
|
|
456
337
|
/**
|
|
457
338
|
* @param {Event} e
|
|
458
339
|
*/
|
|
@@ -461,7 +342,6 @@ class SearchView {
|
|
|
461
342
|
const query = e.target.querySelector('[name="query"]').value;
|
|
462
343
|
if (!query.length) { return false; }
|
|
463
344
|
this.br.search(query);
|
|
464
|
-
this.dom.searchField.blur();
|
|
465
345
|
this.emptyMatches();
|
|
466
346
|
this.toggleSearchPending(true);
|
|
467
347
|
return false;
|
|
@@ -479,9 +359,7 @@ class SearchView {
|
|
|
479
359
|
this.teardownSearchNavigation();
|
|
480
360
|
this.renderSearchNavigation();
|
|
481
361
|
this.bindSearchNavigationEvents();
|
|
482
|
-
this.renderMatches(results.matches);
|
|
483
362
|
this.renderPins(results.matches);
|
|
484
|
-
this.updateResultsCount(results.matches.length);
|
|
485
363
|
this.toggleSearchPending(false);
|
|
486
364
|
if (options.goToFirstResult) {
|
|
487
365
|
$(document).one('BookReader:pageChanged', () => {
|
|
@@ -492,15 +370,6 @@ class SearchView {
|
|
|
492
370
|
}
|
|
493
371
|
}
|
|
494
372
|
|
|
495
|
-
/**
|
|
496
|
-
* @param {Event} e
|
|
497
|
-
*/
|
|
498
|
-
handleNavToggledCallback(e) {
|
|
499
|
-
const is_visible = this.br.navigationIsVisible();
|
|
500
|
-
this.togglePinsFor(is_visible);
|
|
501
|
-
this.toggleSearchTray(is_visible ? !!this.dom.results.querySelector('li') : false);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
373
|
handleSearchStarted() {
|
|
505
374
|
this.emptyMatches();
|
|
506
375
|
this.br.removeSearchHilites();
|
|
@@ -510,9 +379,14 @@ class SearchView {
|
|
|
510
379
|
this.setQuery(this.br.searchTerm);
|
|
511
380
|
}
|
|
512
381
|
|
|
513
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Event listener for: `BookReader:SearchCallbackError`
|
|
384
|
+
* @param {CustomEvent} event
|
|
385
|
+
*/
|
|
386
|
+
handleSearchCallbackError(event = {}) {
|
|
514
387
|
this.toggleSearchPending(false);
|
|
515
|
-
|
|
388
|
+
const isIndexed = event?.detail?.props?.results?.indexed;
|
|
389
|
+
this.renderErrorModal(isIndexed);
|
|
516
390
|
}
|
|
517
391
|
|
|
518
392
|
handleSearchCallbackBookNotIndexed() {
|
|
@@ -528,26 +402,14 @@ class SearchView {
|
|
|
528
402
|
bindEvents() {
|
|
529
403
|
const namespace = 'BookReader:';
|
|
530
404
|
|
|
405
|
+
window.addEventListener(`${namespace}SearchCallbackError`, this.handleSearchCallbackError.bind(this));
|
|
531
406
|
$(document).on(`${namespace}SearchCallback`, this.handleSearchCallback.bind(this))
|
|
532
|
-
.on(`${namespace}navToggled`, this.handleNavToggledCallback.bind(this))
|
|
533
407
|
.on(`${namespace}SearchStarted`, this.handleSearchStarted.bind(this))
|
|
534
|
-
.on(`${namespace}SearchCallbackError`, this.handleSearchCallbackError.bind(this))
|
|
535
408
|
.on(`${namespace}SearchCallbackBookNotIndexed`, this.handleSearchCallbackBookNotIndexed.bind(this))
|
|
536
409
|
.on(`${namespace}SearchCallbackEmpty`, this.handleSearchCallbackEmpty.bind(this))
|
|
537
410
|
.on(`${namespace}pageChanged`, this.updateSearchNavigation.bind(this));
|
|
538
411
|
|
|
539
|
-
this.dom.searchTray.addEventListener('submit', this.submitHandler.bind(this));
|
|
540
412
|
this.dom.toolbarSearch.querySelector('form').addEventListener('submit', this.submitHandler.bind(this));
|
|
541
|
-
this.dom.searchField.addEventListener('search', () => {
|
|
542
|
-
if (this.dom.searchField.value) { return; }
|
|
543
|
-
this.clearSearchFieldAndResults();
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
$(this.dom.results).on('click', 'li', (e) => {
|
|
547
|
-
this.br._searchPluginGoToResult(+e.currentTarget.dataset.pageIndex);
|
|
548
|
-
this.br.updateSearchHilites();
|
|
549
|
-
this.closeMobileMenu();
|
|
550
|
-
});
|
|
551
413
|
}
|
|
552
414
|
}
|
|
553
415
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import PageChunkIterator from './PageChunkIterator.js';
|
|
2
|
+
import { hasLocalStorage } from './utils.js';
|
|
2
3
|
/** @typedef {import('./utils.js').ISO6391} ISO6391 */
|
|
3
4
|
/** @typedef {import('./PageChunk.js')} PageChunk */
|
|
4
5
|
|
|
@@ -28,6 +29,7 @@ import PageChunkIterator from './PageChunkIterator.js';
|
|
|
28
29
|
* @property {() => void} resume
|
|
29
30
|
* @property {() => void} finish force the sound to 'finish'
|
|
30
31
|
* @property {number => void} setPlaybackRate
|
|
32
|
+
* @property {SpeechSynthesisVoice => void} setVoice
|
|
31
33
|
**/
|
|
32
34
|
|
|
33
35
|
/** Handling bookreader's text-to-speech */
|
|
@@ -50,9 +52,7 @@ export default class AbstractTTSEngine {
|
|
|
50
52
|
/** @type {SpeechSynthesisVoice} */
|
|
51
53
|
this.voice = null;
|
|
52
54
|
// Listen for voice changes (fired by subclasses)
|
|
53
|
-
this.events.on('voiceschanged',
|
|
54
|
-
this.voice = AbstractTTSEngine.getBestBookVoice(this.getVoices(), this.opts.bookLanguage);
|
|
55
|
-
});
|
|
55
|
+
this.events.on('voiceschanged', this.updateBestVoice);
|
|
56
56
|
this.events.trigger('voiceschanged');
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -71,12 +71,17 @@ export default class AbstractTTSEngine {
|
|
|
71
71
|
/** @abstract */
|
|
72
72
|
init() { return null; }
|
|
73
73
|
|
|
74
|
+
updateBestVoice = () => {
|
|
75
|
+
this.voice = AbstractTTSEngine.getBestBookVoice(this.getVoices(), this.opts.bookLanguage);
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
/**
|
|
75
79
|
* @param {number} leafIndex
|
|
76
80
|
* @param {number} numLeafs total number of leafs in the current book
|
|
77
81
|
*/
|
|
78
82
|
start(leafIndex, numLeafs) {
|
|
79
83
|
this.playing = true;
|
|
84
|
+
this.paused = false;
|
|
80
85
|
this.opts.onLoadingStart();
|
|
81
86
|
|
|
82
87
|
this._chunkIterator = new PageChunkIterator(numLeafs, leafIndex, {
|
|
@@ -92,6 +97,7 @@ export default class AbstractTTSEngine {
|
|
|
92
97
|
stop() {
|
|
93
98
|
if (this.activeSound) this.activeSound.stop();
|
|
94
99
|
this.playing = false;
|
|
100
|
+
this.paused = true;
|
|
95
101
|
this._chunkIterator = null;
|
|
96
102
|
this.activeSound = null;
|
|
97
103
|
this.events.trigger('stop');
|
|
@@ -124,13 +130,26 @@ export default class AbstractTTSEngine {
|
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
/** @public */
|
|
127
|
-
jumpBackward() {
|
|
128
|
-
Promise.all([
|
|
133
|
+
async jumpBackward() {
|
|
134
|
+
await Promise.all([
|
|
129
135
|
this.activeSound.stop(),
|
|
130
136
|
this._chunkIterator.decrement()
|
|
131
137
|
.then(() => this._chunkIterator.decrement())
|
|
132
|
-
])
|
|
133
|
-
|
|
138
|
+
]);
|
|
139
|
+
this.step();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** @param {string} voiceURI */
|
|
143
|
+
setVoice(voiceURI) {
|
|
144
|
+
// if the user actively selects a voice, don't re-choose best voice anymore
|
|
145
|
+
// MS Edge fires voices changed randomly very often
|
|
146
|
+
this.events.off('voiceschanged', this.updateBestVoice);
|
|
147
|
+
this.voice = this.getVoices().find(voice => voice.voiceURI === voiceURI);
|
|
148
|
+
// if the current book has a language set, store the selected voice with the book language as a suffix
|
|
149
|
+
if (this.opts.bookLanguage && hasLocalStorage()) {
|
|
150
|
+
localStorage.setItem(`BRtts-voice-${this.opts.bookLanguage}`, this.voice.voiceURI);
|
|
151
|
+
}
|
|
152
|
+
if (this.activeSound) this.activeSound.setVoice(this.voice);
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
/** @param {number} newRate */
|
|
@@ -140,36 +159,33 @@ export default class AbstractTTSEngine {
|
|
|
140
159
|
}
|
|
141
160
|
|
|
142
161
|
/** @private */
|
|
143
|
-
step() {
|
|
144
|
-
this._chunkIterator.next()
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
.then(() => {
|
|
171
|
-
if (this.playing) return this.step();
|
|
172
|
-
});
|
|
162
|
+
async step() {
|
|
163
|
+
const chunk = await this._chunkIterator.next();
|
|
164
|
+
if (chunk == PageChunkIterator.AT_END) {
|
|
165
|
+
this.stop();
|
|
166
|
+
this.opts.onDone();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.opts.onLoadingStart();
|
|
170
|
+
const sound = this.createSound(chunk);
|
|
171
|
+
sound.chunk = chunk;
|
|
172
|
+
sound.rate = this.playbackRate;
|
|
173
|
+
sound.voice = this.voice;
|
|
174
|
+
sound.load(() => this.opts.onLoadingComplete());
|
|
175
|
+
|
|
176
|
+
this.opts.onLoadingComplete();
|
|
177
|
+
|
|
178
|
+
await this.opts.beforeChunkPlay(chunk);
|
|
179
|
+
|
|
180
|
+
if (!this.playing) return;
|
|
181
|
+
|
|
182
|
+
const playPromise = await this.playSound(sound)
|
|
183
|
+
.then(()=> this.opts.afterChunkPlay(sound.chunk));
|
|
184
|
+
|
|
185
|
+
if (this.paused) this.pause();
|
|
186
|
+
await playPromise;
|
|
187
|
+
|
|
188
|
+
if (this.playing) return this.step();
|
|
173
189
|
}
|
|
174
190
|
|
|
175
191
|
/**
|
|
@@ -212,10 +228,12 @@ export default class AbstractTTSEngine {
|
|
|
212
228
|
// user languages that match the book language
|
|
213
229
|
const matchingUserLangs = userLanguages.filter(lang => lang.startsWith(bookLanguage));
|
|
214
230
|
|
|
215
|
-
//
|
|
216
|
-
return AbstractTTSEngine.
|
|
231
|
+
// First try to find the last chosen voice from localStorage for the current book language
|
|
232
|
+
return AbstractTTSEngine.getMatchingStoredVoice(bookLangVoices, bookLanguage)
|
|
233
|
+
// Try to find voices that intersect these two sets
|
|
234
|
+
|| AbstractTTSEngine.getMatchingVoice(matchingUserLangs, bookLangVoices)
|
|
217
235
|
// no user languages match the books; let's return the best voice for the book language
|
|
218
|
-
(bookLangVoices.find(v => v.default) || bookLangVoices[0])
|
|
236
|
+
|| (bookLangVoices.find(v => v.default) || bookLangVoices[0])
|
|
219
237
|
// No voices match the book language? let's find a voice in the user's language
|
|
220
238
|
// and ignore book lang
|
|
221
239
|
|| AbstractTTSEngine.getMatchingVoice(userLanguages, voices)
|
|
@@ -223,6 +241,19 @@ export default class AbstractTTSEngine {
|
|
|
223
241
|
|| (voices.find(v => v.default) || voices[0]);
|
|
224
242
|
}
|
|
225
243
|
|
|
244
|
+
/**
|
|
245
|
+
* @private
|
|
246
|
+
* Get the voice last selected by the user for the book language from localStorage.
|
|
247
|
+
* Returns undefined if no voice is stored or found.
|
|
248
|
+
* @param {SpeechSynthesisVoice[]} voices browser voices to choose from
|
|
249
|
+
* @param {ISO6391} bookLanguage book language to look for
|
|
250
|
+
* @return {SpeechSynthesisVoice | undefined}
|
|
251
|
+
*/
|
|
252
|
+
static getMatchingStoredVoice(voices, bookLanguage) {
|
|
253
|
+
const storedVoice = hasLocalStorage() && localStorage.getItem(`BRtts-voice-${bookLanguage}`);
|
|
254
|
+
return (storedVoice ? voices.find(v => v.voiceURI === storedVoice) : undefined);
|
|
255
|
+
}
|
|
256
|
+
|
|
226
257
|
/**
|
|
227
258
|
* @private
|
|
228
259
|
* Get the best voice that matches one of the BCP47 languages (order by preference)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import AbstractTTSEngine from './AbstractTTSEngine.js';
|
|
2
|
-
import { sleep } from '
|
|
2
|
+
import { sleep } from '../../BookReader/utils.js';
|
|
3
3
|
/* global soundManager */
|
|
4
4
|
import 'soundmanager2';
|
|
5
5
|
import 'jquery.browser';
|
|
@@ -23,7 +23,7 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
|
|
|
23
23
|
// $.browsers is sometimes undefined on some Android browsers :/
|
|
24
24
|
// Likely related to when $.browser was moved to npm
|
|
25
25
|
/** @type {'mp3' | 'ogg'} format of audio to get */
|
|
26
|
-
this.audioFormat = $.browser?.mozilla ? 'ogg' : 'mp3';
|
|
26
|
+
this.audioFormat = $.browser?.mozilla ? 'ogg' : 'mp3'; //eslint-disable-line no-jquery/no-browser
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/** @override */
|
|
@@ -91,10 +91,10 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
|
|
|
91
91
|
* See https://stackoverflow.com/questions/12206631/html5-audio-cant-play-through-javascript-unless-triggered-manually-once
|
|
92
92
|
* @return {PromiseLike}
|
|
93
93
|
*/
|
|
94
|
-
iOSCaptureUserIntentHack() {
|
|
94
|
+
async iOSCaptureUserIntentHack() {
|
|
95
95
|
const sound = soundManager.createSound({ url: SILENCE_1MS[this.audioFormat] });
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
await new Promise(res => sound.play({onfinish: res}));
|
|
97
|
+
sound.destruct();
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -107,7 +107,7 @@ class FestivalTTSSound {
|
|
|
107
107
|
this.sound = null;
|
|
108
108
|
this.rate = 1;
|
|
109
109
|
/** @type {function} calling this resolves the "play" promise */
|
|
110
|
-
this._finishResolver = null
|
|
110
|
+
this._finishResolver = null;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
get loaded() {
|
|
@@ -122,21 +122,20 @@ class FestivalTTSSound {
|
|
|
122
122
|
if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
|
|
123
123
|
onload();
|
|
124
124
|
},
|
|
125
|
-
onresume: () => {
|
|
126
|
-
sleep(25)
|
|
127
|
-
|
|
128
|
-
});
|
|
125
|
+
onresume: async () => {
|
|
126
|
+
await sleep(25);
|
|
127
|
+
if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
|
|
129
128
|
}
|
|
130
129
|
});
|
|
131
130
|
return this.sound.load();
|
|
132
131
|
}
|
|
133
132
|
|
|
134
|
-
play() {
|
|
135
|
-
|
|
133
|
+
async play() {
|
|
134
|
+
await new Promise(res => {
|
|
136
135
|
this._finishResolver = res;
|
|
137
136
|
this.sound.play({ onfinish: res });
|
|
138
|
-
})
|
|
139
|
-
|
|
137
|
+
});
|
|
138
|
+
this.sound.destruct();
|
|
140
139
|
}
|
|
141
140
|
|
|
142
141
|
/** @override */
|