@internetarchive/bookreader 5.0.0-70-a3 → 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/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/CHANGELOG.md +8 -0
- package/package.json +4 -4
- package/src/BookReader.js +4 -5
- package/src/plugins/tts/AbstractTTSEngine.js +22 -3
- package/src/plugins/url/UrlPlugin.js +5 -7
- package/tests/jest/plugins/tts/AbstractTTSEngine.test.js +20 -0
- package/tests/jest/plugins/url/UrlPlugin.test.js +8 -0
- /package/tests/jest/BookNavigator/{volumes/volumes-provider.test.js → viewable-files/viewable-files-provider.test.js} +0 -0
@@ -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
|
@@ -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
|
|