diff options
author | Alex Yatskov <alex@foosoft.net> | 2016-10-08 17:39:21 -0700 |
---|---|---|
committer | Alex Yatskov <alex@foosoft.net> | 2016-10-08 17:39:21 -0700 |
commit | 1e9906c624828819f74628bac4ee42cca3cbe3b6 (patch) | |
tree | 8d752b94329dc2539db935cb9d8cbbffc6530009 | |
parent | 1d573f4179503ea9e1cc15327f67e8b52740adad (diff) |
Automatically look up Kanji when cannot find definitions.
Allow text scanning without holding Shift key (resolves #18).
-rw-r--r-- | ext/bg/import.html | 5 | ||||
-rw-r--r-- | ext/bg/js/options-form.js | 85 | ||||
-rw-r--r-- | ext/bg/js/options.js | 5 | ||||
-rw-r--r-- | ext/bg/js/translator.js | 2 | ||||
-rw-r--r-- | ext/bg/options.html | 37 | ||||
-rw-r--r-- | ext/fg/js/driver.js | 85 | ||||
-rw-r--r-- | ext/fg/js/popup.js | 44 |
7 files changed, 157 insertions, 106 deletions
diff --git a/ext/bg/import.html b/ext/bg/import.html index 34df2126..7f30b491 100644 --- a/ext/bg/import.html +++ b/ext/bg/import.html @@ -46,15 +46,10 @@ <li>Left-click on the <img src="../img/icon16.png" alt> icon to enable or disable Yomichan for the current browser instance.</li> <li>Right-click on the <img src="../img/icon16.png" alt> icon and select <em>Options</em> to open the Yomichan options page.</li> <li>Hold down <kbd>Shift</kbd> (or the middle mouse button) as you hover over text to see term definitions.</li> - <li>Hold down <kbd>Ctrl</kbd> + <kbd>Shift</kbd> (or the middle mouse button) as you hover over text to see Kanji definitions.</li> <li>Resize the definitions window by dragging the bottom-left corner inwards or outwards.</li> <li>Click on Kanji in the definition window to view additional information about that character.</li> </ol> </div> - - <br> - - <p>よろしくね!</p> </div> <script src="../lib/jquery-2.2.2.min.js"></script> diff --git a/ext/bg/js/options-form.js b/ext/bg/js/options-form.js index 1cd050b4..ed8c7927 100644 --- a/ext/bg/js/options-form.js +++ b/ext/bg/js/options-form.js @@ -39,51 +39,43 @@ function modelIdToFieldOptKey(id) { function modelIdToMarkers(id) { return { - 'anki-term-model': [ - 'audio', - 'expression', - 'glossary', - 'glossary-list', - 'reading', - 'sentence', - 'tags', - 'url' - ], - 'anki-kanji-model': [ - 'character', - 'glossary', - 'glossary-list', - 'kunyomi', - 'onyomi', - 'url' - ], + 'anki-term-model': ['audio', 'exholdion', 'glossary', 'glossary-list', 'reading', 'sentence', 'tags', 'url'], + 'anki-kanji-model': ['character', 'glossary', 'glossary-list', 'kunyomi', 'onyomi', 'url'], }[id]; } -function formToOptions(section) { +function getBasicOptions() { return loadOptions().then(optsOld => { const optsNew = $.extend({}, optsOld); - switch (section) { - case 'general': - optsNew.scanLength = parseInt($('#scan-length').val(), 10); - optsNew.activateOnStartup = $('#activate-on-startup').prop('checked'); - optsNew.selectMatchedText = $('#select-matched-text').prop('checked'); - optsNew.showAdvancedOptions = $('#show-advanced-options').prop('checked'); - optsNew.enableAudioPlayback = $('#enable-audio-playback').prop('checked'); - optsNew.enableAnkiConnect = $('#enable-anki-connect').prop('checked'); - break; - case 'anki': - optsNew.ankiCardTags = $('#anki-card-tags').val().split(/[,; ]+/); - optsNew.sentenceExtent = parseInt($('#sentence-extent').val(), 10); - optsNew.ankiTermDeck = $('#anki-term-deck').val(); - optsNew.ankiTermModel = $('#anki-term-model').val(); - optsNew.ankiTermFields = fieldsToDict($('#term .anki-field-value')); - optsNew.ankiKanjiDeck = $('#anki-kanji-deck').val(); - optsNew.ankiKanjiModel = $('#anki-kanji-model').val(); - optsNew.ankiKanjiFields = fieldsToDict($('#kanji .anki-field-value')); - break; - } + optsNew.activateOnStartup = $('#activate-on-startup').prop('checked'); + optsNew.showAdvancedOptions = $('#show-advanced-options').prop('checked'); + optsNew.enableAudioPlayback = $('#enable-audio-playback').prop('checked'); + optsNew.enableAnkiConnect = $('#enable-anki-connect').prop('checked'); + optsNew.holdShiftToScan = $('#hold-shift-to-scan').prop('checked'); + optsNew.selectMatchedText = $('#select-matched-text').prop('checked'); + optsNew.scanDelay = parseInt($('#scan-delay').val(), 10); + optsNew.scanLength = parseInt($('#scan-length').val(), 10); + + return { + optsNew: sanitizeOptions(optsNew), + optsOld: sanitizeOptions(optsOld) + }; + }); +} + +function getAnkiOptions() { + return loadOptions().then(optsOld => { + const optsNew = $.extend({}, optsOld); + + optsNew.ankiCardTags = $('#anki-card-tags').val().split(/[,; ]+/); + optsNew.sentenceExtent = parseInt($('#sentence-extent').val(), 10); + optsNew.ankiTermDeck = $('#anki-term-deck').val(); + optsNew.ankiTermModel = $('#anki-term-model').val(); + optsNew.ankiTermFields = fieldsToDict($('#term .anki-field-value')); + optsNew.ankiKanjiDeck = $('#anki-kanji-deck').val(); + optsNew.ankiKanjiModel = $('#anki-kanji-model').val(); + optsNew.ankiKanjiFields = fieldsToDict($('#kanji .anki-field-value')); return { optsNew: sanitizeOptions(optsNew), @@ -182,12 +174,12 @@ function populateAnkiFields(element, opts) { }}); } -function onOptionsGeneralChanged(e) { +function onOptionsBasicChanged(e) { if (!e.originalEvent && !e.isTrigger) { return; } - formToOptions('general').then(({optsNew, optsOld}) => { + getBasicOptions().then(({optsNew, optsOld}) => { saveOptions(optsNew).then(() => { yomichan().setOptions(optsNew); if (!optsOld.enableAnkiConnect && optsNew.enableAnkiConnect) { @@ -212,14 +204,14 @@ function onOptionsAnkiChanged(e) { return; } - formToOptions('anki').then(({optsNew, optsOld}) => { + getAnkiOptions().then(({optsNew, optsOld}) => { saveOptions(optsNew).then(() => yomichan().setOptions(optsNew)); }); } function onAnkiModelChanged(e) { if (e.originalEvent) { - formToOptions('anki').then(({optsNew, optsOld}) => { + getAnkiOptions().then(({optsNew, optsOld}) => { optsNew[modelIdToFieldOptKey($(this).id)] = {}; populateAnkiFields($(this), optsNew); saveOptions(optsNew).then(() => yomichan().setOptions(optsNew)); @@ -230,16 +222,17 @@ function onAnkiModelChanged(e) { $(document).ready(() => { loadOptions().then(opts => { $('#activate-on-startup').prop('checked', opts.activateOnStartup); - $('#select-matched-text').prop('checked', opts.selectMatchedText); $('#enable-audio-playback').prop('checked', opts.enableAudioPlayback); $('#enable-anki-connect').prop('checked', opts.enableAnkiConnect); $('#show-advanced-options').prop('checked', opts.showAdvancedOptions); + $('#hold-shift-to-scan').prop('checked', opts.holdShiftToScan); + $('#select-matched-text').prop('checked', opts.selectMatchedText); + $('#scan-delay').val(opts.scanDelay); $('#scan-length').val(opts.scanLength); - $('#anki-card-tags').val(opts.ankiCardTags.join(' ')); $('#sentence-extent').val(opts.sentenceExtent); - $('.options-general input').change(onOptionsGeneralChanged); + $('.options-basic input').change(onOptionsBasicChanged); $('.options-anki input').change(onOptionsAnkiChanged); $('.anki-deck').change(onOptionsAnkiChanged); $('.anki-model').change(onAnkiModelChanged); diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 915164c7..e9ad74a3 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -20,12 +20,13 @@ function sanitizeOptions(options) { const defaults = { activateOnStartup: true, - selectMatchedText: true, enableAudioPlayback: true, enableAnkiConnect: false, showAdvancedOptions: false, + selectMatchedText: true, + holdShiftToScan: true, + scanDelay: 15, scanLength: 20, - ankiCardTags: ['yomichan'], sentenceExtent: 200, diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 9711cf8d..8f895800 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -135,7 +135,7 @@ class Translator { } } - return Promise.all(promises).then(sets => this.processKanji(sets.reduce((a, b) => a.concat(b)))); + return Promise.all(promises).then(sets => this.processKanji(sets.reduce((a, b) => a.concat(b), []))); } processTerm(groups, source, tags, rules, root) { diff --git a/ext/bg/options.html b/ext/bg/options.html index 781257a2..f5bad2bd 100644 --- a/ext/bg/options.html +++ b/ext/bg/options.html @@ -24,7 +24,7 @@ </head> <body> <div class="container"> - <div class="options-general"> + <div class="options-basic"> <h3>General Options</h3> <form class="form-horizontal"> @@ -39,7 +39,7 @@ <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> - <label class="control-label"><input type="checkbox" id="select-matched-text"> Select matched text</label> + <label class="control-label"><input type="checkbox" id="enable-audio-playback"> Enable audio playback</label> </div> </div> </div> @@ -47,7 +47,7 @@ <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> - <label class="control-label"><input type="checkbox" id="enable-audio-playback"> Enable audio playback</label> + <label class="control-label"><input type="checkbox" id="enable-anki-connect"> Enable <a href="https://foosoft.net/projects/anki-connect">AnkiConnect</a></label> </div> </div> </div> @@ -55,35 +55,55 @@ <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> - <label class="control-label"><input type="checkbox" id="enable-anki-connect"> Enable <a href="https://foosoft.net/projects/anki-connect">AnkiConnect</a></label> + <label class="control-label"><input type="checkbox" id="show-advanced-options"> Show advanced options</label> </div> </div> </div> + </form> + </div> + + <div class="options-basic"> + <h3>Scanning Options</h3> + <form class="form-horizontal"> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> - <label class="control-label"><input type="checkbox" id="show-advanced-options"> Show advanced options</label> + <label class="control-label"><input type="checkbox" id="hold-shift-to-scan"> Hold <kbd>Shift</kbd> to scan</label> + </div> + </div> + </div> + + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label class="control-label"><input type="checkbox" id="select-matched-text"> Select matched text</label> </div> </div> </div> <div class="form-group options-advanced"> + <label for="scan-delay" class="control-label col-sm-2">Scan delay</label> + <div class="col-sm-10"><input type="number" min="1" id="scan-delay" class="form-control"></div> + </div> + + <div class="form-group options-advanced"> <label for="scan-length" class="control-label col-sm-2">Scan length</label> <div class="col-sm-10"><input type="number" min="1" id="scan-length" class="form-control"></div> </div> </form> - </div> <div class="options-anki"> <h3>Anki Options</h3> <div class="alert alert-danger error-dlg error-dlg-connection"> - <strong>Unable to connect</strong>: is the <a href="https://foosoft.net/projects/anki-connect">AnkiConnect</a> extension for <a href="http://ankisrs.net/">Anki</a> installed and running? This software is required for Anki-related features. + <strong>Unable to Connect</strong><br> + Is the <a href="https://foosoft.net/projects/anki-connect">AnkiConnect</a> extension for <a href="http://ankisrs.net/">Anki</a> installed and running? This software is required for Anki-related features. </div> <div class="alert alert-warning error-dlg error-dlg-version"> - <strong>Unsupported version</strong>: the installed version of the <a href="https://foosoft.net/projects/anki-connect">AnkiConnect</a> extension for <a href="http://ankisrs.net/">Anki</a> is not compatible with this release; please update it. + <strong>Unsupported Version</strong><br> + The installed version of the <a href="https://foosoft.net/projects/anki-connect">AnkiConnect</a> extension for <a href="http://ankisrs.net/">Anki</a> is not compatible with this release; please update it. </div> <form class="form-horizontal options-anki-controls"> @@ -158,7 +178,6 @@ </div> </div> </form> - </div> <div style="text-align: right;"> diff --git a/ext/fg/js/driver.js b/ext/fg/js/driver.js index d0907e7d..1e817c1c 100644 --- a/ext/fg/js/driver.js +++ b/ext/fg/js/driver.js @@ -20,6 +20,7 @@ class Driver { constructor() { this.popup = new Popup(); + this.popupTimer = null; this.audio = {}; this.lastMousePos = null; this.lastTextSource = null; @@ -32,6 +33,7 @@ class Driver { chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this)); window.addEventListener('message', this.onFrameMessage.bind(this)); + window.addEventListener('mouseover', this.onMouseOver.bind(this)); window.addEventListener('mousedown', this.onMouseDown.bind(this)); window.addEventListener('mousemove', this.onMouseMove.bind(this)); window.addEventListener('keydown', this.onKeyDown.bind(this)); @@ -46,25 +48,58 @@ class Driver { }); } + popupTimerSet(callback) { + this.popupTimerClear(); + this.popupTimer = window.setTimeout(callback, this.options.scanDelay); + } + + popupTimerClear() { + if (this.popupTimer !== null) { + window.clearTimeout(this.popupTimer); + this.popupTimer = null; + } + } + onKeyDown(e) { - if (this.enabled && this.lastMousePos !== null && (e.keyCode === 16 || e.charCode === 16)) { - this.searchAt(this.lastMousePos, e.ctrlKey ? 'kanji' : 'terms'); + this.popupTimerClear(); + + if (this.enabled && this.lastMousePos !== null && e.keyCode === 16 /* shift */) { + this.searchAt(this.lastMousePos, true); } else { this.hidePopup(); } } + onMouseOver(e) { + if (e.target === this.popup.container && this.popuptimer !== null) { + this.popupTimerClear(); + } + } + onMouseMove(e) { + this.popupTimerClear(); + this.lastMousePos = {x: e.clientX, y: e.clientY}; - if (this.enabled && (e.shiftKey || e.which === 2)) { - this.searchAt(this.lastMousePos, e.ctrlKey ? 'kanji' : 'terms'); + if (!this.enabled) { + return; + } + + if (this.options.holdShiftToScan && !e.shiftKey) { + return; + } + + const searcher = () => this.searchAt(this.lastMousePos, false); + if (!this.popup.visible() || e.shiftKey || e.which === 2 /* mmb */) { + searcher(); + } else { + this.popupTimerSet(searcher); } } onMouseDown(e) { this.lastMousePos = {x: e.clientX, y: e.clientY}; - if (this.enabled && (e.shiftKey || e.which === 2)) { - this.searchAt(this.lastMousePos, e.ctrlKey ? 'kanji' : 'terms'); + if (this.enabled && (e.shiftKey || !this.options.holdShiftToScan || e.which === 2 /* mmb */)) { + this.searchAt(this.lastMousePos, true); } else { this.hidePopup(); } @@ -90,10 +125,10 @@ class Driver { textSource.setEndOffset(this.options.scanLength); this.pendingLookup = true; - findTerm(textSource.text()).then(({definitions, length}) => { + return findTerm(textSource.text()).then(({definitions, length}) => { if (definitions.length === 0) { this.pendingLookup = false; - this.hidePopup(); + return false; } else { textSource.setEndOffset(length); @@ -113,6 +148,8 @@ class Driver { if (states !== null) { states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence})); } + + return true; }); } }); @@ -122,10 +159,10 @@ class Driver { textSource.setEndOffset(1); this.pendingLookup = true; - findKanji(textSource.text()).then(definitions => { + return findKanji(textSource.text()).then(definitions => { if (definitions.length === 0) { this.pendingLookup = false; - this.hidePopup(); + return false; } else { definitions.forEach(definition => definition.url = window.location.href); @@ -139,34 +176,40 @@ class Driver { if (states !== null) { states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence})); } + + return true; }); } }); } - searchAt(point, mode) { + searchAt(point, hideNotFound) { if (this.pendingLookup) { return; } const textSource = textSourceFromPoint(point); if (textSource === null || !textSource.containsPoint(point)) { - this.hidePopup(); + if (hideNotFound) { + this.hidePopup(); + } + return; } if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) { - return; + return true; } - switch (mode) { - case 'terms': - this.searchTerms(textSource); - break; - case 'kanji': - this.searchKanji(textSource); - break; - } + this.searchTerms(textSource).then(found => { + if (!found) { + this.searchKanji(textSource).then(found => { + if (!found && hideNotFound) { + this.hidePopup(); + } + }); + } + }); } showPopup(textSource, content) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 31ff02d8..83da3fe1 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -19,16 +19,16 @@ class Popup { constructor() { - this.popup = null; + this.container = null; this.offset = 10; } showAt(pos, content) { this.inject(); - this.popup.style.left = pos.x + 'px'; - this.popup.style.top = pos.y + 'px'; - this.popup.style.visibility = 'visible'; + this.container.style.left = pos.x + 'px'; + this.container.style.top = pos.y + 'px'; + this.container.style.visibility = 'visible'; this.setContent(content); } @@ -36,58 +36,58 @@ class Popup { showNextTo(elementRect, content) { this.inject(); - const popupRect = this.popup.getBoundingClientRect(); + const containerRect = this.container.getBoundingClientRect(); let posX = elementRect.left; - if (posX + popupRect.width >= window.innerWidth) { - posX = window.innerWidth - popupRect.width; + if (posX + containerRect.width >= window.innerWidth) { + posX = window.innerWidth - containerRect.width; } let posY = elementRect.bottom + this.offset; - if (posY + popupRect.height >= window.innerHeight) { - posY = elementRect.top - popupRect.height - this.offset; + if (posY + containerRect.height >= window.innerHeight) { + posY = elementRect.top - containerRect.height - this.offset; } this.showAt({x: posX, y: posY}, content); } visible() { - return this.popup !== null && this.popup.style.visibility !== 'hidden'; + return this.container !== null && this.container.style.visibility !== 'hidden'; } hide() { - if (this.popup !== null) { - this.popup.style.visibility = 'hidden'; + if (this.container !== null) { + this.container.style.visibility = 'hidden'; } } setContent(content) { - if (this.popup === null) { + if (this.container === null) { return; } - const doc = this.popup.contentDocument; + const doc = this.container.contentDocument; doc.open(); doc.write(content); doc.close(); } invokeApi(action, params) { - if (this.popup !== null) { - this.popup.contentWindow.postMessage({action, params}, '*'); + if (this.container !== null) { + this.container.contentWindow.postMessage({action, params}, '*'); } } inject() { - if (this.popup !== null) { + if (this.container !== null) { return; } - this.popup = document.createElement('iframe'); - this.popup.id = 'yomichan-popup'; - this.popup.addEventListener('mousedown', e => e.stopPropagation()); - this.popup.addEventListener('scroll', e => e.stopPropagation()); + this.container = document.createElement('iframe'); + this.container.id = 'yomichan-popup'; + this.container.addEventListener('mousedown', e => e.stopPropagation()); + this.container.addEventListener('scroll', e => e.stopPropagation()); - document.body.appendChild(this.popup); + document.body.appendChild(this.container); } } |