Merge branch 'dev'

This commit is contained in:
Alex Yatskov 2017-04-02 12:08:49 -07:00
commit ccb740da58
7 changed files with 86 additions and 130 deletions

View File

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

View File

@ -25,9 +25,6 @@ function promiseCallback(promise, callback) {
return promise.then(result => { return promise.then(result => {
callback({result}); callback({result});
}).catch(error => { }).catch(error => {
/* eslint-disable */
console.log(error);
/* eslint-enable */
callback({error}); callback({error});
}); });
} }
@ -84,7 +81,7 @@ function optionsSetDefaults(options) {
const defaults = { const defaults = {
general: { general: {
enable: true, enable: true,
audioPlayback: true, audioSource: 'jpod101',
audioVolume: 100, audioVolume: 100,
groupResults: true, groupResults: true,
softKatakana: true, softKatakana: true,
@ -137,84 +134,15 @@ function optionsSetDefaults(options) {
function optionsVersion(options) { function optionsVersion(options) {
const fixups = [ const fixups = [
() => { },
() => { },
() => { },
() => { },
() => { () => {
const copy = (targetDict, targetKey, sourceDict, sourceKey) => { if (options.general.audioPlayback) {
targetDict[targetKey] = sourceDict.hasOwnProperty(sourceKey) ? sourceDict[sourceKey] : targetDict[targetKey]; options.general.audioSource = 'jpod101';
}; } else {
options.general.audioSource = 'disabled';
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;
}
} }
} }
]; ];

View File

@ -121,7 +121,7 @@ window.yomichan = new class {
definitionAdd(definition, mode) { definitionAdd(definition, mode) {
let promise = Promise.resolve(); let promise = Promise.resolve();
if (mode !== 'kanji') { 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(() => { return promise.then(() => {

View File

@ -37,8 +37,13 @@
<label><input type="checkbox" id="soft-katakana-search"> Soft Katakana search</label> <label><input type="checkbox" id="soft-katakana-search"> Soft Katakana search</label>
</div> </div>
<div class="checkbox"> <div class="form-group">
<label><input type="checkbox" id="audio-playback-buttons"> Audio playback buttons</label> <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>
<div class="form-group options-advanced"> <div class="form-group options-advanced">
@ -52,7 +57,7 @@
</div> </div>
<div class="form-group options-advanced"> <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="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-width" class="form-control"></div>
<div class="col-xs-6"><input type="number" min="1" id="popup-height" 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, "manifest_version": 2,
"name": "Yomichan", "name": "Yomichan",
"version": "1.1.10", "version": "1.1.11",
"description": "Japanese dictionary with Anki integration", "description": "Japanese dictionary with Anki integration",
"icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"}, "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, definitions,
addable: options.anki.enable, addable: options.anki.enable,
grouped: options.general.groupResults, grouped: options.general.groupResults,
playback: options.general.audioPlayback playback: options.general.audioSource !== 'disabled'
}; };
if (context) { if (context) {
@ -335,7 +335,7 @@ class Display {
this.audioCache[key].pause(); this.audioCache[key].pause();
} }
audioBuildUrl(definition, this.responseCache).then(url => { audioBuildUrl(definition, this.options.general.audioSource, this.responseCache).then(url => {
if (!url) { if (!url) {
url = '/mixed/mp3/button.mp3'; url = '/mixed/mp3/button.mp3';
} }

View File

@ -21,48 +21,71 @@
* Audio * Audio
*/ */
function audioBuildUrl(definition, cache={}) { function audioBuildUrl(definition, mode, cache={}) {
return new Promise((resolve, reject) => { if (mode === 'jpod101') {
const response = cache[definition.expression]; let kana = definition.reading;
if (response) { let kanji = definition.expression;
resolve(response);
} else {
const data = {
post: 'dictionary_reference',
match_type: 'exact',
search_query: definition.expression
};
const params = []; if (!kana && wanakana.isHiragana(kanji)) {
for (const key in data) { kana = kanji;
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`); kanji = null;
}
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'); const params = [];
for (const row of dom.getElementsByClassName('dc-result-row')) { if (kanji) {
try { params.push(`kanji=${encodeURIComponent(kanji)}`);
const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url'); }
const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText; if (kana) {
if (url && reading && (!definition.reading || definition.reading === reading)) { params.push(`kana=${encodeURIComponent(kana)}`);
return url; }
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) { function audioBuildFilename(definition) {
@ -79,7 +102,7 @@ function audioBuildFilename(definition) {
} }
} }
function audioInject(definition, fields) { function audioInject(definition, fields, mode) {
const filename = audioBuildFilename(definition); const filename = audioBuildFilename(definition);
if (!filename) { if (!filename) {
return Promise.resolve(true); return Promise.resolve(true);
@ -97,7 +120,7 @@ function audioInject(definition, fields) {
return Promise.resolve(true); return Promise.resolve(true);
} }
return audioBuildUrl(definition).then(url => { return audioBuildUrl(definition, mode).then(url => {
definition.audio = {url, filename}; definition.audio = {url, filename};
return true; return true;
}).catch(() => false); }).catch(() => false);