support for multiple audio sources, version bump

This commit is contained in:
Alex Yatskov 2017-04-02 12:08:15 -07:00
parent 4eb3e2f06c
commit 9ac55fb5d1
7 changed files with 86 additions and 130 deletions

View File

@ -25,7 +25,7 @@ function formRead() {
return optionsLoad().then(optionsOld => {
const optionsNew = $.extend(true, {}, optionsOld);
optionsNew.general.audioPlayback = $('#audio-playback-buttons').prop('checked');
optionsNew.general.audioSource = $('#audio-playback-source').val();
optionsNew.general.audioVolume = $('#audio-playback-volume').val();
optionsNew.general.groupResults = $('#group-terms-results').prop('checked');
optionsNew.general.softKatakana = $('#soft-katakana-search').prop('checked');
@ -111,7 +111,7 @@ $(document).ready(() => {
handlebarsRegister();
optionsLoad().then(options => {
$('#audio-playback-buttons').prop('checked', options.general.audioPlayback);
$('#audio-playback-source').val(options.general.audioSource);
$('#audio-playback-volume').val(options.general.audioVolume);
$('#group-terms-results').prop('checked', options.general.groupResults);
$('#soft-katakana-search').prop('checked', options.general.softKatakana);

View File

@ -25,9 +25,6 @@ function promiseCallback(promise, callback) {
return promise.then(result => {
callback({result});
}).catch(error => {
/* eslint-disable */
console.log(error);
/* eslint-enable */
callback({error});
});
}
@ -84,7 +81,7 @@ function optionsSetDefaults(options) {
const defaults = {
general: {
enable: true,
audioPlayback: true,
audioSource: 'jpod101',
audioVolume: 100,
groupResults: true,
softKatakana: true,
@ -137,84 +134,15 @@ function optionsSetDefaults(options) {
function optionsVersion(options) {
const fixups = [
() => { },
() => { },
() => { },
() => { },
() => {
const copy = (targetDict, targetKey, sourceDict, sourceKey) => {
targetDict[targetKey] = sourceDict.hasOwnProperty(sourceKey) ? sourceDict[sourceKey] : targetDict[targetKey];
};
copy(options.general, 'autoStart', options, 'activateOnStartup');
copy(options.general, 'audioPlayback', options, 'enableAudioPlayback');
copy(options.general, 'softKatakana', options, 'enableSoftKatakanaSearch');
copy(options.general, 'groupResults', options, 'groupTermResults');
copy(options.general, 'showAdvanced', options, 'showAdvancedOptions');
copy(options.scanning, 'requireShift', options, 'holdShiftToScan');
copy(options.scanning, 'selectText', options, 'selectMatchedText');
copy(options.scanning, 'delay', options, 'scanDelay');
copy(options.scanning, 'length', options, 'scanLength');
options.anki.enable = options.ankiMethod === 'ankiconnect';
copy(options.anki, 'tags', options, 'ankiCardTags');
copy(options.anki, 'sentenceExt', options, 'sentenceExtent');
copy(options.anki.terms, 'deck', options, 'ankiTermDeck');
copy(options.anki.terms, 'model', options, 'ankiTermModel');
copy(options.anki.terms, 'fields', options, 'ankiTermFields');
copy(options.anki.kanji, 'deck', options, 'ankiKanjiDeck');
copy(options.anki.kanji, 'model', options, 'ankiKanjiModel');
copy(options.anki.kanji, 'fields', options, 'ankiKanjiFields');
for (const title in options.dictionaries) {
const dictionary = options.dictionaries[title];
dictionary.enabled = dictionary.enableTerms || dictionary.enableKanji;
dictionary.priority = 0;
}
},
() => {
const fixupFields = fields => {
const fixups = {
'{expression-furigana}': '{furigana}',
'{glossary-list}': '{glossary}'
};
for (const name in fields) {
for (const fixup in fixups) {
fields[name] = fields[name].replace(fixup, fixups[fixup]);
}
}
};
fixupFields(options.anki.terms.fields);
fixupFields(options.anki.kanji.fields);
},
() => {
let hasEnabledDict = false;
for (const title in options.dictionaries) {
if (options.dictionaries[title].enabled) {
hasEnabledDict = true;
break;
}
}
if (!hasEnabledDict) {
for (const title in options.dictionaries) {
options.dictionaries[title].enabled = true;
}
}
},
() => {
let hasEnabledDict = false;
for (const title in options.dictionaries) {
if (options.dictionaries[title].enabled) {
hasEnabledDict = true;
break;
}
}
if (!hasEnabledDict) {
for (const title in options.dictionaries) {
options.dictionaries[title].enabled = true;
}
if (options.general.audioPlayback) {
options.general.audioSource = 'jpod101';
} else {
options.general.audioSource = 'disabled';
}
}
];

View File

@ -121,7 +121,7 @@ window.yomichan = new class {
definitionAdd(definition, mode) {
let promise = Promise.resolve();
if (mode !== 'kanji') {
promise = audioInject(definition, this.options.anki.terms.fields);
promise = audioInject(definition, this.options.anki.terms.fields, this.options.general.audioSource);
}
return promise.then(() => {

View File

@ -37,8 +37,13 @@
<label><input type="checkbox" id="soft-katakana-search"> Soft Katakana search</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="audio-playback-buttons"> Audio playback buttons</label>
<div class="form-group">
<label for="audio-playback-source">Audio playback source</label>
<select class="form-control" id="audio-playback-source">
<option value="disabled">Disabled</option>
<option value="jpod101">JapanesePod101</option>
<option value="jpod101-alternate">JapanesePod101 (Alternate)</option>
</select>
</div>
<div class="form-group options-advanced">
@ -52,7 +57,7 @@
</div>
<div class="form-group options-advanced">
<label>Popup size (in pixels)</label>
<label>Popup size (width x height, in pixels)</label>
<div class="row">
<div class="col-xs-6"><input type="number" min="1" id="popup-width" class="form-control"></div>
<div class="col-xs-6"><input type="number" min="1" id="popup-height" class="form-control"></div>

View File

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Yomichan",
"version": "1.1.10",
"version": "1.1.11",
"description": "Japanese dictionary with Anki integration",
"icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"},

View File

@ -69,7 +69,7 @@ class Display {
definitions,
addable: options.anki.enable,
grouped: options.general.groupResults,
playback: options.general.audioPlayback
playback: options.general.audioSource !== 'disabled'
};
if (context) {
@ -335,7 +335,7 @@ class Display {
this.audioCache[key].pause();
}
audioBuildUrl(definition, this.responseCache).then(url => {
audioBuildUrl(definition, this.options.general.audioSource, this.responseCache).then(url => {
if (!url) {
url = '/mixed/mp3/button.mp3';
}

View File

@ -21,48 +21,71 @@
* Audio
*/
function audioBuildUrl(definition, cache={}) {
return new Promise((resolve, reject) => {
const response = cache[definition.expression];
if (response) {
resolve(response);
} else {
const data = {
post: 'dictionary_reference',
match_type: 'exact',
search_query: definition.expression
};
function audioBuildUrl(definition, mode, cache={}) {
if (mode === 'jpod101') {
let kana = definition.reading;
let kanji = definition.expression;
const params = [];
for (const key in data) {
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
}
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.addEventListener('error', () => reject('failed to scrape audio data'));
xhr.addEventListener('load', () => {
cache[definition.expression] = xhr.responseText;
resolve(xhr.responseText);
});
xhr.send(params.join('&'));
if (!kana && wanakana.isHiragana(kanji)) {
kana = kanji;
kanji = null;
}
}).then(response => {
const dom = new DOMParser().parseFromString(response, 'text/html');
for (const row of dom.getElementsByClassName('dc-result-row')) {
try {
const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url');
const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText;
if (url && reading && (!definition.reading || definition.reading === reading)) {
return url;
const params = [];
if (kanji) {
params.push(`kanji=${encodeURIComponent(kanji)}`);
}
if (kana) {
params.push(`kana=${encodeURIComponent(kana)}`);
}
const url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`;
return Promise.resolve(url);
} else if (mode === 'jpod101-alternate') {
return new Promise((resolve, reject) => {
const response = cache[definition.expression];
if (response) {
resolve(response);
} else {
const data = {
post: 'dictionary_reference',
match_type: 'exact',
search_query: definition.expression
};
const params = [];
for (const key in data) {
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
}
} catch (e) {
// NOP
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.addEventListener('error', () => reject('failed to scrape audio data'));
xhr.addEventListener('load', () => {
cache[definition.expression] = xhr.responseText;
resolve(xhr.responseText);
});
xhr.send(params.join('&'));
}
}
});
}).then(response => {
const dom = new DOMParser().parseFromString(response, 'text/html');
for (const row of dom.getElementsByClassName('dc-result-row')) {
try {
const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url');
const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText;
if (url && reading && (!definition.reading || definition.reading === reading)) {
return url;
}
} catch (e) {
// NOP
}
}
});
} else {
return Promise.reject('unsupported audio source');
}
}
function audioBuildFilename(definition) {
@ -79,7 +102,7 @@ function audioBuildFilename(definition) {
}
}
function audioInject(definition, fields) {
function audioInject(definition, fields, mode) {
const filename = audioBuildFilename(definition);
if (!filename) {
return Promise.resolve(true);
@ -97,7 +120,7 @@ function audioInject(definition, fields) {
return Promise.resolve(true);
}
return audioBuildUrl(definition).then(url => {
return audioBuildUrl(definition, mode).then(url => {
definition.audio = {url, filename};
return true;
}).catch(() => false);