From 6183864f164a44bcb24e98ad2150ac9aafe371d3 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 11 Sep 2016 19:47:40 -0700
Subject: Cleanup

---
 ext/fg/js/client.js | 38 ++++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 14 deletions(-)

(limited to 'ext/fg/js')

diff --git a/ext/fg/js/client.js b/ext/fg/js/client.js
index 628d5b30..ebfb1090 100644
--- a/ext/fg/js/client.js
+++ b/ext/fg/js/client.js
@@ -23,6 +23,7 @@ class Client {
         this.audio = {};
         this.lastMousePos = null;
         this.lastTextSource = null;
+        this.pendingLookup = false;
         this.activateKey = 16;
         this.activateBtn = 2;
         this.enabled = false;
@@ -36,8 +37,8 @@ class Client {
         window.addEventListener('mousedown', this.onMouseDown.bind(this));
         window.addEventListener('mousemove', this.onMouseMove.bind(this));
         window.addEventListener('keydown', this.onKeyDown.bind(this));
-        window.addEventListener('scroll', (e) => this.hidePopup());
-        window.addEventListener('resize', (e) => this.hidePopup());
+        window.addEventListener('scroll', e => this.hidePopup());
+        window.addEventListener('resize', e => this.hidePopup());
     }
 
     onKeyDown(e) {
@@ -89,11 +90,16 @@ class Client {
             return;
         }
 
+        if (this.pendingLookup) {
+            return;
+        }
+
         textSource.setEndOffset(this.options.scanLength);
 
         let defs = [];
         let seq = -1;
 
+        this.pendingLookup = true;
         bgFindTerm(textSource.text())
             .then(({definitions, length}) => {
                 if (length === 0) {
@@ -103,7 +109,7 @@ class Client {
                 textSource.setEndOffset(length);
 
                 const sentence = Client.extractSentence(textSource, this.options.sentenceExtent);
-                definitions.forEach((definition) => {
+                definitions.forEach(definition => {
                     definition.url = window.location.href;
                     definition.sentence = sentence;
                 });
@@ -113,17 +119,21 @@ class Client {
 
                 return bgRenderText({definitions, root: this.fgRoot, options: this.options, sequence: seq}, 'term-list.html');
             })
-            .then((content) => {
+            .then(content => {
                 this.definitions = defs;
                 this.showPopup(textSource, content);
 
                 return bgCanAddDefinitions(defs, ['term_kanji', 'term_kana']);
             })
-            .then((states) => {
+            .then(states => {
+                this.pendingLookup = false;
                 if (states !== null) {
                     states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence: seq}));
                 }
-            }, () => this.hidePopup());
+            }, () => {
+                this.pendingLookup = false;
+                this.hidePopup();
+            });
     }
 
     showPopup(textSource, content) {
@@ -159,7 +169,7 @@ class Client {
 
     api_addNote({index, mode}) {
         const state = {[mode]: false};
-        bgAddDefinition(this.definitions[index], mode).then((success) => {
+        bgAddDefinition(this.definitions[index], mode).then(success => {
             if (success) {
                 this.popup.sendMessage('setActionState', {index, state, sequence: this.sequence});
             } else {
@@ -192,21 +202,21 @@ class Client {
         let seq = -1;
 
         bgFindKanji(kanji)
-            .then((definitions) => {
-                definitions.forEach((definition) => definition.url = window.location.href);
+            .then(definitions => {
+                definitions.forEach(definition => definition.url = window.location.href);
 
                 defs = definitions;
                 seq = ++this.sequence;
 
                 return bgRenderText({definitions, root: this.fgRoot, options: this.options, sequence: seq}, 'kanji-list.html');
             })
-            .then((content) => {
+            .then(content => {
                 this.definitions = defs;
                 this.popup.setContent(content, defs);
 
                 return bgCanAddDefinitions(defs, ['kanji']);
             })
-            .then((states) => {
+            .then(states => {
                 if (states !== null) {
                     states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence: seq}));
                 }
@@ -217,7 +227,7 @@ class Client {
         const element = document.elementFromPoint(point.x, point.y);
         if (element !== null) {
             const names = ['IMG', 'INPUT', 'BUTTON', 'TEXTAREA'];
-            if (names.indexOf(element.nodeName) !== -1) {
+            if (names.includes(element.nodeName)) {
                 return new TextSourceElement(element);
             }
         }
@@ -246,7 +256,7 @@ class Client {
         for (let i = position; i >= startPos; --i) {
             const c = content[i];
 
-            if (quoteStack.length === 0 && (terminators.indexOf(c) !== -1 || c in quotesFwd)) {
+            if (quoteStack.length === 0 && (terminators.includes(c) || c in quotesFwd)) {
                 startPos = i + 1;
                 break;
             }
@@ -265,7 +275,7 @@ class Client {
             const c = content[i];
 
             if (quoteStack.length === 0) {
-                if (terminators.indexOf(c) !== -1) {
+                if (terminators.includes(c)) {
                     endPos = i + 1;
                     break;
                 }
-- 
cgit v1.2.3


From 8f776cf759c0572904afbed5fc168883e8d3324f Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 11 Sep 2016 20:18:34 -0700
Subject: Cleanup

---
 ext/fg/js/client.js | 92 +++++++++++++++++++++--------------------------------
 1 file changed, 37 insertions(+), 55 deletions(-)

(limited to 'ext/fg/js')

diff --git a/ext/fg/js/client.js b/ext/fg/js/client.js
index ebfb1090..8f9fdc08 100644
--- a/ext/fg/js/client.js
+++ b/ext/fg/js/client.js
@@ -24,8 +24,6 @@ class Client {
         this.lastMousePos = null;
         this.lastTextSource = null;
         this.pendingLookup = false;
-        this.activateKey = 16;
-        this.activateBtn = 2;
         this.enabled = false;
         this.options = {};
         this.definitions = null;
@@ -42,21 +40,21 @@ class Client {
     }
 
     onKeyDown(e) {
-        if (this.enabled && this.lastMousePos !== null && (e.keyCode === this.activateKey || e.charCode === this.activateKey)) {
+        if (this.enabled && this.lastMousePos !== null && (e.keyCode === 16 || e.charCode === 16)) {
             this.searchAt(this.lastMousePos);
         }
     }
 
     onMouseMove(e) {
         this.lastMousePos = {x: e.clientX, y: e.clientY};
-        if (this.enabled && (e.shiftKey || e.which === this.activateBtn)) {
+        if (this.enabled && (e.shiftKey || e.which === 2)) {
             this.searchAt(this.lastMousePos);
         }
     }
 
     onMouseDown(e) {
         this.lastMousePos = {x: e.clientX, y: e.clientY};
-        if (this.enabled && (e.shiftKey || e.which === this.activateBtn)) {
+        if (this.enabled && (e.shiftKey || e.which === 2)) {
             this.searchAt(this.lastMousePos);
         } else {
             this.hidePopup();
@@ -80,6 +78,10 @@ class Client {
     }
 
     searchAt(point) {
+        if (this.pendingLookup) {
+            return;
+        }
+
         const textSource = Client.textSourceFromPoint(point);
         if (textSource === null || !textSource.containsPoint(point)) {
             this.hidePopup();
@@ -90,22 +92,13 @@ class Client {
             return;
         }
 
-        if (this.pendingLookup) {
-            return;
-        }
-
         textSource.setEndOffset(this.options.scanLength);
 
-        let defs = [];
-        let seq = -1;
-
         this.pendingLookup = true;
-        bgFindTerm(textSource.text())
-            .then(({definitions, length}) => {
-                if (length === 0) {
-                    return Promise.reject();
-                }
-
+        bgFindTerm(textSource.text()).then(({definitions, length}) => {
+            if (length === 0) {
+                this.hidePopup();
+            } else {
                 textSource.setEndOffset(length);
 
                 const sentence = Client.extractSentence(textSource, this.options.sentenceExtent);
@@ -114,26 +107,18 @@ class Client {
                     definition.sentence = sentence;
                 });
 
-                defs = definitions;
-                seq = ++this.sequence;
-
-                return bgRenderText({definitions, root: this.fgRoot, options: this.options, sequence: seq}, 'term-list.html');
-            })
-            .then(content => {
-                this.definitions = defs;
-                this.showPopup(textSource, content);
-
-                return bgCanAddDefinitions(defs, ['term_kanji', 'term_kana']);
-            })
-            .then(states => {
-                this.pendingLookup = false;
-                if (states !== null) {
-                    states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence: seq}));
-                }
-            }, () => {
-                this.pendingLookup = false;
-                this.hidePopup();
-            });
+                const sequence = ++this.sequence;
+                return bgRenderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'term-list.html').then((content) => {
+                    this.definitions = definitions;
+                    this.showPopup(textSource, content);
+                    return bgCanAddDefinitions(definitions, ['term_kanji', 'term_kana']);
+                }).then(states => {
+                    if (states !== null) {
+                        states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence }));
+                    }
+                });
+            }
+        }).then(() => this.pendingLookup = false);
     }
 
     showPopup(textSource, content) {
@@ -201,26 +186,23 @@ class Client {
         let defs = [];
         let seq = -1;
 
-        bgFindKanji(kanji)
-            .then(definitions => {
-                definitions.forEach(definition => definition.url = window.location.href);
+        bgFindKanji(kanji).then(definitions => {
+            definitions.forEach(definition => definition.url = window.location.href);
 
-                defs = definitions;
-                seq = ++this.sequence;
+            defs = definitions;
+            seq = ++this.sequence;
 
-                return bgRenderText({definitions, root: this.fgRoot, options: this.options, sequence: seq}, 'kanji-list.html');
-            })
-            .then(content => {
-                this.definitions = defs;
-                this.popup.setContent(content, defs);
+            return bgRenderText({definitions, root: this.fgRoot, options: this.options, sequence: seq}, 'kanji-list.html');
+        }).then(content => {
+            this.definitions = defs;
+            this.popup.setContent(content, defs);
 
-                return bgCanAddDefinitions(defs, ['kanji']);
-            })
-            .then(states => {
-                if (states !== null) {
-                    states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence: seq}));
-                }
-            });
+            return bgCanAddDefinitions(defs, ['kanji']);
+        }).then(states => {
+            if (states !== null) {
+                states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence: seq}));
+            }
+        });
     }
 
     static textSourceFromPoint(point) {
-- 
cgit v1.2.3


From f653195aed4fe1ce12b771cb1e5de06d9e6f0253 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 11 Sep 2016 20:35:53 -0700
Subject: Cleanup

---
 ext/fg/js/client.js | 26 ++++++++++----------------
 1 file changed, 10 insertions(+), 16 deletions(-)

(limited to 'ext/fg/js')

diff --git a/ext/fg/js/client.js b/ext/fg/js/client.js
index 8f9fdc08..266e4b5a 100644
--- a/ext/fg/js/client.js
+++ b/ext/fg/js/client.js
@@ -183,25 +183,19 @@ class Client {
     }
 
     api_displayKanji(kanji) {
-        let defs = [];
-        let seq = -1;
-
         bgFindKanji(kanji).then(definitions => {
             definitions.forEach(definition => definition.url = window.location.href);
 
-            defs = definitions;
-            seq = ++this.sequence;
-
-            return bgRenderText({definitions, root: this.fgRoot, options: this.options, sequence: seq}, 'kanji-list.html');
-        }).then(content => {
-            this.definitions = defs;
-            this.popup.setContent(content, defs);
-
-            return bgCanAddDefinitions(defs, ['kanji']);
-        }).then(states => {
-            if (states !== null) {
-                states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence: seq}));
-            }
+            const sequence = ++this.sequence;
+            return bgRenderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'kanji-list.html').then(content => {
+                this.definitions = definitions;
+                this.popup.setContent(content, definitions);
+                return bgCanAddDefinitions(definitions, ['kanji']);
+            }).then(states => {
+                if (states !== null) {
+                    states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence}));
+                }
+            });
         });
     }
 
-- 
cgit v1.2.3


From fa446f540a3a93d51c457da661d61976bdd5cbdf Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 11 Sep 2016 20:41:41 -0700
Subject: WIP

---
 ext/fg/js/client.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

(limited to 'ext/fg/js')

diff --git a/ext/fg/js/client.js b/ext/fg/js/client.js
index 266e4b5a..9a0ad25a 100644
--- a/ext/fg/js/client.js
+++ b/ext/fg/js/client.js
@@ -97,6 +97,7 @@ class Client {
         this.pendingLookup = true;
         bgFindTerm(textSource.text()).then(({definitions, length}) => {
             if (length === 0) {
+                this.pendingLookup = false;
                 this.hidePopup();
             } else {
                 textSource.setEndOffset(length);
@@ -108,8 +109,9 @@ class Client {
                 });
 
                 const sequence = ++this.sequence;
-                return bgRenderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'term-list.html').then((content) => {
+                return bgRenderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'term-list.html').then(content => {
                     this.definitions = definitions;
+                    this.pendingLookup = false;
                     this.showPopup(textSource, content);
                     return bgCanAddDefinitions(definitions, ['term_kanji', 'term_kana']);
                 }).then(states => {
@@ -118,7 +120,7 @@ class Client {
                     }
                 });
             }
-        }).then(() => this.pendingLookup = false);
+        });
     }
 
     showPopup(textSource, content) {
-- 
cgit v1.2.3


From f4314497e401e94ccd2ff88358b5720c73074612 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 11 Sep 2016 20:59:42 -0700
Subject: Cleanup

---
 ext/fg/js/api.js    |  42 -------------------
 ext/fg/js/client.js |  90 +++++------------------------------------
 ext/fg/js/frame.js  |   6 +--
 ext/fg/js/popup.js  |   4 +-
 ext/fg/js/util.js   | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 ext/manifest.json   |   2 +-
 6 files changed, 129 insertions(+), 129 deletions(-)
 delete mode 100644 ext/fg/js/api.js
 create mode 100644 ext/fg/js/util.js

(limited to 'ext/fg/js')

diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js
deleted file mode 100644
index 643d0360..00000000
--- a/ext/fg/js/api.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
- * Author: Alex Yatskov <alex@foosoft.net>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-
-function bgSendMessage(action, params) {
-    return new Promise((resolve, reject) => chrome.runtime.sendMessage({action, params}, resolve));
-}
-
-function bgFindTerm(text) {
-    return bgSendMessage('findTerm', {text});
-}
-
-function bgFindKanji(text) {
-    return bgSendMessage('findKanji', {text});
-}
-
-function bgRenderText(data, template) {
-    return bgSendMessage('renderText', {data, template});
-}
-
-function bgCanAddDefinitions(definitions, modes) {
-    return bgSendMessage('canAddDefinitions', {definitions, modes});
-}
-
-function bgAddDefinition(definition, mode) {
-    return bgSendMessage('addDefinition', {definition, mode});
-}
diff --git a/ext/fg/js/client.js b/ext/fg/js/client.js
index 9a0ad25a..daeabf99 100644
--- a/ext/fg/js/client.js
+++ b/ext/fg/js/client.js
@@ -82,7 +82,7 @@ class Client {
             return;
         }
 
-        const textSource = Client.textSourceFromPoint(point);
+        const textSource = textSourceFromPoint(point);
         if (textSource === null || !textSource.containsPoint(point)) {
             this.hidePopup();
             return;
@@ -95,25 +95,25 @@ class Client {
         textSource.setEndOffset(this.options.scanLength);
 
         this.pendingLookup = true;
-        bgFindTerm(textSource.text()).then(({definitions, length}) => {
+        findTerm(textSource.text()).then(({definitions, length}) => {
             if (length === 0) {
                 this.pendingLookup = false;
                 this.hidePopup();
             } else {
                 textSource.setEndOffset(length);
 
-                const sentence = Client.extractSentence(textSource, this.options.sentenceExtent);
+                const sentence = extractSentence(textSource, this.options.sentenceExtent);
                 definitions.forEach(definition => {
                     definition.url = window.location.href;
                     definition.sentence = sentence;
                 });
 
                 const sequence = ++this.sequence;
-                return bgRenderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'term-list.html').then(content => {
+                return renderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'term-list.html').then(content => {
                     this.definitions = definitions;
                     this.pendingLookup = false;
                     this.showPopup(textSource, content);
-                    return bgCanAddDefinitions(definitions, ['term_kanji', 'term_kana']);
+                    return canAddDefinitions(definitions, ['term_kanji', 'term_kana']);
                 }).then(states => {
                     if (states !== null) {
                         states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence }));
@@ -156,7 +156,7 @@ class Client {
 
     api_addNote({index, mode}) {
         const state = {[mode]: false};
-        bgAddDefinition(this.definitions[index], mode).then(success => {
+        addDefinition(this.definitions[index], mode).then(success => {
             if (success) {
                 this.popup.sendMessage('setActionState', {index, state, sequence: this.sequence});
             } else {
@@ -185,14 +185,14 @@ class Client {
     }
 
     api_displayKanji(kanji) {
-        bgFindKanji(kanji).then(definitions => {
+        findKanji(kanji).then(definitions => {
             definitions.forEach(definition => definition.url = window.location.href);
 
             const sequence = ++this.sequence;
-            return bgRenderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'kanji-list.html').then(content => {
+            return renderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'kanji-list.html').then(content => {
                 this.definitions = definitions;
                 this.popup.setContent(content, definitions);
-                return bgCanAddDefinitions(definitions, ['kanji']);
+                return canAddDefinitions(definitions, ['kanji']);
             }).then(states => {
                 if (states !== null) {
                     states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence}));
@@ -200,78 +200,6 @@ class Client {
             });
         });
     }
-
-    static textSourceFromPoint(point) {
-        const element = document.elementFromPoint(point.x, point.y);
-        if (element !== null) {
-            const names = ['IMG', 'INPUT', 'BUTTON', 'TEXTAREA'];
-            if (names.includes(element.nodeName)) {
-                return new TextSourceElement(element);
-            }
-        }
-
-        const range = document.caretRangeFromPoint(point.x, point.y);
-        if (range !== null) {
-            return new TextSourceRange(range);
-        }
-
-        return null;
-    }
-
-    static extractSentence(source, extent) {
-        const quotesFwd = {'「': '」', '『': '』', "'": "'", '"': '"'};
-        const quotesBwd = {'」': '「', '』': '『', "'": "'", '"': '"'};
-        const terminators = '…。..??!!';
-
-        const sourceLocal = source.clone();
-        const position = sourceLocal.setStartOffset(extent);
-        sourceLocal.setEndOffset(position + extent);
-        const content = sourceLocal.text();
-
-        let quoteStack = [];
-
-        let startPos = 0;
-        for (let i = position; i >= startPos; --i) {
-            const c = content[i];
-
-            if (quoteStack.length === 0 && (terminators.includes(c) || c in quotesFwd)) {
-                startPos = i + 1;
-                break;
-            }
-
-            if (quoteStack.length > 0 && c === quoteStack[0]) {
-                quoteStack.pop();
-            } else if (c in quotesBwd) {
-                quoteStack = [quotesBwd[c]].concat(quoteStack);
-            }
-        }
-
-        quoteStack = [];
-
-        let endPos = content.length;
-        for (let i = position; i < endPos; ++i) {
-            const c = content[i];
-
-            if (quoteStack.length === 0) {
-                if (terminators.includes(c)) {
-                    endPos = i + 1;
-                    break;
-                }
-                else if (c in quotesBwd) {
-                    endPos = i;
-                    break;
-                }
-            }
-
-            if (quoteStack.length > 0 && c === quoteStack[0]) {
-                quoteStack.pop();
-            } else if (c in quotesFwd) {
-                quoteStack = [quotesFwd[c]].concat(quoteStack);
-            }
-        }
-
-        return content.substring(startPos, endPos).trim();
-    }
 }
 
 window.yomiClient = new Client();
diff --git a/ext/fg/js/frame.js b/ext/fg/js/frame.js
index 8f3d3877..30cb7c73 100644
--- a/ext/fg/js/frame.js
+++ b/ext/fg/js/frame.js
@@ -19,7 +19,7 @@
 
 function registerKanjiLinks() {
     for (const link of Array.from(document.getElementsByClassName('kanji-link'))) {
-        link.addEventListener('click', (e) => {
+        link.addEventListener('click', e => {
             e.preventDefault();
             window.parent.postMessage({action: 'displayKanji', params: e.target.innerHTML}, '*');
         });
@@ -28,7 +28,7 @@ function registerKanjiLinks() {
 
 function registerAddNoteLinks() {
     for (const link of Array.from(document.getElementsByClassName('action-add-note'))) {
-        link.addEventListener('click', (e) => {
+        link.addEventListener('click', e => {
             e.preventDefault();
             const ds = e.currentTarget.dataset;
             window.parent.postMessage({action: 'addNote', params: {index: ds.index, mode: ds.mode}}, '*');
@@ -38,7 +38,7 @@ function registerAddNoteLinks() {
 
 function registerAudioLinks() {
     for (const link of Array.from(document.getElementsByClassName('action-play-audio'))) {
-        link.addEventListener('click', (e) => {
+        link.addEventListener('click', e => {
             e.preventDefault();
             const ds = e.currentTarget.dataset;
             window.parent.postMessage({action: 'playAudio', params: ds.index}, '*');
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index a0eb725c..930df18d 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -81,8 +81,8 @@ class Popup {
 
         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.popup.addEventListener('mousedown', e => e.stopPropagation());
+        this.popup.addEventListener('scroll', e => e.stopPropagation());
 
         document.body.appendChild(this.popup);
     }
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
new file mode 100644
index 00000000..176765fc
--- /dev/null
+++ b/ext/fg/js/util.js
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Author: Alex Yatskov <alex@foosoft.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+function sendMessage(action, params) {
+    return new Promise((resolve, reject) => chrome.runtime.sendMessage({action, params}, resolve));
+}
+
+function findTerm(text) {
+    return sendMessage('findTerm', {text});
+}
+
+function findKanji(text) {
+    return sendMessage('findKanji', {text});
+}
+
+function renderText(data, template) {
+    return sendMessage('renderText', {data, template});
+}
+
+function canAddDefinitions(definitions, modes) {
+    return sendMessage('canAddDefinitions', {definitions, modes});
+}
+
+function addDefinition(definition, mode) {
+    return sendMessage('addDefinition', {definition, mode});
+}
+
+function textSourceFromPoint(point) {
+    const element = document.elementFromPoint(point.x, point.y);
+    if (element !== null) {
+        const names = ['IMG', 'INPUT', 'BUTTON', 'TEXTAREA'];
+        if (names.includes(element.nodeName)) {
+            return new TextSourceElement(element);
+        }
+    }
+
+    const range = document.caretRangeFromPoint(point.x, point.y);
+    if (range !== null) {
+        return new TextSourceRange(range);
+    }
+
+    return null;
+}
+
+function extractSentence(source, extent) {
+    const quotesFwd = {'「': '」', '『': '』', "'": "'", '"': '"'};
+    const quotesBwd = {'」': '「', '』': '『', "'": "'", '"': '"'};
+    const terminators = '…。..??!!';
+
+    const sourceLocal = source.clone();
+    const position = sourceLocal.setStartOffset(extent);
+    sourceLocal.setEndOffset(position + extent);
+    const content = sourceLocal.text();
+
+    let quoteStack = [];
+
+    let startPos = 0;
+    for (let i = position; i >= startPos; --i) {
+        const c = content[i];
+
+        if (quoteStack.length === 0 && (terminators.includes(c) || c in quotesFwd)) {
+            startPos = i + 1;
+            break;
+        }
+
+        if (quoteStack.length > 0 && c === quoteStack[0]) {
+            quoteStack.pop();
+        } else if (c in quotesBwd) {
+            quoteStack = [quotesBwd[c]].concat(quoteStack);
+        }
+    }
+
+    quoteStack = [];
+
+    let endPos = content.length;
+    for (let i = position; i < endPos; ++i) {
+        const c = content[i];
+
+        if (quoteStack.length === 0) {
+            if (terminators.includes(c)) {
+                endPos = i + 1;
+                break;
+            }
+            else if (c in quotesBwd) {
+                endPos = i;
+                break;
+            }
+        }
+
+        if (quoteStack.length > 0 && c === quoteStack[0]) {
+            quoteStack.pop();
+        } else if (c in quotesFwd) {
+            quoteStack = [quotesFwd[c]].concat(quoteStack);
+        }
+    }
+
+    return content.substring(startPos, endPos).trim();
+}
diff --git a/ext/manifest.json b/ext/manifest.json
index 080f6f4f..c3864e10 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -15,7 +15,7 @@
             "fg/js/source-range.js",
             "fg/js/source-element.js",
             "fg/js/popup.js",
-            "fg/js/api.js",
+            "fg/js/util.js",
             "fg/js/client.js"
         ],
         "css": ["fg/css/client.css"]
-- 
cgit v1.2.3


From 0eb54e24c6c290d8aa2df1e5f6c52ae35e35ca1a Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Wed, 14 Sep 2016 22:34:05 -0700
Subject: Deleting dead options

---
 ext/bg/js/options-form.js | 18 ++++++++----------
 ext/bg/js/options.js      |  1 -
 ext/bg/options.html       | 19 +++++--------------
 ext/fg/js/frame.js        | 27 ++++++++++++---------------
 4 files changed, 25 insertions(+), 40 deletions(-)

(limited to 'ext/fg/js')

diff --git a/ext/bg/js/options-form.js b/ext/bg/js/options-form.js
index a1ecccda..f7d4279f 100644
--- a/ext/bg/js/options-form.js
+++ b/ext/bg/js/options-form.js
@@ -68,7 +68,6 @@ function formToOptions(section) {
             case 'general':
                 optsNew.scanLength = parseInt($('#scan-length').val(), 10);
                 optsNew.activateOnStartup = $('#activate-on-startup').prop('checked');
-                optsNew.loadEnamDict = $('#load-enamdict').prop('checked');
                 optsNew.selectMatchedText = $('#select-matched-text').prop('checked');
                 optsNew.showAdvancedOptions = $('#show-advanced-options').prop('checked');
                 optsNew.enableAudioPlayback = $('#enable-audio-playback').prop('checked');
@@ -98,9 +97,9 @@ function populateAnkiDeckAndModel(opts) {
 
     const ankiDeck = $('.anki-deck');
     ankiDeck.find('option').remove();
-    yomi.api_getDeckNames({callback: (names) => {
+    yomi.api_getDeckNames({callback: names => {
         if (names !== null) {
-            names.forEach((name) => ankiDeck.append($('<option/>', {value: name, text: name})));
+            names.forEach(name => ankiDeck.append($('<option/>', {value: name, text: name})));
         }
 
         $('#anki-term-deck').val(opts.ankiTermDeck);
@@ -109,9 +108,9 @@ function populateAnkiDeckAndModel(opts) {
 
     const ankiModel = $('.anki-model');
     ankiModel.find('option').remove();
-    yomi.api_getModelNames({callback: (names) => {
+    yomi.api_getModelNames({callback: names => {
         if (names !== null) {
-            names.forEach((name) => ankiModel.append($('<option/>', {value: name, text: name})));
+            names.forEach(name => ankiModel.append($('<option/>', {value: name, text: name})));
         }
 
         populateAnkiFields($('#anki-term-model').val(opts.ankiTermModel), opts);
@@ -122,7 +121,7 @@ function populateAnkiDeckAndModel(opts) {
 function updateAnkiStatus() {
     $('.error-dlg').hide();
 
-    yomichan().api_getVersion({callback: (version) => {
+    yomichan().api_getVersion({callback: version => {
         if (version === null) {
             $('.error-dlg-connection').show();
             $('.options-anki-controls').hide();
@@ -145,19 +144,19 @@ function populateAnkiFields(element, opts) {
     const optKey = modelIdToFieldOptKey(modelId);
     const markers = modelIdToMarkers(modelId);
 
-    yomichan().api_getModelFieldNames({modelName, callback: (names) => {
+    yomichan().api_getModelFieldNames({modelName, callback: names => {
         const table = element.closest('.tab-pane').find('.anki-fields');
         table.find('tbody').remove();
 
         const tbody = $('<tbody>');
-        names.forEach((name) => {
+        names.forEach(name => {
             const button = $('<button>', {type: 'button', class: 'btn btn-default dropdown-toggle'});
             button.attr('data-toggle', 'dropdown').dropdown();
 
             const markerItems = $('<ul>', {class: 'dropdown-menu dropdown-menu-right'});
             for (const marker of markers) {
                 const link = $('<a>', {href: '#'}).text(`{${marker}}`);
-                link.click((e) => {
+                link.click(e => {
                     e.preventDefault();
                     link.closest('.input-group').find('.anki-field-value').val(link.text()).trigger('change');
                 });
@@ -232,7 +231,6 @@ $(document).ready(() => {
     loadOptions().then(opts => {
         $('#scan-length').val(opts.scanLength);
         $('#activate-on-startup').prop('checked', opts.activateOnStartup);
-        $('#load-enamdict').prop('checked', opts.loadEnamDict);
         $('#select-matched-text').prop('checked', opts.selectMatchedText);
         $('#show-advanced-options').prop('checked', opts.showAdvancedOptions);
         $('#enable-audio-playback').prop('checked', opts.enableAudioPlayback);
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index 4c726730..6b305f2f 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -23,7 +23,6 @@ function sanitizeOptions(options) {
         activateOnStartup:   false,
         selectMatchedText:   true,
         showAdvancedOptions: false,
-        loadEnamDict:        false,
         enableAudioPlayback: true,
         enableAnkiConnect:   false,
         ankiCardTags:        ['yomichan'],
diff --git a/ext/bg/options.html b/ext/bg/options.html
index 9a2bd0e8..781257a2 100644
--- a/ext/bg/options.html
+++ b/ext/bg/options.html
@@ -28,11 +28,6 @@
                 <h3>General Options</h3>
 
                 <form class="form-horizontal">
-                    <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>
-
                     <div class="form-group">
                         <div class="col-sm-offset-2 col-sm-10">
                             <div class="checkbox">
@@ -41,14 +36,6 @@
                         </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="load-enamdict"> Load <a href="http://www.edrdg.org/enamdict/enamdict_doc.html">ENAMDICT</a> (requires restart)</label>
-                            </div>
-                        </div>
-                    </div>
-
                     <div class="form-group">
                         <div class="col-sm-offset-2 col-sm-10">
                             <div class="checkbox">
@@ -73,7 +60,6 @@
                         </div>
                     </div>
 
-
                     <div class="form-group">
                         <div class="col-sm-offset-2 col-sm-10">
                             <div class="checkbox">
@@ -81,6 +67,11 @@
                             </div>
                         </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>
diff --git a/ext/fg/js/frame.js b/ext/fg/js/frame.js
index 30cb7c73..d06e8b94 100644
--- a/ext/fg/js/frame.js
+++ b/ext/fg/js/frame.js
@@ -46,19 +46,6 @@ function registerAudioLinks() {
     }
 }
 
-function onDomContentLoaded() {
-    registerKanjiLinks();
-    registerAddNoteLinks();
-    registerAudioLinks();
-}
-
-function onMessage(e) {
-    const {action, params} = e.data, method = window['api_' + action];
-    if (typeof(method) === 'function') {
-        method(params);
-    }
-}
-
 function api_setActionState({index, state, sequence}) {
     for (const mode in state) {
         const matches = document.querySelectorAll(`.action-bar[data-sequence="${sequence}"] .action-add-note[data-index="${index}"][data-mode="${mode}"]`);
@@ -75,5 +62,15 @@ function api_setActionState({index, state, sequence}) {
     }
 }
 
-document.addEventListener('DOMContentLoaded', onDomContentLoaded, false);
-window.addEventListener('message', onMessage);
+document.addEventListener('DOMContentLoaded', () => {
+    registerKanjiLinks();
+    registerAddNoteLinks();
+    registerAudioLinks();
+});
+
+window.addEventListener('message', () => {
+    const {action, params} = e.data, method = window['api_' + action];
+    if (typeof(method) === 'function') {
+        method(params);
+    }
+});
-- 
cgit v1.2.3


From b87611cfddf5ca0e1529b88ed1610ebe0d4657e6 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Thu, 15 Sep 2016 20:16:23 -0700
Subject: Bugfixes

---
 ext/fg/js/frame.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'ext/fg/js')

diff --git a/ext/fg/js/frame.js b/ext/fg/js/frame.js
index d06e8b94..590646d5 100644
--- a/ext/fg/js/frame.js
+++ b/ext/fg/js/frame.js
@@ -68,7 +68,7 @@ document.addEventListener('DOMContentLoaded', () => {
     registerAudioLinks();
 });
 
-window.addEventListener('message', () => {
+window.addEventListener('message', e => {
     const {action, params} = e.data, method = window['api_' + action];
     if (typeof(method) === 'function') {
         method(params);
-- 
cgit v1.2.3


From b969e8952c551fdc7c150dcc111eccdc90ac7408 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Thu, 15 Sep 2016 21:03:58 -0700
Subject: Cleanup

---
 ext/bg/js/translator.js |  6 +++---
 ext/fg/js/client.js     |  6 +++---
 ext/fg/js/frame.js      | 10 +++++++---
 ext/fg/js/popup.js      |  2 +-
 ext/fg/js/util.js       | 12 ++++++------
 5 files changed, 20 insertions(+), 16 deletions(-)

(limited to 'ext/fg/js')

diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js
index 2331bde7..e534e0cb 100644
--- a/ext/bg/js/translator.js
+++ b/ext/bg/js/translator.js
@@ -54,10 +54,10 @@ class Translator {
                     percent += banks[url].loaded / banks[url].total;
                 }
 
-                percent /= 3;
+                percent /= 3.0;
 
                 if (callback) {
-                    callback({state: 'update', progress: Math.ceil(100 * percent)});
+                    callback({state: 'update', progress: Math.ceil(100.0 * percent)});
                 }
             };
 
@@ -69,7 +69,7 @@ class Translator {
                 return this.dictionary.sealDb();
             }).then(() => {
                 if (callback) {
-                    callback({state: 'end', progress: 100});
+                    callback({state: 'end', progress: 100.0});
                 }
             });
         }).then(() => {
diff --git a/ext/fg/js/client.js b/ext/fg/js/client.js
index daeabf99..717b5873 100644
--- a/ext/fg/js/client.js
+++ b/ext/fg/js/client.js
@@ -116,7 +116,7 @@ class Client {
                     return canAddDefinitions(definitions, ['term_kanji', 'term_kana']);
                 }).then(states => {
                     if (states !== null) {
-                        states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence }));
+                        states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence}));
                     }
                 });
             }
@@ -158,7 +158,7 @@ class Client {
         const state = {[mode]: false};
         addDefinition(this.definitions[index], mode).then(success => {
             if (success) {
-                this.popup.sendMessage('setActionState', {index, state, sequence: this.sequence});
+                this.popup.invokeApi('setActionState', {index, state, sequence: this.sequence});
             } else {
                 alert('Note could not be added');
             }
@@ -195,7 +195,7 @@ class Client {
                 return canAddDefinitions(definitions, ['kanji']);
             }).then(states => {
                 if (states !== null) {
-                    states.forEach((state, index) => this.popup.sendMessage('setActionState', {index, state, sequence}));
+                    states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence}));
                 }
             });
         });
diff --git a/ext/fg/js/frame.js b/ext/fg/js/frame.js
index 590646d5..8a99a405 100644
--- a/ext/fg/js/frame.js
+++ b/ext/fg/js/frame.js
@@ -17,11 +17,15 @@
  */
 
 
+function invokeApi(action, params, target) {
+    target.postMessage({action, params}, '*');
+}
+
 function registerKanjiLinks() {
     for (const link of Array.from(document.getElementsByClassName('kanji-link'))) {
         link.addEventListener('click', e => {
             e.preventDefault();
-            window.parent.postMessage({action: 'displayKanji', params: e.target.innerHTML}, '*');
+            invokeApi('displayKanji', e.target.innerHTML, window.parent);
         });
     }
 }
@@ -31,7 +35,7 @@ function registerAddNoteLinks() {
         link.addEventListener('click', e => {
             e.preventDefault();
             const ds = e.currentTarget.dataset;
-            window.parent.postMessage({action: 'addNote', params: {index: ds.index, mode: ds.mode}}, '*');
+            invokeApi('addNote', {index: ds.index, mode: ds.mode}, window.parent);
         });
     }
 }
@@ -41,7 +45,7 @@ function registerAudioLinks() {
         link.addEventListener('click', e => {
             e.preventDefault();
             const ds = e.currentTarget.dataset;
-            window.parent.postMessage({action: 'playAudio', params: ds.index}, '*');
+            invokeApi('playAudio', ds.index, window.parent);
         });
     }
 }
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index 930df18d..88b8e4e3 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -68,7 +68,7 @@ class Popup {
         doc.close();
     }
 
-    sendMessage(action, params, callback) {
+    invokeApi(action, params) {
         if (this.popup !== null) {
             this.popup.contentWindow.postMessage({action, params}, '*');
         }
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
index 176765fc..c24ad885 100644
--- a/ext/fg/js/util.js
+++ b/ext/fg/js/util.js
@@ -17,28 +17,28 @@
  */
 
 
-function sendMessage(action, params) {
+function invokeApiBg(action, params) {
     return new Promise((resolve, reject) => chrome.runtime.sendMessage({action, params}, resolve));
 }
 
 function findTerm(text) {
-    return sendMessage('findTerm', {text});
+    return invokeApiBg('findTerm', {text});
 }
 
 function findKanji(text) {
-    return sendMessage('findKanji', {text});
+    return invokeApiBg('findKanji', {text});
 }
 
 function renderText(data, template) {
-    return sendMessage('renderText', {data, template});
+    return invokeApiBg('renderText', {data, template});
 }
 
 function canAddDefinitions(definitions, modes) {
-    return sendMessage('canAddDefinitions', {definitions, modes});
+    return invokeApiBg('canAddDefinitions', {definitions, modes});
 }
 
 function addDefinition(definition, mode) {
-    return sendMessage('addDefinition', {definition, mode});
+    return invokeApiBg('addDefinition', {definition, mode});
 }
 
 function textSourceFromPoint(point) {
-- 
cgit v1.2.3


From dc273c0c73dd39e8cad45e591b02231cb2cbed8c Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Thu, 15 Sep 2016 22:44:33 -0700
Subject: Cleanup

---
 ext/fg/js/client.js | 205 ----------------------------------------------------
 ext/fg/js/driver.js | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 ext/manifest.json   |   2 +-
 3 files changed, 206 insertions(+), 206 deletions(-)
 delete mode 100644 ext/fg/js/client.js
 create mode 100644 ext/fg/js/driver.js

(limited to 'ext/fg/js')

diff --git a/ext/fg/js/client.js b/ext/fg/js/client.js
deleted file mode 100644
index 717b5873..00000000
--- a/ext/fg/js/client.js
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
- * Author: Alex Yatskov <alex@foosoft.net>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-
-class Client {
-    constructor() {
-        this.popup = new Popup();
-        this.audio = {};
-        this.lastMousePos = null;
-        this.lastTextSource = null;
-        this.pendingLookup = false;
-        this.enabled = false;
-        this.options = {};
-        this.definitions = null;
-        this.sequence = 0;
-        this.fgRoot = chrome.extension.getURL('fg');
-
-        chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
-        window.addEventListener('message', this.onFrameMessage.bind(this));
-        window.addEventListener('mousedown', this.onMouseDown.bind(this));
-        window.addEventListener('mousemove', this.onMouseMove.bind(this));
-        window.addEventListener('keydown', this.onKeyDown.bind(this));
-        window.addEventListener('scroll', e => this.hidePopup());
-        window.addEventListener('resize', e => this.hidePopup());
-    }
-
-    onKeyDown(e) {
-        if (this.enabled && this.lastMousePos !== null && (e.keyCode === 16 || e.charCode === 16)) {
-            this.searchAt(this.lastMousePos);
-        }
-    }
-
-    onMouseMove(e) {
-        this.lastMousePos = {x: e.clientX, y: e.clientY};
-        if (this.enabled && (e.shiftKey || e.which === 2)) {
-            this.searchAt(this.lastMousePos);
-        }
-    }
-
-    onMouseDown(e) {
-        this.lastMousePos = {x: e.clientX, y: e.clientY};
-        if (this.enabled && (e.shiftKey || e.which === 2)) {
-            this.searchAt(this.lastMousePos);
-        } else {
-            this.hidePopup();
-        }
-    }
-
-    onBgMessage({action, params}, sender, callback) {
-        const method = this['api_' + action];
-        if (typeof(method) === 'function') {
-            method.call(this, params);
-        }
-
-        callback();
-    }
-
-    onFrameMessage(e) {
-        const {action, params} = e.data, method = this['api_' + action];
-        if (typeof(method) === 'function') {
-            method.call(this, params);
-        }
-    }
-
-    searchAt(point) {
-        if (this.pendingLookup) {
-            return;
-        }
-
-        const textSource = textSourceFromPoint(point);
-        if (textSource === null || !textSource.containsPoint(point)) {
-            this.hidePopup();
-            return;
-        }
-
-        if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) {
-            return;
-        }
-
-        textSource.setEndOffset(this.options.scanLength);
-
-        this.pendingLookup = true;
-        findTerm(textSource.text()).then(({definitions, length}) => {
-            if (length === 0) {
-                this.pendingLookup = false;
-                this.hidePopup();
-            } else {
-                textSource.setEndOffset(length);
-
-                const sentence = extractSentence(textSource, this.options.sentenceExtent);
-                definitions.forEach(definition => {
-                    definition.url = window.location.href;
-                    definition.sentence = sentence;
-                });
-
-                const sequence = ++this.sequence;
-                return renderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'term-list.html').then(content => {
-                    this.definitions = definitions;
-                    this.pendingLookup = false;
-                    this.showPopup(textSource, content);
-                    return canAddDefinitions(definitions, ['term_kanji', 'term_kana']);
-                }).then(states => {
-                    if (states !== null) {
-                        states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence}));
-                    }
-                });
-            }
-        });
-    }
-
-    showPopup(textSource, content) {
-        this.popup.showNextTo(textSource.getRect(), content);
-
-        if (this.options.selectMatchedText) {
-            textSource.select();
-        }
-
-        this.lastTextSource = textSource;
-    }
-
-    hidePopup() {
-        this.popup.hide();
-
-        if (this.options.selectMatchedText && this.lastTextSource !== null) {
-            this.lastTextSource.deselect();
-        }
-
-        this.lastTextSource = null;
-        this.definitions = null;
-    }
-
-    api_setOptions(opts) {
-        this.options = opts;
-    }
-
-    api_setEnabled(enabled) {
-        if (!(this.enabled = enabled)) {
-            this.hidePopup();
-        }
-    }
-
-    api_addNote({index, mode}) {
-        const state = {[mode]: false};
-        addDefinition(this.definitions[index], mode).then(success => {
-            if (success) {
-                this.popup.invokeApi('setActionState', {index, state, sequence: this.sequence});
-            } else {
-                alert('Note could not be added');
-            }
-        });
-    }
-
-    api_playAudio(index) {
-        const definition = this.definitions[index];
-
-        let url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=${encodeURIComponent(definition.expression)}`;
-        if (definition.reading) {
-            url += `&kana=${encodeURIComponent(definition.reading)}`;
-        }
-
-        for (const key in this.audio) {
-            this.audio[key].pause();
-        }
-
-        const audio = this.audio[url] || new Audio(url);
-        audio.currentTime = 0;
-        audio.play();
-
-        this.audio[url] = audio;
-    }
-
-    api_displayKanji(kanji) {
-        findKanji(kanji).then(definitions => {
-            definitions.forEach(definition => definition.url = window.location.href);
-
-            const sequence = ++this.sequence;
-            return renderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'kanji-list.html').then(content => {
-                this.definitions = definitions;
-                this.popup.setContent(content, definitions);
-                return canAddDefinitions(definitions, ['kanji']);
-            }).then(states => {
-                if (states !== null) {
-                    states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence}));
-                }
-            });
-        });
-    }
-}
-
-window.yomiClient = new Client();
diff --git a/ext/fg/js/driver.js b/ext/fg/js/driver.js
new file mode 100644
index 00000000..de097980
--- /dev/null
+++ b/ext/fg/js/driver.js
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Author: Alex Yatskov <alex@foosoft.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+class Driver {
+    constructor() {
+        this.popup = new Popup();
+        this.audio = {};
+        this.lastMousePos = null;
+        this.lastTextSource = null;
+        this.pendingLookup = false;
+        this.enabled = false;
+        this.options = {};
+        this.definitions = null;
+        this.sequence = 0;
+        this.fgRoot = chrome.extension.getURL('fg');
+
+        chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
+        window.addEventListener('message', this.onFrameMessage.bind(this));
+        window.addEventListener('mousedown', this.onMouseDown.bind(this));
+        window.addEventListener('mousemove', this.onMouseMove.bind(this));
+        window.addEventListener('keydown', this.onKeyDown.bind(this));
+        window.addEventListener('scroll', e => this.hidePopup());
+        window.addEventListener('resize', e => this.hidePopup());
+    }
+
+    onKeyDown(e) {
+        if (this.enabled && this.lastMousePos !== null && (e.keyCode === 16 || e.charCode === 16)) {
+            this.searchAt(this.lastMousePos);
+        }
+    }
+
+    onMouseMove(e) {
+        this.lastMousePos = {x: e.clientX, y: e.clientY};
+        if (this.enabled && (e.shiftKey || e.which === 2)) {
+            this.searchAt(this.lastMousePos);
+        }
+    }
+
+    onMouseDown(e) {
+        this.lastMousePos = {x: e.clientX, y: e.clientY};
+        if (this.enabled && (e.shiftKey || e.which === 2)) {
+            this.searchAt(this.lastMousePos);
+        } else {
+            this.hidePopup();
+        }
+    }
+
+    onBgMessage({action, params}, sender, callback) {
+        const method = this['api_' + action];
+        if (typeof(method) === 'function') {
+            method.call(this, params);
+        }
+
+        callback();
+    }
+
+    onFrameMessage(e) {
+        const {action, params} = e.data, method = this['api_' + action];
+        if (typeof(method) === 'function') {
+            method.call(this, params);
+        }
+    }
+
+    searchAt(point) {
+        if (this.pendingLookup) {
+            return;
+        }
+
+        const textSource = textSourceFromPoint(point);
+        if (textSource === null || !textSource.containsPoint(point)) {
+            this.hidePopup();
+            return;
+        }
+
+        if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) {
+            return;
+        }
+
+        textSource.setEndOffset(this.options.scanLength);
+
+        this.pendingLookup = true;
+        findTerm(textSource.text()).then(({definitions, length}) => {
+            if (length === 0) {
+                this.pendingLookup = false;
+                this.hidePopup();
+            } else {
+                textSource.setEndOffset(length);
+
+                const sentence = extractSentence(textSource, this.options.sentenceExtent);
+                definitions.forEach(definition => {
+                    definition.url = window.location.href;
+                    definition.sentence = sentence;
+                });
+
+                const sequence = ++this.sequence;
+                return renderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'term-list.html').then(content => {
+                    this.definitions = definitions;
+                    this.pendingLookup = false;
+                    this.showPopup(textSource, content);
+                    return canAddDefinitions(definitions, ['term_kanji', 'term_kana']);
+                }).then(states => {
+                    if (states !== null) {
+                        states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence}));
+                    }
+                });
+            }
+        });
+    }
+
+    showPopup(textSource, content) {
+        this.popup.showNextTo(textSource.getRect(), content);
+
+        if (this.options.selectMatchedText) {
+            textSource.select();
+        }
+
+        this.lastTextSource = textSource;
+    }
+
+    hidePopup() {
+        this.popup.hide();
+
+        if (this.options.selectMatchedText && this.lastTextSource !== null) {
+            this.lastTextSource.deselect();
+        }
+
+        this.lastTextSource = null;
+        this.definitions = null;
+    }
+
+    api_setOptions(opts) {
+        this.options = opts;
+    }
+
+    api_setEnabled(enabled) {
+        if (!(this.enabled = enabled)) {
+            this.hidePopup();
+        }
+    }
+
+    api_addNote({index, mode}) {
+        const state = {[mode]: false};
+        addDefinition(this.definitions[index], mode).then(success => {
+            if (success) {
+                this.popup.invokeApi('setActionState', {index, state, sequence: this.sequence});
+            } else {
+                alert('Note could not be added');
+            }
+        });
+    }
+
+    api_playAudio(index) {
+        const definition = this.definitions[index];
+
+        let url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=${encodeURIComponent(definition.expression)}`;
+        if (definition.reading) {
+            url += `&kana=${encodeURIComponent(definition.reading)}`;
+        }
+
+        for (const key in this.audio) {
+            this.audio[key].pause();
+        }
+
+        const audio = this.audio[url] || new Audio(url);
+        audio.currentTime = 0;
+        audio.play();
+
+        this.audio[url] = audio;
+    }
+
+    api_displayKanji(kanji) {
+        findKanji(kanji).then(definitions => {
+            definitions.forEach(definition => definition.url = window.location.href);
+
+            const sequence = ++this.sequence;
+            return renderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'kanji-list.html').then(content => {
+                this.definitions = definitions;
+                this.popup.setContent(content, definitions);
+                return canAddDefinitions(definitions, ['kanji']);
+            }).then(states => {
+                if (states !== null) {
+                    states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence}));
+                }
+            });
+        });
+    }
+}
+
+window.driver = new Driver();
diff --git a/ext/manifest.json b/ext/manifest.json
index c3864e10..fcf7cef7 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -16,7 +16,7 @@
             "fg/js/source-element.js",
             "fg/js/popup.js",
             "fg/js/util.js",
-            "fg/js/client.js"
+            "fg/js/driver.js"
         ],
         "css": ["fg/css/client.css"]
     }],
-- 
cgit v1.2.3


From 2a65060b73ec07a99df9e5922745427f81ea7e51 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 18 Sep 2016 17:32:57 -0700
Subject: Hold down ctrl to search for kanji, fixes #13

---
 ext/fg/js/driver.js | 77 ++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 58 insertions(+), 19 deletions(-)

(limited to 'ext/fg/js')

diff --git a/ext/fg/js/driver.js b/ext/fg/js/driver.js
index de097980..68cbcdee 100644
--- a/ext/fg/js/driver.js
+++ b/ext/fg/js/driver.js
@@ -41,21 +41,23 @@ class Driver {
 
     onKeyDown(e) {
         if (this.enabled && this.lastMousePos !== null && (e.keyCode === 16 || e.charCode === 16)) {
-            this.searchAt(this.lastMousePos);
+            this.searchAt(this.lastMousePos, e.ctrlKey ? 'kanji' : 'terms');
+        } else {
+            this.hidePopup();
         }
     }
 
     onMouseMove(e) {
         this.lastMousePos = {x: e.clientX, y: e.clientY};
         if (this.enabled && (e.shiftKey || e.which === 2)) {
-            this.searchAt(this.lastMousePos);
+            this.searchAt(this.lastMousePos, e.ctrlKey ? 'kanji' : 'terms');
         }
     }
 
     onMouseDown(e) {
         this.lastMousePos = {x: e.clientX, y: e.clientY};
         if (this.enabled && (e.shiftKey || e.which === 2)) {
-            this.searchAt(this.lastMousePos);
+            this.searchAt(this.lastMousePos, e.ctrlKey ? 'kanji' : 'terms');
         } else {
             this.hidePopup();
         }
@@ -77,26 +79,12 @@ class Driver {
         }
     }
 
-    searchAt(point) {
-        if (this.pendingLookup) {
-            return;
-        }
-
-        const textSource = textSourceFromPoint(point);
-        if (textSource === null || !textSource.containsPoint(point)) {
-            this.hidePopup();
-            return;
-        }
-
-        if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) {
-            return;
-        }
-
+    searchTerms(textSource) {
         textSource.setEndOffset(this.options.scanLength);
 
         this.pendingLookup = true;
         findTerm(textSource.text()).then(({definitions, length}) => {
-            if (length === 0) {
+            if (definitions.length === 0) {
                 this.pendingLookup = false;
                 this.hidePopup();
             } else {
@@ -123,6 +111,57 @@ class Driver {
         });
     }
 
+    searchKanji(textSource) {
+        textSource.setEndOffset(1);
+
+        this.pendingLookup = true;
+        findKanji(textSource.text()).then(definitions => {
+            if (definitions.length === 0) {
+                this.pendingLookup = false;
+                this.hidePopup();
+            } else {
+                definitions.forEach(definition => definition.url = window.location.href);
+
+                const sequence = ++this.sequence;
+                return renderText({definitions, sequence, root: this.fgRoot, options: this.options}, 'kanji-list.html').then(content => {
+                    this.definitions = definitions;
+                    this.pendingLookup = false;
+                    this.showPopup(textSource, content);
+                    return canAddDefinitions(definitions, ['kanji']);
+                }).then(states => {
+                    if (states !== null) {
+                        states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence}));
+                    }
+                });
+            }
+        });
+    }
+
+    searchAt(point, mode) {
+        if (this.pendingLookup) {
+            return;
+        }
+
+        const textSource = textSourceFromPoint(point);
+        if (textSource === null || !textSource.containsPoint(point)) {
+            this.hidePopup();
+            return;
+        }
+
+        if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) {
+            return;
+        }
+
+        switch (mode) {
+            case 'terms':
+                this.searchTerms(textSource);
+                break;
+            case 'kanji':
+                this.searchKanji(textSource);
+                break;
+        }
+    }
+
     showPopup(textSource, content) {
         this.popup.showNextTo(textSource.getRect(), content);
 
-- 
cgit v1.2.3