diff options
| -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);      }  } |