@internetarchive/bookreader 5.0.0-70-a2 → 5.0.0-71
Sign up to get free protection for your applications and to get access to all the features.
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +11 -8
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/plugins/plugin.url.js +1 -1
- package/BookReader/plugins/plugin.url.js.map +1 -1
- package/BookReaderDemo/demo-internetarchive.html +1 -1
- package/CHANGELOG.md +8 -0
- package/package.json +5 -5
- package/src/BookNavigator/book-navigator.js +2 -2
- package/src/BookNavigator/sharing.js +4 -4
- package/src/BookNavigator/{volumes/volumes-provider.js → viewable-files.js} +7 -7
- package/src/BookReader.js +4 -5
- package/src/ia-bookreader/ia-bookreader.js +1 -1
- package/src/plugins/tts/AbstractTTSEngine.js +22 -3
- package/src/plugins/url/UrlPlugin.js +5 -7
- package/tests/jest/BookNavigator/book-navigator.test.js +2 -2
- package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +1 -1
- package/tests/jest/BookNavigator/{volumes/volumes-provider.test.js → viewable-files/viewable-files-provider.test.js} +2 -2
- package/tests/jest/plugins/tts/AbstractTTSEngine.test.js +20 -0
- package/tests/jest/plugins/url/UrlPlugin.test.js +8 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
import { html } from 'lit';
|
2
2
|
|
3
|
-
import { viewableFilesIcon } from '@internetarchive/ia-item-navigator';
|
4
|
-
import '@internetarchive/ia-item-navigator';
|
3
|
+
import { viewableFilesIcon } from '@internetarchive/ia-item-navigator/dist/src/menus/viewable-files';
|
4
|
+
import '@internetarchive/ia-item-navigator/dist/src/menus/viewable-files';
|
5
5
|
|
6
6
|
/**
|
7
7
|
* * @typedef { 'title_asc' | 'title_desc' | 'default'} SortTypesT
|
@@ -11,12 +11,12 @@ const sortTypes = {
|
|
11
11
|
title_desc: 'title_desc',
|
12
12
|
default: 'default'
|
13
13
|
};
|
14
|
-
export default class
|
14
|
+
export default class ViewableFilesProvider {
|
15
15
|
/**
|
16
|
-
* @param {import('
|
16
|
+
* @param {import('../BookReader').default} bookreader
|
17
17
|
*/
|
18
18
|
constructor({ baseHost, bookreader, onProviderChange }) {
|
19
|
-
/** @type {import('
|
19
|
+
/** @type {import('../BookReader').default} */
|
20
20
|
this.bookreader = bookreader;
|
21
21
|
this.onProviderChange = onProviderChange;
|
22
22
|
this.baseHost = baseHost;
|
@@ -30,13 +30,13 @@ export default class VolumesProvider {
|
|
30
30
|
this.icon = html`${viewableFilesIcon}`;
|
31
31
|
this.sortOrderBy = sortTypes.default;
|
32
32
|
|
33
|
-
this.component = document.createElement("iaux-viewable-files");
|
33
|
+
this.component = document.createElement("iaux-in-viewable-files-panel");
|
34
34
|
this.component.addSortToUrl = true;
|
35
35
|
this.component.subPrefix = bookreader.options.subPrefix || "";
|
36
36
|
this.component.baseHost = baseHost;
|
37
37
|
this.component.fileList = [...this.viewableFiles];
|
38
38
|
|
39
|
-
this.sortFilesComponent = document.createElement("iaux-sort-
|
39
|
+
this.sortFilesComponent = document.createElement("iaux-in-sort-files-button");
|
40
40
|
this.sortFilesComponent.fileListRaw = this.viewableFiles;
|
41
41
|
this.sortFilesComponent.addEventListener('fileListSorted', (e) => this.handleFileListSorted(e));
|
42
42
|
this.actionButton = this.sortFilesComponent;
|
package/src/BookReader.js
CHANGED
@@ -1788,7 +1788,7 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
|
|
1788
1788
|
// Index and page
|
1789
1789
|
if ('undefined' != typeof(urlHash['page'])) {
|
1790
1790
|
// page was set -- may not be int
|
1791
|
-
params.page = urlHash['page'];
|
1791
|
+
params.page = decodeURIComponent(urlHash['page']);
|
1792
1792
|
}
|
1793
1793
|
|
1794
1794
|
// $$$ process /region
|
@@ -1820,11 +1820,10 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
|
|
1820
1820
|
* @return {string}
|
1821
1821
|
*/
|
1822
1822
|
BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
|
1823
|
-
const separator = '/';
|
1824
1823
|
const fragments = [];
|
1825
1824
|
|
1826
1825
|
if ('undefined' != typeof(params.page)) {
|
1827
|
-
fragments.push('page', params.page);
|
1826
|
+
fragments.push('page', encodeURIComponent(params.page));
|
1828
1827
|
} else {
|
1829
1828
|
if ('undefined' != typeof(params.index)) {
|
1830
1829
|
// Don't have page numbering but we do have the index
|
@@ -1850,10 +1849,10 @@ BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
|
|
1850
1849
|
|
1851
1850
|
// search
|
1852
1851
|
if (params.search && urlMode === 'hash') {
|
1853
|
-
fragments.push('search', params.search);
|
1852
|
+
fragments.push('search', utils.encodeURIComponentPlus(params.search));
|
1854
1853
|
}
|
1855
1854
|
|
1856
|
-
return
|
1855
|
+
return fragments.join('/');
|
1857
1856
|
};
|
1858
1857
|
|
1859
1858
|
/**
|
@@ -30,7 +30,7 @@ export class IaBookReader extends LitElement {
|
|
30
30
|
super();
|
31
31
|
this.item = undefined;
|
32
32
|
this.bookreader = undefined;
|
33
|
-
this.baseHost = '
|
33
|
+
this.baseHost = 'archive.org';
|
34
34
|
this.fullscreen = false;
|
35
35
|
this.signedIn = false;
|
36
36
|
/** @type {ModalManager} */
|
@@ -142,6 +142,10 @@ export default class AbstractTTSEngine {
|
|
142
142
|
// MS Edge fires voices changed randomly very often
|
143
143
|
this.events.off('voiceschanged', this.updateBestVoice);
|
144
144
|
this.voice = this.getVoices().find(voice => voice.voiceURI === voiceURI);
|
145
|
+
// if the current book has a language set, store the selected voice with the book language as a suffix
|
146
|
+
if (this.opts.bookLanguage) {
|
147
|
+
localStorage.setItem(`BRtts-voice-${this.opts.bookLanguage}`, this.voice.voiceURI);
|
148
|
+
}
|
145
149
|
if (this.activeSound) this.activeSound.setVoice(this.voice);
|
146
150
|
}
|
147
151
|
|
@@ -221,10 +225,12 @@ export default class AbstractTTSEngine {
|
|
221
225
|
// user languages that match the book language
|
222
226
|
const matchingUserLangs = userLanguages.filter(lang => lang.startsWith(bookLanguage));
|
223
227
|
|
224
|
-
//
|
225
|
-
return AbstractTTSEngine.
|
228
|
+
// First try to find the last chosen voice from localStorage for the current book language
|
229
|
+
return AbstractTTSEngine.getMatchingStoredVoice(bookLangVoices, bookLanguage)
|
230
|
+
// Try to find voices that intersect these two sets
|
231
|
+
|| AbstractTTSEngine.getMatchingVoice(matchingUserLangs, bookLangVoices)
|
226
232
|
// no user languages match the books; let's return the best voice for the book language
|
227
|
-
(bookLangVoices.find(v => v.default) || bookLangVoices[0])
|
233
|
+
|| (bookLangVoices.find(v => v.default) || bookLangVoices[0])
|
228
234
|
// No voices match the book language? let's find a voice in the user's language
|
229
235
|
// and ignore book lang
|
230
236
|
|| AbstractTTSEngine.getMatchingVoice(userLanguages, voices)
|
@@ -232,6 +238,19 @@ export default class AbstractTTSEngine {
|
|
232
238
|
|| (voices.find(v => v.default) || voices[0]);
|
233
239
|
}
|
234
240
|
|
241
|
+
/**
|
242
|
+
* @private
|
243
|
+
* Get the voice last selected by the user for the book language from localStorage.
|
244
|
+
* Returns undefined if no voice is stored or found.
|
245
|
+
* @param {SpeechSynthesisVoice[]} voices browser voices to choose from
|
246
|
+
* @param {ISO6391} bookLanguage book language to look for
|
247
|
+
* @return {SpeechSynthesisVoice | undefined}
|
248
|
+
*/
|
249
|
+
static getMatchingStoredVoice(voices, bookLanguage) {
|
250
|
+
const storedVoice = localStorage.getItem(`BRtts-voice-${bookLanguage}`);
|
251
|
+
return (storedVoice ? voices.find(v => v.voiceURI === storedVoice) : undefined);
|
252
|
+
}
|
253
|
+
|
235
254
|
/**
|
236
255
|
* @private
|
237
256
|
* Get the best voice that matches one of the BCP47 languages (order by preference)
|
@@ -45,7 +45,7 @@ export class UrlPlugin {
|
|
45
45
|
|
46
46
|
const strPathParams = this.urlSchema
|
47
47
|
.filter(s => s.position == 'path')
|
48
|
-
.map(schema => pathParams[schema.name] ? `${schema.name}/${pathParams[schema.name]}` : '')
|
48
|
+
.map(schema => pathParams[schema.name] ? `${schema.name}/${encodeURIComponent(pathParams[schema.name])}` : '')
|
49
49
|
.join('/');
|
50
50
|
|
51
51
|
// replace consecutive slashes with a single slash + remove trailing slashes
|
@@ -83,15 +83,13 @@ export class UrlPlugin {
|
|
83
83
|
const hasPropertyKey = doesKeyExists(urlStrSplitSlashObj, schema.name);
|
84
84
|
const hasDeprecatedKey = doesKeyExists(schema, 'deprecated_for') && hasPropertyKey;
|
85
85
|
|
86
|
-
|
87
|
-
|
86
|
+
// Not in the URL
|
87
|
+
if (!hasPropertyKey && !hasDeprecatedKey) {
|
88
88
|
return;
|
89
89
|
}
|
90
90
|
|
91
|
-
|
92
|
-
|
93
|
-
return;
|
94
|
-
}
|
91
|
+
const urlStateParam = hasDeprecatedKey ? schema.deprecated_for : schema.name;
|
92
|
+
urlState[urlStateParam] = decodeURIComponent(urlStrSplitSlashObj[schema.name]);
|
95
93
|
});
|
96
94
|
|
97
95
|
// Add searchParams to urlState
|
@@ -9,7 +9,7 @@ import DownloadsProvider from '@/src/BookNavigator/downloads/downloads-provider.
|
|
9
9
|
import SearchProvider from '@/src/BookNavigator/search/search-provider.js';
|
10
10
|
import SharingProvider from '@/src/BookNavigator/sharing.js';
|
11
11
|
import VisualAdjustmentsProvider from '@/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js';
|
12
|
-
import
|
12
|
+
import ViewableFilesProvider from '@/src/BookNavigator/viewable-files.js';
|
13
13
|
import { ModalManager } from '@internetarchive/modal-manager';
|
14
14
|
import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
|
15
15
|
import '@/src/BookNavigator/book-navigator.js';
|
@@ -214,7 +214,7 @@ describe('<book-navigator>', () => {
|
|
214
214
|
await el.elementUpdated;
|
215
215
|
|
216
216
|
expect(el.menuProviders.volumes).toBeDefined();
|
217
|
-
expect(el.menuProviders.volumes).toBeInstanceOf(
|
217
|
+
expect(el.menuProviders.volumes).toBeInstanceOf(ViewableFilesProvider);
|
218
218
|
|
219
219
|
// also adds a menu shortcut
|
220
220
|
expect(el.menuShortcuts.find(m => m.id === 'volumes')).toBeDefined();
|
@@ -30,7 +30,7 @@ describe('Sharing Provider', () => {
|
|
30
30
|
expect(provider.id).toEqual('share');
|
31
31
|
expect(provider.icon).toBeDefined();
|
32
32
|
expect(provider.label).toEqual('Share this book');
|
33
|
-
expect(fixtureSync(provider.component).tagName).toContain('IAUX-
|
33
|
+
expect(fixtureSync(provider.component).tagName).toContain('IAUX-IN-SHARE-PANEL');
|
34
34
|
});
|
35
35
|
|
36
36
|
describe('Handles being a sub file/volume', () => {
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { fixtureCleanup, fixtureSync } from '@open-wc/testing-helpers';
|
2
2
|
import sinon from 'sinon';
|
3
|
-
import
|
3
|
+
import ViewableFilesProvider from '@/src/BookNavigator/viewable-files';
|
4
4
|
|
5
5
|
const brOptions = {
|
6
6
|
"options": {
|
@@ -44,7 +44,7 @@ describe('Volumes Provider', () => {
|
|
44
44
|
const onProviderChange = sinon.fake();
|
45
45
|
|
46
46
|
const baseHost = "https://archive.org";
|
47
|
-
const provider = new
|
47
|
+
const provider = new ViewableFilesProvider({
|
48
48
|
baseHost,
|
49
49
|
bookreader: brOptions,
|
50
50
|
onProviderChange
|
@@ -111,6 +111,26 @@ for (const dummyVoice of [dummyVoiceHyphens, dummyVoiceUnderscores]) {
|
|
111
111
|
|
112
112
|
expect(getBestBookVoice(voices, 'en', ['en-CA', 'en'])).toBe(voices[0]);
|
113
113
|
});
|
114
|
+
|
115
|
+
test('choose stored language from localStorage', () => {
|
116
|
+
const voices = [
|
117
|
+
dummyVoice({lang: "en-US", voiceURI: "English US", default: true}),
|
118
|
+
dummyVoice({lang: "en-GB", voiceURI: "English GB"}),
|
119
|
+
dummyVoice({lang: "en-CA", voiceURI: "English CA"}),
|
120
|
+
];
|
121
|
+
class DummyEngine extends AbstractTTSEngine {
|
122
|
+
getVoices() { return voices; }
|
123
|
+
}
|
124
|
+
const ttsEngine = new DummyEngine({...DUMMY_TTS_ENGINE_OPTS, bookLanguage: 'en'});
|
125
|
+
// simulates setting default voice on tts startup
|
126
|
+
ttsEngine.updateBestVoice();
|
127
|
+
// simulates user choosing a voice that matches the bookLanguage
|
128
|
+
// voice will be stored in localStorage
|
129
|
+
ttsEngine.setVoice(voices[2].voiceURI);
|
130
|
+
|
131
|
+
// expecting the voice to be selected by getMatchingStoredVoice and returned as best voice
|
132
|
+
expect(getBestBookVoice(voices, 'en', [])).toBe(voices[2]);
|
133
|
+
});
|
114
134
|
});
|
115
135
|
}
|
116
136
|
|
@@ -19,6 +19,10 @@ describe('UrlPlugin tests', () => {
|
|
19
19
|
expect(urlPlugin.urlStateToUrlString(urlStateWithQueries)).toBe(expectedUrlFromStateWithQueries);
|
20
20
|
});
|
21
21
|
|
22
|
+
test('encodes page number', () => {
|
23
|
+
expect(urlPlugin.urlStateToUrlString({ page: '12/46' })).toBe(`page/12%2F46`);
|
24
|
+
});
|
25
|
+
|
22
26
|
test('urlStateToUrlString with unknown states in schema', () => {
|
23
27
|
const urlState = { page: 'n7', mode: '1up' };
|
24
28
|
const urlStateWithQueries = { page: 'n7', mode: '1up', q: 'hello', viewer: 'theater', sortBy: 'title_asc' };
|
@@ -47,6 +51,10 @@ describe('UrlPlugin tests', () => {
|
|
47
51
|
expect(urlPlugin.urlStringToUrlState(url1)).toEqual({page: 'n7', mode: '1up'});
|
48
52
|
});
|
49
53
|
|
54
|
+
test('decodes page number', () => {
|
55
|
+
expect(urlPlugin.urlStringToUrlState('/page/12%2F46')).toStrictEqual({ page: '12/46' });
|
56
|
+
});
|
57
|
+
|
50
58
|
test('urlStringToUrlState with deprecated_for', () => {
|
51
59
|
const url = '/page/n7/mode/2up/search/hello';
|
52
60
|
|