This commit is contained in:
Alex Yatskov 2019-04-28 11:47:04 -07:00
commit 9b00daed07
9 changed files with 276 additions and 7 deletions

View File

@ -69,10 +69,10 @@ async function audioBuildUrl(definition, mode, cache={}) {
const dom = new DOMParser().parseFromString(response, 'text/html'); const dom = new DOMParser().parseFromString(response, 'text/html');
for (const row of dom.getElementsByClassName('dc-result-row')) { for (const row of dom.getElementsByClassName('dc-result-row')) {
try { try {
const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url'); const url = row.querySelector('audio>source[src]').getAttribute('src');
const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText; const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText;
if (url && reading && (!definition.reading || definition.reading === reading)) { if (url && reading && (!definition.reading || definition.reading === reading)) {
return url; return audioUrlNormalize(url, 'https://www.japanesepod101.com', '/learningcenter/reference/');
} }
} catch (e) { } catch (e) {
// NOP // NOP
@ -86,7 +86,7 @@ async function audioBuildUrl(definition, mode, cache={}) {
resolve(response); resolve(response);
} else { } else {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('GET', `http://jisho.org/search/${definition.expression}`); xhr.open('GET', `https://jisho.org/search/${definition.expression}`);
xhr.addEventListener('error', () => reject('Failed to scrape audio data')); xhr.addEventListener('error', () => reject('Failed to scrape audio data'));
xhr.addEventListener('load', () => { xhr.addEventListener('load', () => {
cache[definition.expression] = xhr.responseText; cache[definition.expression] = xhr.responseText;
@ -100,7 +100,10 @@ async function audioBuildUrl(definition, mode, cache={}) {
const dom = new DOMParser().parseFromString(response, 'text/html'); const dom = new DOMParser().parseFromString(response, 'text/html');
const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`); const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`);
if (audio) { if (audio) {
return audio.getElementsByTagName('source').item(0).getAttribute('src'); const url = audio.getElementsByTagName('source').item(0).getAttribute('src');
if (url) {
return audioUrlNormalize(url, 'https://jisho.org', '/search/');
}
} }
} catch (e) { } catch (e) {
// NOP // NOP
@ -112,6 +115,24 @@ async function audioBuildUrl(definition, mode, cache={}) {
} }
} }
function audioUrlNormalize(url, baseUrl, basePath) {
if (url) {
if (url[0] === '/') {
if (url.length >= 2 && url[1] === '/') {
// Begins with "//"
url = baseUrl.substr(0, baseUrl.indexOf(':') + 1) + url;
} else {
// Begins with "/"
url = baseUrl + url;
}
} else if (!/^[a-z][a-z0-9\+\-\.]*:/i.test(url)) {
// No URI scheme => relative path
url = baseUrl + basePath + url;
}
}
return url;
}
function audioBuildFilename(definition) { function audioBuildFilename(definition) {
if (definition.reading || definition.expression) { if (definition.reading || definition.expression) {
let filename = 'yomichan'; let filename = 'yomichan';

View File

@ -190,6 +190,7 @@ function optionsSetDefaults(options) {
debugInfo: false, debugInfo: false,
maxResults: 32, maxResults: 32,
showAdvanced: false, showAdvanced: false,
popupDisplayMode: 'default',
popupWidth: 400, popupWidth: 400,
popupHeight: 250, popupHeight: 250,
popupOffset: 10, popupOffset: 10,
@ -201,6 +202,7 @@ function optionsSetDefaults(options) {
scanning: { scanning: {
middleMouse: true, middleMouse: true,
touchInputEnabled: true,
selectText: true, selectText: true,
alphanumeric: true, alphanumeric: true,
autoHideResults: false, autoHideResults: false,

View File

@ -31,11 +31,13 @@ async function formRead() {
optionsNew.general.debugInfo = $('#show-debug-info').prop('checked'); optionsNew.general.debugInfo = $('#show-debug-info').prop('checked');
optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked'); optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked');
optionsNew.general.maxResults = parseInt($('#max-displayed-results').val(), 10); optionsNew.general.maxResults = parseInt($('#max-displayed-results').val(), 10);
optionsNew.general.popupDisplayMode = $('#popup-display-mode').val();
optionsNew.general.popupWidth = parseInt($('#popup-width').val(), 10); optionsNew.general.popupWidth = parseInt($('#popup-width').val(), 10);
optionsNew.general.popupHeight = parseInt($('#popup-height').val(), 10); optionsNew.general.popupHeight = parseInt($('#popup-height').val(), 10);
optionsNew.general.popupOffset = parseInt($('#popup-offset').val(), 10); optionsNew.general.popupOffset = parseInt($('#popup-offset').val(), 10);
optionsNew.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); optionsNew.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked');
optionsNew.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked');
optionsNew.scanning.selectText = $('#select-matched-text').prop('checked'); optionsNew.scanning.selectText = $('#select-matched-text').prop('checked');
optionsNew.scanning.alphanumeric = $('#search-alphanumeric').prop('checked'); optionsNew.scanning.alphanumeric = $('#search-alphanumeric').prop('checked');
optionsNew.scanning.autoHideResults = $('#auto-hide-results').prop('checked'); optionsNew.scanning.autoHideResults = $('#auto-hide-results').prop('checked');
@ -161,11 +163,13 @@ async function onReady() {
$('#show-debug-info').prop('checked', options.general.debugInfo); $('#show-debug-info').prop('checked', options.general.debugInfo);
$('#show-advanced-options').prop('checked', options.general.showAdvanced); $('#show-advanced-options').prop('checked', options.general.showAdvanced);
$('#max-displayed-results').val(options.general.maxResults); $('#max-displayed-results').val(options.general.maxResults);
$('#popup-display-mode').val(options.general.popupDisplayMode);
$('#popup-width').val(options.general.popupWidth); $('#popup-width').val(options.general.popupWidth);
$('#popup-height').val(options.general.popupHeight); $('#popup-height').val(options.general.popupHeight);
$('#popup-offset').val(options.general.popupOffset); $('#popup-offset').val(options.general.popupOffset);
$('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse);
$('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled);
$('#select-matched-text').prop('checked', options.scanning.selectText); $('#select-matched-text').prop('checked', options.scanning.selectText);
$('#search-alphanumeric').prop('checked', options.scanning.alphanumeric); $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric);
$('#auto-hide-results').prop('checked', options.scanning.autoHideResults); $('#auto-hide-results').prop('checked', options.scanning.autoHideResults);

View File

@ -74,6 +74,14 @@
</select> </select>
</div> </div>
<div class="form-group">
<label for="popup-display-mode">Popup display mode</label>
<select class="form-control" id="popup-display-mode">
<option value="default">Default</option>
<option value="full-width">Full width</option>
</select>
</div>
<div class="form-group options-advanced"> <div class="form-group options-advanced">
<label for="audio-playback-volume">Audio playback volume (percent)</label> <label for="audio-playback-volume">Audio playback volume (percent)</label>
<input type="number" min="0" max="100" id="audio-playback-volume" class="form-control"> <input type="number" min="0" max="100" id="audio-playback-volume" class="form-control">
@ -105,6 +113,10 @@
<label><input type="checkbox" id="middle-mouse-button-scan"> Middle mouse button scans</label> <label><input type="checkbox" id="middle-mouse-button-scan"> Middle mouse button scans</label>
</div> </div>
<div class="checkbox">
<label><input type="checkbox" id="touch-input-enabled"> Touch input enabled</label>
</div>
<div class="checkbox"> <div class="checkbox">
<label><input type="checkbox" id="select-matched-text"> Select matched text</label> <label><input type="checkbox" id="select-matched-text"> Select matched text</label>
</div> </div>

View File

@ -27,3 +27,25 @@ iframe#yomichan-float {
visibility: hidden; visibility: hidden;
z-index: 2147483647; z-index: 2147483647;
} }
iframe#yomichan-float.yomichan-float-full-width {
border-left: none;
border-right: none;
left: 0 !important;
right: 0 !important;
width: 100% !important;
box-sizing: border-box;
resize: none;
}
iframe#yomichan-float.yomichan-float-full-width:not(.yomichan-float-above) {
border-bottom: none;
top: auto !important;
bottom: 0 !important;
}
iframe#yomichan-float.yomichan-float-full-width.yomichan-float-above {
border-top: none;
top: 0 !important;
bottom: auto !important;
}

View File

@ -85,14 +85,24 @@ function docRangeFromPoint(point) {
range.setEnd(position.offsetNode, position.offset); range.setEnd(position.offsetNode, position.offset);
return range; return range;
} }
return null;
}; };
} }
const range = document.caretRangeFromPoint(point.x, point.y); const range = document.caretRangeFromPoint(point.x, point.y);
if (range === null) {
return;
}
if(imposter !== null) imposter.style.zIndex = -2147483646; if(imposter !== null) imposter.style.zIndex = -2147483646;
const rect = range.getClientRects()[0]; const rects = range.getClientRects();
if (rects.length === 0) {
return;
}
const rect = rects[0];
if (point.y > rect.bottom + 2) { if (point.y > rect.bottom + 2) {
return; return;
} }

View File

@ -26,6 +26,14 @@ class Frontend {
this.textSourceLast = null; this.textSourceLast = null;
this.pendingLookup = false; this.pendingLookup = false;
this.options = null; this.options = null;
this.primaryTouchIdentifier = null;
this.contextMenuChecking = false;
this.contextMenuPrevent = false;
this.contextMenuPreviousRange = null;
this.mouseDownPrevent = false;
this.clickPrevent = false;
this.scrollPrevent = false;
} }
async prepare() { async prepare() {
@ -39,6 +47,15 @@ class Frontend {
window.addEventListener('mouseup', this.onMouseUp.bind(this)); window.addEventListener('mouseup', this.onMouseUp.bind(this));
window.addEventListener('resize', this.onResize.bind(this)); window.addEventListener('resize', this.onResize.bind(this));
if (this.options.scanning.touchInputEnabled) {
window.addEventListener('click', this.onClick.bind(this));
window.addEventListener('touchstart', this.onTouchStart.bind(this));
window.addEventListener('touchend', this.onTouchEnd.bind(this));
window.addEventListener('touchcancel', this.onTouchCancel.bind(this));
window.addEventListener('touchmove', this.onTouchMove.bind(this), {passive: false});
window.addEventListener('contextmenu', this.onContextMenu.bind(this));
}
chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this)); chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
} catch (e) { } catch (e) {
this.onError(e); this.onError(e);
@ -79,7 +96,7 @@ class Frontend {
const search = async () => { const search = async () => {
try { try {
await this.searchAt({x: e.clientX, y: e.clientY}); await this.searchAt({x: e.clientX, y: e.clientY}, 'mouse');
} catch (e) { } catch (e) {
this.onError(e); this.onError(e);
} }
@ -93,6 +110,14 @@ class Frontend {
} }
onMouseDown(e) { onMouseDown(e) {
if (this.mouseDownPrevent) {
this.setMouseDownPrevent(false, false);
this.setClickPrevent(true);
e.preventDefault();
e.stopPropagation();
return false;
}
this.mousePosLast = {x: e.clientX, y: e.clientY}; this.mousePosLast = {x: e.clientX, y: e.clientY};
this.popupTimerClear(); this.popupTimerClear();
this.searchClear(); this.searchClear();
@ -133,6 +158,85 @@ class Frontend {
this.searchClear(); this.searchClear();
} }
onClick(e) {
if (this.clickPrevent) {
this.setClickPrevent(false);
e.preventDefault();
e.stopPropagation();
return false;
}
}
onTouchStart(e) {
if (this.primaryTouchIdentifier !== null && this.getIndexOfTouch(e.touches, this.primaryTouchIdentifier) >= 0) {
return;
}
this.setPrimaryTouch(this.getPrimaryTouch(e.changedTouches));
}
onTouchEnd(e) {
if (this.primaryTouchIdentifier === null) {
return;
}
if (this.getIndexOfTouch(e.changedTouches, this.primaryTouchIdentifier) < 0) {
return;
}
this.setPrimaryTouch(this.getPrimaryTouch(this.excludeTouches(e.touches, e.changedTouches)));
}
onTouchCancel(e) {
this.onTouchEnd(e);
}
onTouchMove(e) {
if (!this.scrollPrevent || this.primaryTouchIdentifier === null) {
return;
}
const touches = e.changedTouches;
const index = this.getIndexOfTouch(touches, this.primaryTouchIdentifier);
if (index < 0) {
return;
}
const touch = touches[index];
this.searchFromTouch(touch.clientX, touch.clientY, 'touchMove');
e.preventDefault(); // Disable scroll
}
onContextMenu(e) {
if (this.contextMenuPrevent) {
this.setContextMenuPrevent(false, false);
e.preventDefault();
e.stopPropagation();
return false;
}
}
onAfterSearch(newRange, type, searched, success) {
if (type === 'mouse') {
return;
}
if (
!this.contextMenuChecking ||
(this.contextMenuPreviousRange === null ? newRange === null : this.contextMenuPreviousRange.equals(newRange))) {
return;
}
if (type === 'touchStart' && newRange !== null) {
this.scrollPrevent = true;
}
this.setContextMenuPrevent(true, false);
this.setMouseDownPrevent(true, false);
this.contextMenuChecking = false;
}
onBgMessage({action, params}, sender, callback) { onBgMessage({action, params}, sender, callback) {
const handlers = { const handlers = {
optionsSet: options => { optionsSet: options => {
@ -167,18 +271,22 @@ class Frontend {
} }
} }
async searchAt(point) { async searchAt(point, type) {
if (this.pendingLookup || this.popup.containsPoint(point)) { if (this.pendingLookup || this.popup.containsPoint(point)) {
return; return;
} }
const textSource = docRangeFromPoint(point); const textSource = docRangeFromPoint(point);
let hideResults = !textSource || !textSource.containsPoint(point); let hideResults = !textSource || !textSource.containsPoint(point);
let searched = false;
let success = false;
try { try {
if (!hideResults && (!this.textSourceLast || !this.textSourceLast.equals(textSource))) { if (!hideResults && (!this.textSourceLast || !this.textSourceLast.equals(textSource))) {
searched = true;
this.pendingLookup = true; this.pendingLookup = true;
hideResults = !await this.searchTerms(textSource) && !await this.searchKanji(textSource); hideResults = !await this.searchTerms(textSource) && !await this.searchKanji(textSource);
success = true;
} }
} catch (e) { } catch (e) {
if (window.yomichan_orphaned) { if (window.yomichan_orphaned) {
@ -196,6 +304,7 @@ class Frontend {
} }
this.pendingLookup = false; this.pendingLookup = false;
this.onAfterSearch(this.textSourceLast, type, searched, success);
} }
} }
@ -262,6 +371,87 @@ class Frontend {
this.textSourceLast = null; this.textSourceLast = null;
} }
getPrimaryTouch(touchList) {
return touchList.length > 0 ? touchList[0] : null;
}
getIndexOfTouch(touchList, identifier) {
for (let i in touchList) {
let t = touchList[i];
if (t.identifier === identifier) {
return i;
}
}
return -1;
}
excludeTouches(touchList, excludeTouchList) {
const result = [];
for (let r of touchList) {
if (this.getIndexOfTouch(excludeTouchList, r.identifier) < 0) {
result.push(r);
}
}
return result;
}
setPrimaryTouch(touch) {
if (touch === null) {
this.primaryTouchIdentifier = null;
this.contextMenuPreviousRange = null;
this.contextMenuChecking = false;
this.scrollPrevent = false;
this.setContextMenuPrevent(false, true);
this.setMouseDownPrevent(false, true);
this.setClickPrevent(false);
}
else {
this.primaryTouchIdentifier = touch.identifier;
this.contextMenuPreviousRange = this.textSourceLast ? this.textSourceLast.clone() : null;
this.contextMenuChecking = true;
this.scrollPrevent = false;
this.setContextMenuPrevent(false, false);
this.setMouseDownPrevent(false, false);
this.setClickPrevent(false);
this.searchFromTouch(touch.clientX, touch.clientY, 'touchStart');
}
}
setContextMenuPrevent(value, delay) {
if (!delay) {
this.contextMenuPrevent = value;
}
}
setMouseDownPrevent(value, delay) {
if (!delay) {
this.mouseDownPrevent = value;
}
}
setClickPrevent(value) {
this.clickPrevent = value;
}
searchFromTouch(x, y, type) {
this.popupTimerClear();
if (!this.options.general.enable || this.pendingLookup) {
return;
}
const search = async () => {
try {
await this.searchAt({x, y}, type);
} catch (e) {
this.onError(e);
}
};
search();
}
} }
window.yomichan_frontend = new Frontend(); window.yomichan_frontend = new Frontend();

View File

@ -62,6 +62,7 @@ class Popup {
} }
} }
let above = false;
let y = 0; let y = 0;
let height = Math.max(containerHeight, options.general.popupHeight); let height = Math.max(containerHeight, options.general.popupHeight);
const yBelow = elementRect.bottom + options.general.popupOffset; const yBelow = elementRect.bottom + options.general.popupOffset;
@ -75,11 +76,14 @@ class Popup {
} else { } else {
height = Math.max(height - overflowAbove, 0); height = Math.max(height - overflowAbove, 0);
y = Math.max(yAbove - height, 0); y = Math.max(yAbove - height, 0);
above = true;
} }
} else { } else {
y = yBelow; y = yBelow;
} }
this.container.classList.toggle('yomichan-float-full-width', options.general.popupDisplayMode === 'full-width');
this.container.classList.toggle('yomichan-float-above', above);
this.container.style.left = `${x}px`; this.container.style.left = `${x}px`;
this.container.style.top = `${y}px`; this.container.style.top = `${y}px`;
this.container.style.width = `${width}px`; this.container.style.width = `${width}px`;

View File

@ -27,6 +27,10 @@
}], }],
"minimum_chrome_version": "57.0.0.0", "minimum_chrome_version": "57.0.0.0",
"options_page": "bg/settings.html", "options_page": "bg/settings.html",
"options_ui": {
"page": "bg/settings.html",
"open_in_tab": true
},
"permissions": [ "permissions": [
"<all_urls>", "<all_urls>",
"storage", "storage",