diff options
-rw-r--r-- | README.md | 24 | ||||
-rw-r--r-- | ext/bg/guide.html | 2 | ||||
-rw-r--r-- | ext/bg/settings.html | 14 | ||||
-rw-r--r-- | ext/fg/js/document.js | 16 | ||||
-rw-r--r-- | ext/fg/js/frontend-api-sender.js | 14 | ||||
-rw-r--r-- | ext/fg/js/frontend.js | 85 | ||||
-rw-r--r-- | ext/fg/js/popup-proxy-host.js | 12 | ||||
-rw-r--r-- | ext/fg/js/popup-proxy.js | 4 | ||||
-rw-r--r-- | ext/fg/js/popup.js | 9 | ||||
-rw-r--r-- | ext/mixed/js/display.js | 2 |
10 files changed, 97 insertions, 85 deletions
@@ -61,7 +61,7 @@ Import](https://foosoft.net/projects/yomichan-import) page to learn how to conve Please be aware that the non-English dictionaries contain fewer entries than their English counterparts. Even if your primary language is not English, you may consider also importing the English version for better coverage. -* **[JMdict](http://www.edrdg.org/enamdict/enamdict_doc.html)** (Japanese vocabulary) +* **[JMdict](https://www.edrdg.org/enamdict/enamdict_doc.html)** (Japanese vocabulary) * [jmdict\_dutch.zip](https://foosoft.net/projects/yomichan/dl/dict/jmdict_dutch.zip) * [jmdict\_english.zip](https://foosoft.net/projects/yomichan/dl/dict/jmdict_english.zip) * [jmdict\_french.zip](https://foosoft.net/projects/yomichan/dl/dict/jmdict_french.zip) @@ -71,7 +71,7 @@ primary language is not English, you may consider also importing the English ver * [jmdict\_slovenian.zip](https://foosoft.net/projects/yomichan/dl/dict/jmdict_slovenian.zip) * [jmdict\_spanish.zip](https://foosoft.net/projects/yomichan/dl/dict/jmdict_spanish.zip) * [jmdict\_swedish.zip](https://foosoft.net/projects/yomichan/dl/dict/jmdict_swedish.zip) -* **[JMnedict](http://www.edrdg.org/enamdict/enamdict_doc.html)** (Japanese names) +* **[JMnedict](https://www.edrdg.org/enamdict/enamdict_doc.html)** (Japanese names) * [jmnedict.zip](https://foosoft.net/projects/yomichan/dl/dict/jmnedict.zip) * **[KireiCake](https://kireicake.com/rikaicakes/)** (Japanese slang) * [kireicake.zip](https://foosoft.net/projects/yomichan/dl/dict/kireicake.zip) @@ -127,7 +127,7 @@ Import](https://foosoft.net/projects/yomichan-import). Please see the project pa ## Anki Integration ## -Yomichan features automatic flashcard creation for [Anki](http://ankisrs.net/), a free application designed to help you +Yomichan features automatic flashcard creation for [Anki](https://apps.ankiweb.net/), a free application designed to help you retain knowledge. This feature requires the prior installation of an Anki plugin called [AnkiConnect](https://foosoft.net/projects/anki-connect). Please see the respective project page for more information about how to set up this software. @@ -135,7 +135,7 @@ Please see the respective project page for more information about how to set up Before flashcards can be automatically created, you must configure the templates used to create term and/or Kanji notes. If you are unfamiliar with Anki deck and model management, this would be a good time to reference the [Anki -Manual](http://ankisrs.net/docs/manual.html). In short, you must specify what information should be included in the +Manual](https://apps.ankiweb.net/docs/manual.html). In short, you must specify what information should be included in the flashcards that Yomichan creates through AnkiConnect. Flashcard fields can be configured with the following steps: @@ -145,7 +145,7 @@ Flashcard fields can be configured with the following steps: 3. Select the type of template to configure by clicking on either the *Terms* or *Kanji* tabs. 4. Select the Anki deck and model to use for new creating new flashcards of this type. 5. Fill the model fields with markers corresponding to the information you wish to include (several can be used at - once). Advanced users can also configure the actual [Handlebars](http://handlebarsjs.com/) templates used to create + once). Advanced users can also configure the actual [Handlebars](https://handlebarsjs.com/) templates used to create the flashcard contents (this is strictly optional). #### Markers for Term Cards #### @@ -243,7 +243,7 @@ following basic guidelines when creating pull requests: ### Templates ### -Yomichan uses [Handlebars](http://handlebarsjs.com/) templates for user interface generation. The source templates are +Yomichan uses [Handlebars](https://handlebarsjs.com/) templates for user interface generation. The source templates are found in the `tmpl` directory and the compiled version is stored in the `ext/bg/js/templates.js` file. If you modify the source templates, you will need to also recompile them. If you are developing on Linux or Mac OS X, you can use the included `build_tmpl.sh` and `build_tmpl_auto.sh` shell scripts to do this for you @@ -255,13 +255,13 @@ tmpl/*.html -f ext/bg/js/templates.js` from the project's base directory to comp Yomichan uses several third-party libraries to function. Below are links to homepages and snapshots of the exact versions packaged. -* Bootstrap Toggle: [homepage](http://www.bootstraptoggle.com/) - [snapshot](https://github.com/minhur/bootstrap-toggle/archive/b76c094.zip) -* Bootstrap: [homepage](http://getbootstrap.com/) - [snapshot](https://github.com/twbs/bootstrap/releases/download/v3.3.7/bootstrap-3.3.7-dist.zip) -* Dexie: [homepage](http://dexie.org/) - [snapshot](https://github.com/dfahlander/Dexie.js/archive/v2.0.0-beta.10.zip) -* Handlebars: [homepage](http://handlebarsjs.com/) - [snapshot](http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-714a4c4.js) +* Bootstrap Toggle: [homepage](https://www.bootstraptoggle.com/) - [snapshot](https://github.com/minhur/bootstrap-toggle/archive/b76c094.zip) +* Bootstrap: [homepage](https://getbootstrap.com/) - [snapshot](https://github.com/twbs/bootstrap/releases/download/v3.3.7/bootstrap-3.3.7-dist.zip) +* Dexie: [homepage](https://dexie.org/) - [snapshot](https://github.com/dfahlander/Dexie.js/archive/v2.0.0-beta.10.zip) +* Handlebars: [homepage](https://handlebarsjs.com/) - [snapshot](http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-714a4c4.js) * JQuery: [homepage](https://blog.jquery.com/) - [snapshot](https://code.jquery.com/jquery-3.2.1.min.js) -* JSZip: [homepage](http://stuk.github.io/jszip/) - [snapshot](https://raw.githubusercontent.com/Stuk/jszip/de7f52fbcba485737bef7923a83f0fad92d9f5bc/dist/jszip.min.js) -* WanaKana: [homepage](http://wanakana.com/) - [snapshot](https://raw.githubusercontent.com/WaniKani/WanaKana/7c4a052/gh-pages/assets/js/wanakana.min.js) +* JSZip: [homepage](https://stuk.github.io/jszip/) - [snapshot](https://raw.githubusercontent.com/Stuk/jszip/de7f52fbcba485737bef7923a83f0fad92d9f5bc/dist/jszip.min.js) +* WanaKana: [homepage](https://wanakana.com/) - [snapshot](https://raw.githubusercontent.com/WaniKani/WanaKana/7c4a052/gh-pages/assets/js/wanakana.min.js) ## Frequently Asked Questions ## diff --git a/ext/bg/guide.html b/ext/bg/guide.html index 6f98d264..7ec1d8d9 100644 --- a/ext/bg/guide.html +++ b/ext/bg/guide.html @@ -15,7 +15,7 @@ <p> Read the steps below to get up and running with Yomichan. For complete documentation, - visit the <a href="https://foosoft.net/projects/yomichan/" target="_blank">official homepage</a>. + visit the <a href="https://foosoft.net/projects/yomichan/" target="_blank" rel="noopener">official homepage</a>. </p> <ol> diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 577e1a1f..7df47980 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -307,7 +307,7 @@ <div id="dict-importer"> <p class="help-block"> Select a dictionary to import for use below. Please visit the Yomichan homepage to - <a href="https://foosoft.net/projects/yomichan" target="_blank">download free dictionaries</a> + <a href="https://foosoft.net/projects/yomichan" target="_blank" rel="noopener">download free dictionaries</a> for use with this extension and to learn about importing proprietary EPWING dictionaries. </p> <input type="file" id="dict-file"> @@ -333,7 +333,7 @@ <div data-show-for-browser="firefox firefox-mobile"><div class="alert alert-danger options-advanced"> On Firefox and Firefox for Android, the storage information feature may be hidden behind a browser flag. - If you would like to enable this flag, open <a href="about:config" target="_blank">about:config</a> and search for the + If you would like to enable this flag, open <a href="about:config" target="_blank" rel="noopener">about:config</a> and search for the <strong>dom.storageManager.enabled</strong> option. If this option has a value of <strong>false</strong>, toggling it to <strong>true</strong> may allow storage information to be calculated. </div></div> @@ -355,9 +355,9 @@ </div> <p class="help-block"> - Yomichan supports automatic flashcard creation for <a href="http://ankisrs.net/" target="_blank">Anki</a>, a free application + Yomichan supports automatic flashcard creation for <a href="https://apps.ankiweb.net/" target="_blank" rel="noopener">Anki</a>, a free application designed to help you remember. This feature requires installation of the - <a href="https://foosoft.net/projects/anki-connect/" target="_blank">AnkiConnect</a> plugin. + <a href="https://foosoft.net/projects/anki-connect/" target="_blank" rel="noopener">AnkiConnect</a> plugin. </p> <div class="alert alert-danger" id="anki-error"></div> @@ -450,7 +450,7 @@ <div class="options-advanced"> <p class="help-block"> - Fields are formatted using the <a href="http://handlebarsjs.com/">Handlebars.js</a> template rendering + Fields are formatted using the <a href="https://handlebarsjs.com/" target="_blank" rel="noopener">Handlebars.js</a> template rendering engine. Advanced users can modify these templates for ultimate control of what information gets included in their Anki cards. If you encounter problems with your changes you can always <a href="#" id="field-templates-reset">reset to default</a> template settings. @@ -473,14 +473,14 @@ countless hours that I have devoted to this extension. </p> <p> - <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4DBTN9A3CUAFN" target="_blank"><img src="/bg/img/paypal.gif" alt></a> + <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4DBTN9A3CUAFN" target="_blank" rel="noopener"><img src="/bg/img/paypal.gif" alt></a> </p> </div> <pre id="debug"></pre> <div class="pull-right bottom-links"> - <small><a href="search.html">Search</a> • <a href="https://foosoft.net/projects/yomichan/" target="_blank">Homepage</a> • <a href="legal.html">Legal</a></small> + <small><a href="search.html">Search</a> • <a href="https://foosoft.net/projects/yomichan/" target="_blank" rel="noopener">Homepage</a> • <a href="legal.html">Legal</a></small> </div> </div> diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js index 60b1b9bd..079a5034 100644 --- a/ext/fg/js/document.js +++ b/ext/fg/js/document.js @@ -89,8 +89,18 @@ function docImposterCreate(element, isTextarea) { return [imposter, container]; } -function docRangeFromPoint({x, y}, options) { - const elements = document.elementsFromPoint(x, y); +function docElementsFromPoint(x, y, all) { + if (all) { + return document.elementsFromPoint(x, y); + } + + const e = document.elementFromPoint(x, y); + return e !== null ? [e] : []; +} + +function docRangeFromPoint(x, y, options) { + const deepDomScan = options.scanning.deepDomScan; + const elements = docElementsFromPoint(x, y, deepDomScan); let imposter = null; let imposterContainer = null; if (elements.length > 0) { @@ -108,7 +118,7 @@ function docRangeFromPoint({x, y}, options) { } } - const range = caretRangeFromPointExt(x, y, options.scanning.deepDomScan ? elements : []); + const range = caretRangeFromPointExt(x, y, deepDomScan ? elements : []); if (range !== null) { if (imposter !== null) { docSetImposterStyle(imposterContainer.style, 'z-index', '-2147483646'); diff --git a/ext/fg/js/frontend-api-sender.js b/ext/fg/js/frontend-api-sender.js index a1cb02c4..2e037e62 100644 --- a/ext/fg/js/frontend-api-sender.js +++ b/ext/fg/js/frontend-api-sender.js @@ -26,9 +26,7 @@ class FrontendApiSender { this.disconnected = false; this.nextId = 0; - this.port = chrome.runtime.connect(null, {name: 'backend-api-forwarder'}); - this.port.onDisconnect.addListener(this.onDisconnect.bind(this)); - this.port.onMessage.addListener(this.onMessage.bind(this)); + this.port = null; } invoke(action, params, target) { @@ -36,6 +34,10 @@ class FrontendApiSender { return Promise.reject('Disconnected'); } + if (this.port === null) { + this.createPort(); + } + const id = `${this.nextId}`; ++this.nextId; @@ -48,6 +50,12 @@ class FrontendApiSender { }); } + createPort() { + this.port = chrome.runtime.connect(null, {name: 'backend-api-forwarder'}); + this.port.onDisconnect.addListener(this.onDisconnect.bind(this)); + this.port.onMessage.addListener(this.onMessage.bind(this)); + } + onMessage({type, id, data, senderId}) { if (senderId !== this.senderId) { return; } switch (type) { diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 0b60aa2b..c98a9a33 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -21,8 +21,6 @@ class Frontend { constructor(popup, ignoreNodes) { this.popup = popup; this.popupTimer = null; - this.mouseDownLeft = false; - this.mouseDownMiddle = false; this.textSourceLast = null; this.pendingLookup = false; this.options = null; @@ -61,7 +59,6 @@ class Frontend { window.addEventListener('mousemove', this.onMouseMove.bind(this)); window.addEventListener('mouseover', this.onMouseOver.bind(this)); window.addEventListener('mouseout', this.onMouseOut.bind(this)); - window.addEventListener('mouseup', this.onMouseUp.bind(this)); window.addEventListener('resize', this.onResize.bind(this)); if (this.options.scanning.touchInputEnabled) { @@ -80,7 +77,7 @@ class Frontend { } onMouseOver(e) { - if (e.target === this.popup.container && this.popupTimer) { + if (e.target === this.popup.container && this.popupTimer !== null) { this.popupTimerClear(); } } @@ -88,38 +85,32 @@ class Frontend { onMouseMove(e) { this.popupTimerClear(); - if (!this.options.general.enable) { - return; - } - - if (this.mouseDownLeft) { - return; - } - - if (this.pendingLookup) { + if ( + this.pendingLookup || + !this.options.general.enable || + (e.buttons & 0x1) !== 0x0 // Left mouse button + ) { return; } - const mouseScan = this.mouseDownMiddle && this.options.scanning.middleMouse; - const keyScan = - this.options.scanning.modifier === 'alt' && e.altKey || - this.options.scanning.modifier === 'ctrl' && e.ctrlKey || - this.options.scanning.modifier === 'shift' && e.shiftKey || - this.options.scanning.modifier === 'none'; - - if (!keyScan && !mouseScan) { + const scanningOptions = this.options.scanning; + const scanningModifier = scanningOptions.modifier; + if (!( + Frontend.isScanningModifierPressed(scanningModifier, e) || + (scanningOptions.middleMouse && (e.buttons & 0x4) !== 0x0) // Middle mouse button + )) { return; } const search = async () => { try { - await this.searchAt({x: e.clientX, y: e.clientY}, 'mouse'); + await this.searchAt(e.clientX, e.clientY, 'mouse'); } catch (e) { this.onError(e); } }; - if (this.options.scanning.modifier === 'none') { + if (scanningModifier === 'none') { this.popupTimerSet(search); } else { search(); @@ -135,23 +126,8 @@ class Frontend { return false; } - this.mousePosLast = {x: e.clientX, y: e.clientY}; this.popupTimerClear(); this.searchClear(); - - if (e.which === 1) { - this.mouseDownLeft = true; - } else if (e.which === 2) { - this.mouseDownMiddle = true; - } - } - - onMouseUp(e) { - if (e.which === 1) { - this.mouseDownLeft = false; - } else if (e.which === 2) { - this.mouseDownMiddle = false; - } } onMouseOut(e) { @@ -243,8 +219,8 @@ class Frontend { } } - onAfterSearch(newRange, type, searched, success) { - if (type === 'mouse') { + onAfterSearch(newRange, cause, searched, success) { + if (cause === 'mouse') { return; } @@ -254,7 +230,7 @@ class Frontend { return; } - if (type === 'touchStart' && newRange !== null) { + if (cause === 'touchStart' && newRange !== null) { this.scrollPrevent = true; } @@ -293,23 +269,22 @@ class Frontend { } popupTimerSet(callback) { - this.popupTimerClear(); this.popupTimer = window.setTimeout(callback, this.options.scanning.delay); } popupTimerClear() { - if (this.popupTimer) { + if (this.popupTimer !== null) { window.clearTimeout(this.popupTimer); this.popupTimer = null; } } - async searchAt(point, type) { - if (this.pendingLookup || await this.popup.containsPoint(point)) { + async searchAt(x, y, cause) { + if (this.pendingLookup || await this.popup.containsPoint(x, y)) { return; } - const textSource = docRangeFromPoint(point, this.options); + const textSource = docRangeFromPoint(x, y, this.options); let hideResults = textSource === null; let searched = false; let success = false; @@ -318,7 +293,7 @@ class Frontend { if (!hideResults && (!this.textSourceLast || !this.textSourceLast.equals(textSource))) { searched = true; this.pendingLookup = true; - const focus = (type === 'mouse'); + const focus = (cause === 'mouse'); hideResults = !await this.searchTerms(textSource, focus) && !await this.searchKanji(textSource, focus); success = true; } @@ -343,7 +318,7 @@ class Frontend { } this.pendingLookup = false; - this.onAfterSearch(this.textSourceLast, type, searched, success); + this.onAfterSearch(this.textSourceLast, cause, searched, success); } } @@ -485,7 +460,7 @@ class Frontend { this.clickPrevent = value; } - searchFromTouch(x, y, type) { + searchFromTouch(x, y, cause) { this.popupTimerClear(); if (!this.options.general.enable || this.pendingLookup) { @@ -494,7 +469,7 @@ class Frontend { const search = async () => { try { - await this.searchAt({x, y}, type); + await this.searchAt(x, y, cause); } catch (e) { this.onError(e); } @@ -531,6 +506,16 @@ class Frontend { textSource.setEndOffset(length); } } + + static isScanningModifierPressed(scanningModifier, mouseEvent) { + switch (scanningModifier) { + case 'alt': return mouseEvent.altKey; + case 'ctrl': return mouseEvent.ctrlKey; + case 'shift': return mouseEvent.shiftKey; + case 'none': return true; + default: return false; + } + } } window.yomichan_frontend = Frontend.create(); diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 041900ed..396f7556 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -42,7 +42,7 @@ class PopupProxyHost { showOrphaned: ({id, elementRect, options}) => this.show(id, elementRect, options), hide: ({id}) => this.hide(id), setVisible: ({id, visible}) => this.setVisible(id, visible), - containsPoint: ({id, point}) => this.containsPoint(id, point), + containsPoint: ({id, x, y}) => this.containsPoint(id, x, y), termsShow: ({id, elementRect, writingMode, definitions, options, context}) => this.termsShow(id, elementRect, writingMode, definitions, options, context), kanjiShow: ({id, elementRect, writingMode, definitions, options, context}) => this.kanjiShow(id, elementRect, writingMode, definitions, options, context), clearAutoPlayTimer: ({id}) => this.clearAutoPlayTimer(id) @@ -108,20 +108,22 @@ class PopupProxyHost { return popup.setVisible(visible); } - async containsPoint(id, point) { + async containsPoint(id, x, y) { const popup = this.getPopup(id); - return await popup.containsPoint(point); + return await popup.containsPoint(x, y); } async termsShow(id, elementRect, writingMode, definitions, options, context) { const popup = this.getPopup(id); elementRect = this.jsonRectToDOMRect(popup, elementRect); + if (!PopupProxyHost.popupCanShow(popup)) { return false; } return await popup.termsShow(elementRect, writingMode, definitions, options, context); } async kanjiShow(id, elementRect, writingMode, definitions, options, context) { const popup = this.getPopup(id); elementRect = this.jsonRectToDOMRect(popup, elementRect); + if (!PopupProxyHost.popupCanShow(popup)) { return false; } return await popup.kanjiShow(elementRect, writingMode, definitions, options, context); } @@ -129,6 +131,10 @@ class PopupProxyHost { const popup = this.getPopup(id); return popup.clearAutoPlayTimer(); } + + static popupCanShow(popup) { + return popup.parent === null || popup.parent.isVisible(); + } } PopupProxyHost.instance = PopupProxyHost.create(); diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index c3a7bff0..f04e24e0 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -69,11 +69,11 @@ class PopupProxy { return await this.invokeHostApi('setVisible', {id, visible}); } - async containsPoint(point) { + async containsPoint(x, y) { if (this.id === null) { return false; } - return await this.invokeHostApi('containsPoint', {id: this.id, point}); + return await this.invokeHostApi('containsPoint', {id: this.id, x, y}); } async termsShow(elementRect, writingMode, definitions, options, context) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 1b15977b..8953cf30 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -239,9 +239,12 @@ class Popup { } focusParent() { - if (this.parent && this.parent.container) { + if (this.parent !== null) { // Chrome doesn't like focusing iframe without contentWindow. - this.parent.container.contentWindow.focus(); + const contentWindow = this.parent.container.contentWindow; + if (contentWindow !== null) { + contentWindow.focus(); + } } else { // Firefox doesn't like focusing window without first blurring the iframe. // this.container.contentWindow.blur() doesn't work on Firefox for some reason. @@ -251,7 +254,7 @@ class Popup { } } - async containsPoint({x, y}) { + async containsPoint(x, y) { for (let popup = this; popup !== null && popup.isVisible(); popup = popup.child) { const rect = popup.container.getBoundingClientRect(); if (x >= rect.left && y >= rect.top && x < rect.right && y < rect.bottom) { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index eca67b5e..ca1738a6 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -81,7 +81,7 @@ class Display { const {docRangeFromPoint, docSentenceExtract} = this.dependencies; const clickedElement = $(e.target); - const textSource = docRangeFromPoint({x: e.clientX, y: e.clientY}, this.options); + const textSource = docRangeFromPoint(e.clientX, e.clientY, this.options); if (textSource === null) { return false; } |