From d57c5530b7ad56a7cc89782b4d186d8fddb55d86 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sat, 1 Jul 2017 18:27:49 -0700
Subject: view added notes

---
 ext/fg/js/display-frame.js | 4 ++++
 ext/fg/js/util.js          | 3 +++
 2 files changed, 7 insertions(+)

(limited to 'ext/fg')

diff --git a/ext/fg/js/display-frame.js b/ext/fg/js/display-frame.js
index 9fd09e74..b29a0379 100644
--- a/ext/fg/js/display-frame.js
+++ b/ext/fg/js/display-frame.js
@@ -31,6 +31,10 @@ window.displayFrame = new class extends Display {
         return bgDefinitionsAddable(definitions, modes);
     }
 
+    noteView(noteId) {
+        return bgNoteView(noteId);
+    }
+
     templateRender(template, data) {
         return bgTemplateRender(template, data);
     }
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
index c6270ce6..e1b0e080 100644
--- a/ext/fg/js/util.js
+++ b/ext/fg/js/util.js
@@ -62,6 +62,9 @@ function bgDefinitionAdd(definition, mode) {
     return bgInvoke('definitionAdd', {definition, mode});
 }
 
+function bgNoteView(noteId) {
+    return bgInvoke('noteView', {noteId});
+}
 
 /*
  * Document
-- 
cgit v1.2.3


From b3984ccd54340195fc352033f61d33e4b5f492ea Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 9 Jul 2017 15:23:11 -0700
Subject: cleanup

---
 ext/bg/js/database.js | 14 +++++++-------
 ext/bg/js/options.js  |  2 +-
 ext/fg/js/util.js     | 18 +++++++-----------
 3 files changed, 15 insertions(+), 19 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js
index 70aeb0d7..400ebd6a 100644
--- a/ext/bg/js/database.js
+++ b/ext/bg/js/database.js
@@ -35,7 +35,7 @@ class Database {
     }
 
     prepare() {
-        if (this.db !== null) {
+        if (!this.db) {
             return Promise.reject('database already initialized');
         }
 
@@ -53,7 +53,7 @@ class Database {
     }
 
     purge() {
-        if (this.db === null) {
+        if (!this.db) {
             return Promise.reject('database not initialized');
         }
 
@@ -66,7 +66,7 @@ class Database {
     }
 
     findTerms(term, dictionaries) {
-        if (this.db === null) {
+        if (!this.db) {
             return Promise.reject('database not initialized');
         }
 
@@ -96,7 +96,7 @@ class Database {
     }
 
     findKanji(kanji, dictionaries) {
-        if (this.db === null) {
+        if (!this.db) {
             return Promise.reject('database not initialized');
         }
 
@@ -124,7 +124,7 @@ class Database {
     }
 
     cacheTagMeta(dictionaries) {
-        if (this.db === null) {
+        if (!this.db) {
             return Promise.reject('database not initialized');
         }
 
@@ -148,7 +148,7 @@ class Database {
     }
 
     getDictionaries() {
-        if (this.db === null) {
+        if (!this.db) {
             return Promise.reject('database not initialized');
         }
 
@@ -156,7 +156,7 @@ class Database {
     }
 
     importDictionary(archive, callback) {
-        if (this.db === null) {
+        if (!this.db) {
             return Promise.reject('database not initialized');
         }
 
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index 5aa18366..728ddae4 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -337,7 +337,7 @@ function ankiFieldsPopulate(element, options) {
     const container = tab.find('tbody').empty();
 
     const modelName = element.val();
-    if (modelName === null) {
+    if (!modelName) {
         return Promise.resolve();
     }
 
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
index e1b0e080..2acd81c4 100644
--- a/ext/fg/js/util.js
+++ b/ext/fg/js/util.js
@@ -117,7 +117,7 @@ function docImposterDestroy() {
 
 function docRangeFromPoint(point) {
     const element = document.elementFromPoint(point.x, point.y);
-    if (element !== null) {
+    if (element) {
         if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') {
             return new TextSourceElement(element);
         } else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
@@ -128,23 +128,19 @@ function docRangeFromPoint(point) {
     if (!document.caretRangeFromPoint) {
         document.caretRangeFromPoint = (x, y) => {
             const position = document.caretPositionFromPoint(x,y);
-            if (position === null) {
-                return null;
+            if (position) {
+                const range = document.createRange();
+                range.setStart(position.offsetNode, position.offset);
+                range.setEnd(position.offsetNode, position.offset);
+                return range;
             }
-
-            const range = document.createRange();
-            range.setStart(position.offsetNode, position.offset);
-            range.setEnd(position.offsetNode, position.offset);
-            return range;
         };
     }
 
     const range = document.caretRangeFromPoint(point.x, point.y);
-    if (range !== null) {
+    if (range) {
         return new TextSourceRange(range);
     }
-
-    return null;
 }
 
 function docSentenceExtract(source, extent) {
-- 
cgit v1.2.3


From 516c7f5381700700dcfdfb06682f11de8205137b Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 16 Jul 2017 12:48:27 -0700
Subject: refactor mixed/js/util.js

---
 ext/bg/background.html  |   2 +-
 ext/bg/search.html      |   2 +-
 ext/fg/frame.html       |   2 +-
 ext/mixed/js/audio.js   | 130 ++++++++++++++++++++++++++++++++++++++++++
 ext/mixed/js/display.js |  18 +++++-
 ext/mixed/js/util.js    | 149 ------------------------------------------------
 6 files changed, 149 insertions(+), 154 deletions(-)
 create mode 100644 ext/mixed/js/audio.js
 delete mode 100644 ext/mixed/js/util.js

(limited to 'ext/fg')

diff --git a/ext/bg/background.html b/ext/bg/background.html
index 4410c249..4a2d8c8d 100644
--- a/ext/bg/background.html
+++ b/ext/bg/background.html
@@ -8,7 +8,7 @@
         <script src="/mixed/lib/dexie.min.js"></script>
         <script src="/mixed/lib/wanakana.min.js"></script>
         <script src="/mixed/lib/jszip.min.js"></script>
-        <script src="/mixed/js/util.js"></script>
+        <script src="/mixed/js/audio.js"></script>
         <script src="/bg/js/templates.js"></script>
         <script src="/bg/js/util.js"></script>
         <script src="/bg/js/anki-connect.js"></script>
diff --git a/ext/bg/search.html b/ext/bg/search.html
index 4c07ee61..0fc3caf7 100644
--- a/ext/bg/search.html
+++ b/ext/bg/search.html
@@ -34,7 +34,7 @@
 
         <script src="/mixed/lib/jquery.min.js"></script>
         <script src="/bg/js/util.js"></script>
-        <script src="/mixed/js/util.js"></script>
+        <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
         <script src="/mixed/lib/wanakana.min.js"></script>
         <script src="/bg/js/display-window.js"></script>
diff --git a/ext/fg/frame.html b/ext/fg/frame.html
index c20745af..ecaee323 100644
--- a/ext/fg/frame.html
+++ b/ext/fg/frame.html
@@ -33,7 +33,7 @@
         <script src="/mixed/lib/jquery.min.js"></script>
         <script src="/mixed/lib/wanakana.min.js"></script>
         <script src="/fg/js/util.js"></script>
-        <script src="/mixed/js/util.js"></script>
+        <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
         <script src="/fg/js/display-frame.js"></script>
     </body>
diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js
new file mode 100644
index 00000000..451fe1d9
--- /dev/null
+++ b/ext/mixed/js/audio.js
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017  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/>.
+ */
+
+
+/*
+ * Audio
+ */
+
+async function audioBuildUrl(definition, mode, cache={}) {
+    if (mode === 'jpod101') {
+        let kana = definition.reading;
+        let kanji = definition.expression;
+
+        if (!kana && wanakana.isHiragana(kanji)) {
+            kana = kanji;
+            kanji = null;
+        }
+
+        const params = [];
+        if (kanji) {
+            params.push(`kanji=${encodeURIComponent(kanji)}`);
+        }
+        if (kana) {
+            params.push(`kana=${encodeURIComponent(kana)}`);
+        }
+
+        const url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`;
+        return Promise.resolve(url);
+    } else if (mode === 'jpod101-alternate') {
+        return new Promise((resolve, reject) => {
+            const response = cache[definition.expression];
+            if (response) {
+                resolve(response);
+            } else {
+                const data = {
+                    post: 'dictionary_reference',
+                    match_type: 'exact',
+                    search_query: definition.expression
+                };
+
+                const params = [];
+                for (const key in data) {
+                    params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
+                }
+
+                const xhr = new XMLHttpRequest();
+                xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
+                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+                xhr.addEventListener('error', () => reject('failed to scrape audio data'));
+                xhr.addEventListener('load', () => {
+                    cache[definition.expression] = xhr.responseText;
+                    resolve(xhr.responseText);
+                });
+
+                xhr.send(params.join('&'));
+            }
+        }).then(response => {
+            const dom = new DOMParser().parseFromString(response, 'text/html');
+            for (const row of dom.getElementsByClassName('dc-result-row')) {
+                try {
+                    const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url');
+                    const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText;
+                    if (url && reading && (!definition.reading || definition.reading === reading)) {
+                        return url;
+                    }
+                } catch (e) {
+                    // NOP
+                }
+            }
+        });
+    } else {
+        return Promise.resolve();
+    }
+}
+
+function audioBuildFilename(definition) {
+    if (definition.reading || definition.expression) {
+        let filename = 'yomichan';
+        if (definition.reading) {
+            filename += `_${definition.reading}`;
+        }
+        if (definition.expression) {
+            filename += `_${definition.expression}`;
+        }
+
+        return filename += '.mp3';
+    }
+}
+
+async function audioInject(definition, fields, mode) {
+    let usesAudio = false;
+    for (const name in fields) {
+        if (fields[name].includes('{audio}')) {
+            usesAudio = true;
+            break;
+        }
+    }
+
+    if (!usesAudio) {
+        return true;
+    }
+
+    try {
+        const url = await audioBuildUrl(definition, mode);
+        const filename = audioBuildFilename(definition);
+
+        if (url && filename) {
+            definition.audio = {url, filename};
+        }
+
+        return true;
+    } catch (e) {
+        return false;
+    }
+}
diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js
index da0cd351..e54bc0f9 100644
--- a/ext/mixed/js/display.js
+++ b/ext/mixed/js/display.js
@@ -80,7 +80,7 @@ class Display {
         if (context) {
             for (const definition of definitions) {
                 if (context.sentence) {
-                    definition.cloze = clozeBuild(context.sentence, definition.source);
+                    definition.cloze = Display.clozeBuild(context.sentence, definition.source);
                 }
 
                 definition.url = context.url;
@@ -119,7 +119,7 @@ class Display {
         if (context) {
             for (const definition of definitions) {
                 if (context.sentence) {
-                    definition.cloze = clozeBuild(context.sentence);
+                    definition.cloze = Display.clozeBuild(context.sentence);
                 }
 
                 definition.url = context.url;
@@ -394,6 +394,20 @@ class Display {
         }).catch(this.handleError.bind(this)).then(() => this.spinner.hide());
     }
 
+    static clozeBuild(sentence, source) {
+        const result = {
+            sentence: sentence.text.trim()
+        };
+
+        if (source) {
+            result.prefix = sentence.text.substring(0, sentence.offset).trim();
+            result.body = source.trim();
+            result.suffix = sentence.text.substring(sentence.offset + source.length).trim();
+        }
+
+        return result;
+    }
+
     static entryIndexFind(element) {
         return $('.entry').index(element.closest('.entry'));
     }
diff --git a/ext/mixed/js/util.js b/ext/mixed/js/util.js
deleted file mode 100644
index 0a8c914d..00000000
--- a/ext/mixed/js/util.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017  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/>.
- */
-
-
-/*
- * Cloze
- */
-
-function clozeBuild(sentence, source) {
-    const result = {
-        sentence: sentence.text.trim()
-    };
-
-    if (source) {
-        result.prefix = sentence.text.substring(0, sentence.offset).trim();
-        result.body = source.trim();
-        result.suffix = sentence.text.substring(sentence.offset + source.length).trim();
-    }
-
-    return result;
-}
-
-
-/*
- * Audio
- */
-
-async function audioBuildUrl(definition, mode, cache={}) {
-    if (mode === 'jpod101') {
-        let kana = definition.reading;
-        let kanji = definition.expression;
-
-        if (!kana && wanakana.isHiragana(kanji)) {
-            kana = kanji;
-            kanji = null;
-        }
-
-        const params = [];
-        if (kanji) {
-            params.push(`kanji=${encodeURIComponent(kanji)}`);
-        }
-        if (kana) {
-            params.push(`kana=${encodeURIComponent(kana)}`);
-        }
-
-        const url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`;
-        return Promise.resolve(url);
-    } else if (mode === 'jpod101-alternate') {
-        return new Promise((resolve, reject) => {
-            const response = cache[definition.expression];
-            if (response) {
-                resolve(response);
-            } else {
-                const data = {
-                    post: 'dictionary_reference',
-                    match_type: 'exact',
-                    search_query: definition.expression
-                };
-
-                const params = [];
-                for (const key in data) {
-                    params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
-                }
-
-                const xhr = new XMLHttpRequest();
-                xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
-                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
-                xhr.addEventListener('error', () => reject('failed to scrape audio data'));
-                xhr.addEventListener('load', () => {
-                    cache[definition.expression] = xhr.responseText;
-                    resolve(xhr.responseText);
-                });
-
-                xhr.send(params.join('&'));
-            }
-        }).then(response => {
-            const dom = new DOMParser().parseFromString(response, 'text/html');
-            for (const row of dom.getElementsByClassName('dc-result-row')) {
-                try {
-                    const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url');
-                    const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText;
-                    if (url && reading && (!definition.reading || definition.reading === reading)) {
-                        return url;
-                    }
-                } catch (e) {
-                    // NOP
-                }
-            }
-        });
-    } else {
-        return Promise.resolve();
-    }
-}
-
-function audioBuildFilename(definition) {
-    if (definition.reading || definition.expression) {
-        let filename = 'yomichan';
-        if (definition.reading) {
-            filename += `_${definition.reading}`;
-        }
-        if (definition.expression) {
-            filename += `_${definition.expression}`;
-        }
-
-        return filename += '.mp3';
-    }
-}
-
-async function audioInject(definition, fields, mode) {
-    let usesAudio = false;
-    for (const name in fields) {
-        if (fields[name].includes('{audio}')) {
-            usesAudio = true;
-            break;
-        }
-    }
-
-    if (!usesAudio) {
-        return true;
-    }
-
-    try {
-        const url = await audioBuildUrl(definition, mode);
-        const filename = audioBuildFilename(definition);
-
-        if (url && filename) {
-            definition.audio = {url, filename};
-        }
-
-        return true;
-    } catch (e) {
-        return false;
-    }
-}
-- 
cgit v1.2.3


From 8bcc4ddf19a6c171ad1cf5d18b165da391089fd2 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Thu, 20 Jul 2017 21:21:22 -0700
Subject: cleanup

---
 ext/fg/frame.html       |   3 +-
 ext/fg/js/background.js |  63 ++++++++++++++
 ext/fg/js/document.js   | 162 ++++++++++++++++++++++++++++++++++++
 ext/fg/js/util.js       | 215 ------------------------------------------------
 ext/manifest.json       |   3 +-
 5 files changed, 229 insertions(+), 217 deletions(-)
 create mode 100644 ext/fg/js/background.js
 create mode 100644 ext/fg/js/document.js
 delete mode 100644 ext/fg/js/util.js

(limited to 'ext/fg')

diff --git a/ext/fg/frame.html b/ext/fg/frame.html
index ecaee323..80e69967 100644
--- a/ext/fg/frame.html
+++ b/ext/fg/frame.html
@@ -32,9 +32,10 @@
 
         <script src="/mixed/lib/jquery.min.js"></script>
         <script src="/mixed/lib/wanakana.min.js"></script>
-        <script src="/fg/js/util.js"></script>
         <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
+        <script src="/fg/js/dictionary.js"></script>
+        <script src="/fg/js/background.js"></script>
         <script src="/fg/js/display-frame.js"></script>
     </body>
 </html>
diff --git a/ext/fg/js/background.js b/ext/fg/js/background.js
new file mode 100644
index 00000000..52b533be
--- /dev/null
+++ b/ext/fg/js/background.js
@@ -0,0 +1,63 @@
+/*
+ * 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 bgInvoke(action, params={}) {
+    return new Promise((resolve, reject) => {
+        try {
+            chrome.runtime.sendMessage({action, params}, ({result, error}) => {
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(result);
+                }
+            });
+        } catch (e) {
+            window.orphaned = true;
+            reject(e.message);
+        }
+    });
+}
+
+function bgOptionsGet() {
+    return bgInvoke('optionsGet');
+}
+
+function bgTermsFind(text) {
+    return bgInvoke('termsFind', {text});
+}
+
+function bgKanjiFind(text) {
+    return bgInvoke('kanjiFind', {text});
+}
+
+function bgTemplateRender(template, data) {
+    return bgInvoke('templateRender', {data, template});
+}
+
+function bgDefinitionsAddable(definitions, modes) {
+    return bgInvoke('definitionsAddable', {definitions, modes}).catch(() => null);
+}
+
+function bgDefinitionAdd(definition, mode) {
+    return bgInvoke('definitionAdd', {definition, mode});
+}
+
+function bgNoteView(noteId) {
+    return bgInvoke('noteView', {noteId});
+}
diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js
new file mode 100644
index 00000000..582b6770
--- /dev/null
+++ b/ext/fg/js/document.js
@@ -0,0 +1,162 @@
+/*
+ * 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 docOffsetCalc(element) {
+    const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
+    const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
+
+    const clientTop = document.documentElement.clientTop || document.body.clientTop || 0;
+    const clientLeft = document.documentElement.clientLeft || document.body.clientLeft || 0;
+
+    const rect = element.getBoundingClientRect();
+    const top  = Math.round(rect.top +  scrollTop - clientTop);
+    const left = Math.round(rect.left + scrollLeft - clientLeft);
+
+    return {top, left};
+}
+
+function docImposterCreate(element) {
+    const styleProps = window.getComputedStyle(element);
+    const stylePairs = [];
+    for (const key of styleProps) {
+        stylePairs.push(`${key}: ${styleProps[key]};`);
+    }
+
+    const offset = docOffsetCalc(element);
+    const imposter = document.createElement('div');
+    imposter.className = 'yomichan-imposter';
+    imposter.innerText = element.value;
+    imposter.style.cssText = stylePairs.join('\n');
+    imposter.style.position = 'absolute';
+    imposter.style.top = `${offset.top}px`;
+    imposter.style.left = `${offset.left}px`;
+    imposter.style.zIndex = 2147483646;
+    if (element.nodeName === 'TEXTAREA' && styleProps.overflow === 'visible') {
+        imposter.style.overflow = 'auto';
+    }
+
+    document.body.appendChild(imposter);
+    imposter.scrollTop = element.scrollTop;
+    imposter.scrollLeft = element.scrollLeft;
+}
+
+function docImposterDestroy() {
+    for (const element of document.getElementsByClassName('yomichan-imposter')) {
+        element.parentNode.removeChild(element);
+    }
+}
+
+function docRangeFromPoint(point) {
+    const element = document.elementFromPoint(point.x, point.y);
+    if (element) {
+        if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') {
+            return new TextSourceElement(element);
+        } else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
+            docImposterCreate(element);
+        }
+    }
+
+    if (!document.caretRangeFromPoint) {
+        document.caretRangeFromPoint = (x, y) => {
+            const position = document.caretPositionFromPoint(x,y);
+            if (position) {
+                const range = document.createRange();
+                range.setStart(position.offsetNode, position.offset);
+                range.setEnd(position.offsetNode, position.offset);
+                return range;
+            }
+        };
+    }
+
+    const range = document.caretRangeFromPoint(point.x, point.y);
+    if (range) {
+        return new TextSourceRange(range);
+    }
+}
+
+function docSentenceExtract(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 (c === '\n') {
+            startPos = i + 1;
+            break;
+        }
+
+        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 (c === '\n') {
+            endPos = i + 1;
+            break;
+        }
+
+        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);
+        }
+    }
+
+    const text = content.substring(startPos, endPos);
+    const padding = text.length - text.replace(/^\s+/, '').length;
+
+    return {
+        text: text.trim(),
+        offset: position - startPos - padding
+    };
+}
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
deleted file mode 100644
index 2acd81c4..00000000
--- a/ext/fg/js/util.js
+++ /dev/null
@@ -1,215 +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/>.
- */
-
-
-/*
- * Background
- */
-
-function bgInvoke(action, params) {
-    return new Promise((resolve, reject) => {
-        try {
-            chrome.runtime.sendMessage({action, params}, ({result, error}) => {
-                if (error) {
-                    reject(error);
-                } else {
-                    resolve(result);
-                }
-            });
-        } catch (e) {
-            window.orphaned = true;
-            reject(e.message);
-        }
-    });
-}
-
-function bgOptionsGet() {
-    return bgInvoke('optionsGet', {});
-}
-
-function bgTermsFind(text) {
-    return bgInvoke('termsFind', {text});
-}
-
-function bgKanjiFind(text) {
-    return bgInvoke('kanjiFind', {text});
-}
-
-function bgTemplateRender(template, data) {
-    return bgInvoke('templateRender', {data, template});
-}
-
-function bgDefinitionsAddable(definitions, modes) {
-    return bgInvoke('definitionsAddable', {definitions, modes}).catch(() => null);
-}
-
-function bgDefinitionAdd(definition, mode) {
-    return bgInvoke('definitionAdd', {definition, mode});
-}
-
-function bgNoteView(noteId) {
-    return bgInvoke('noteView', {noteId});
-}
-
-/*
- * Document
- */
-
-function docOffsetCalc(element) {
-    const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
-    const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
-
-    const clientTop = document.documentElement.clientTop || document.body.clientTop || 0;
-    const clientLeft = document.documentElement.clientLeft || document.body.clientLeft || 0;
-
-    const rect = element.getBoundingClientRect();
-    const top  = Math.round(rect.top +  scrollTop - clientTop);
-    const left = Math.round(rect.left + scrollLeft - clientLeft);
-
-    return {top, left};
-}
-
-function docImposterCreate(element) {
-    const styleProps = window.getComputedStyle(element);
-    const stylePairs = [];
-    for (const key of styleProps) {
-        stylePairs.push(`${key}: ${styleProps[key]};`);
-    }
-
-    const offset = docOffsetCalc(element);
-    const imposter = document.createElement('div');
-    imposter.className = 'yomichan-imposter';
-    imposter.innerText = element.value;
-    imposter.style.cssText = stylePairs.join('\n');
-    imposter.style.position = 'absolute';
-    imposter.style.top = `${offset.top}px`;
-    imposter.style.left = `${offset.left}px`;
-    imposter.style.zIndex = 2147483646;
-    if (element.nodeName === 'TEXTAREA' && styleProps.overflow === 'visible') {
-        imposter.style.overflow = 'auto';
-    }
-
-    document.body.appendChild(imposter);
-    imposter.scrollTop = element.scrollTop;
-    imposter.scrollLeft = element.scrollLeft;
-}
-
-function docImposterDestroy() {
-    for (const element of document.getElementsByClassName('yomichan-imposter')) {
-        element.parentNode.removeChild(element);
-    }
-}
-
-function docRangeFromPoint(point) {
-    const element = document.elementFromPoint(point.x, point.y);
-    if (element) {
-        if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') {
-            return new TextSourceElement(element);
-        } else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
-            docImposterCreate(element);
-        }
-    }
-
-    if (!document.caretRangeFromPoint) {
-        document.caretRangeFromPoint = (x, y) => {
-            const position = document.caretPositionFromPoint(x,y);
-            if (position) {
-                const range = document.createRange();
-                range.setStart(position.offsetNode, position.offset);
-                range.setEnd(position.offsetNode, position.offset);
-                return range;
-            }
-        };
-    }
-
-    const range = document.caretRangeFromPoint(point.x, point.y);
-    if (range) {
-        return new TextSourceRange(range);
-    }
-}
-
-function docSentenceExtract(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 (c === '\n') {
-            startPos = i + 1;
-            break;
-        }
-
-        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 (c === '\n') {
-            endPos = i + 1;
-            break;
-        }
-
-        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);
-        }
-    }
-
-    const text = content.substring(startPos, endPos);
-    const padding = text.length - text.replace(/^\s+/, '').length;
-
-    return {
-        text: text.trim(),
-        offset: position - startPos - padding
-    };
-}
diff --git a/ext/manifest.json b/ext/manifest.json
index be9f3208..7a277fb2 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -15,9 +15,10 @@
     "content_scripts": [{
         "matches": ["http://*/*", "https://*/*", "file://*/*"],
         "js": [
-            "fg/js/util.js",
+            "fg/js/document.js",
             "fg/js/source-range.js",
             "fg/js/source-element.js",
+            "fg/js/background.js",
             "fg/js/popup.js",
             "fg/js/driver.js"
         ],
-- 
cgit v1.2.3


From d6c2f1cc38a2490afe79b0d8e1f24ad214ee9534 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Thu, 20 Jul 2017 21:29:27 -0700
Subject: cleanup

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

(limited to 'ext/fg')

diff --git a/ext/fg/js/driver.js b/ext/fg/js/driver.js
deleted file mode 100644
index b0cc4613..00000000
--- a/ext/fg/js/driver.js
+++ /dev/null
@@ -1,249 +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/>.
- */
-
-
-window.driver = new class {
-    constructor() {
-        this.popup = new Popup();
-        this.popupTimer = null;
-        this.lastMousePos = null;
-        this.mouseDownLeft = false;
-        this.mouseDownMiddle = false;
-        this.lastTextSource = null;
-        this.pendingLookup = false;
-        this.options = null;
-
-        bgOptionsGet().then(options => {
-            this.options = options;
-            window.addEventListener('mouseover', this.onMouseOver.bind(this));
-            window.addEventListener('mousedown', this.onMouseDown.bind(this));
-            window.addEventListener('mouseup', this.onMouseUp.bind(this));
-            window.addEventListener('mousemove', this.onMouseMove.bind(this));
-            window.addEventListener('resize', e => this.searchClear());
-            window.addEventListener('message', this.onFrameMessage.bind(this));
-            chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
-        }).catch(this.handleError.bind(this));
-    }
-
-    popupTimerSet(callback) {
-        this.popupTimerClear();
-        this.popupTimer = window.setTimeout(callback, this.options.scanning.delay);
-    }
-
-    popupTimerClear() {
-        if (this.popupTimer) {
-            window.clearTimeout(this.popupTimer);
-            this.popupTimer = null;
-        }
-    }
-
-    onMouseOver(e) {
-        if (e.target === this.popup.container && this.popupTimer) {
-            this.popupTimerClear();
-        }
-    }
-
-    onMouseMove(e) {
-        this.lastMousePos = {x: e.clientX, y: e.clientY};
-        this.popupTimerClear();
-
-        if (!this.options.general.enable) {
-            return;
-        }
-
-        if (this.mouseDownLeft) {
-            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) {
-            return;
-        }
-
-        const searchFunc = () => this.searchAt(this.lastMousePos);
-        if (this.options.scanning.modifier === 'none') {
-            this.popupTimerSet(searchFunc);
-        } else {
-            searchFunc();
-        }
-    }
-
-    onMouseDown(e) {
-        this.lastMousePos = {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;
-        }
-    }
-
-    onFrameMessage(e) {
-        const handlers = {
-            popupClose: () => {
-                this.searchClear();
-            },
-
-            selectionCopy: () => {
-                document.execCommand('copy');
-            }
-        };
-
-        const handler = handlers[e.data];
-        if (handler) {
-            handler();
-        }
-    }
-
-    onBgMessage({action, params}, sender, callback) {
-        const handlers = {
-            optionsSet: options => {
-                this.options = options;
-                if (!this.options.enable) {
-                    this.searchClear();
-                }
-            }
-        };
-
-        const handler = handlers[action];
-        if (handler) {
-            handler(params);
-        }
-
-        callback();
-    }
-
-    searchAt(point) {
-        if (this.pendingLookup) {
-            return;
-        }
-
-        const textSource = docRangeFromPoint(point);
-        if (!textSource || !textSource.containsPoint(point)) {
-            docImposterDestroy();
-            return;
-        }
-
-        if (this.lastTextSource && this.lastTextSource.equals(textSource)) {
-            return;
-        }
-
-        this.pendingLookup = true;
-        this.searchTerms(textSource).then(found => {
-            if (!found) {
-                return this.searchKanji(textSource);
-            }
-        }).catch(error => {
-            this.handleError(error, textSource);
-        }).then(() => {
-            docImposterDestroy();
-            this.pendingLookup = false;
-        });
-    }
-
-    searchTerms(textSource) {
-        textSource.setEndOffset(this.options.scanning.length);
-
-        return bgTermsFind(textSource.text()).then(({definitions, length}) => {
-            if (definitions.length === 0) {
-                return false;
-            } else {
-                textSource.setEndOffset(length);
-
-                const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
-                const url = window.location.href;
-                this.popup.showTermDefs(
-                    textSource.getRect(),
-                    definitions,
-                    this.options,
-                    {sentence, url}
-                );
-
-                this.lastTextSource = textSource;
-                if (this.options.scanning.selectText) {
-                    textSource.select();
-                }
-
-                return true;
-            }
-        });
-    }
-
-    searchKanji(textSource) {
-        textSource.setEndOffset(1);
-
-        return bgKanjiFind(textSource.text()).then(definitions => {
-            if (definitions.length === 0) {
-                return false;
-            } else {
-                const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
-                const url = window.location.href;
-                this.popup.showKanjiDefs(
-                    textSource.getRect(),
-                    definitions,
-                    this.options,
-                    {sentence, url}
-                );
-
-                this.lastTextSource = textSource;
-                if (this.options.scanning.selectText) {
-                    textSource.select();
-                }
-
-                return true;
-            }
-        });
-    }
-
-    searchClear() {
-        docImposterDestroy();
-        this.popup.hide();
-
-        if (this.options.scanning.selectText && this.lastTextSource) {
-            this.lastTextSource.deselect();
-        }
-
-        this.lastTextSource = null;
-    }
-
-    handleError(error, textSource) {
-        if (window.orphaned) {
-            if (textSource && this.options.scanning.modifier !== 'none') {
-                this.popup.showOrphaned(textSource.getRect(), this.options);
-            }
-        } else {
-            window.alert(`Error: ${error}`);
-        }
-    }
-};
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
new file mode 100644
index 00000000..5a51af6d
--- /dev/null
+++ b/ext/fg/js/frontend.js
@@ -0,0 +1,249 @@
+/*
+ * 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/>.
+ */
+
+
+window.frontend = new class {
+    constructor() {
+        this.popup = new Popup();
+        this.popupTimer = null;
+        this.lastMousePos = null;
+        this.mouseDownLeft = false;
+        this.mouseDownMiddle = false;
+        this.lastTextSource = null;
+        this.pendingLookup = false;
+        this.options = null;
+
+        bgOptionsGet().then(options => {
+            this.options = options;
+            window.addEventListener('mouseover', this.onMouseOver.bind(this));
+            window.addEventListener('mousedown', this.onMouseDown.bind(this));
+            window.addEventListener('mouseup', this.onMouseUp.bind(this));
+            window.addEventListener('mousemove', this.onMouseMove.bind(this));
+            window.addEventListener('resize', e => this.searchClear());
+            window.addEventListener('message', this.onFrameMessage.bind(this));
+            chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
+        }).catch(this.handleError.bind(this));
+    }
+
+    popupTimerSet(callback) {
+        this.popupTimerClear();
+        this.popupTimer = window.setTimeout(callback, this.options.scanning.delay);
+    }
+
+    popupTimerClear() {
+        if (this.popupTimer) {
+            window.clearTimeout(this.popupTimer);
+            this.popupTimer = null;
+        }
+    }
+
+    onMouseOver(e) {
+        if (e.target === this.popup.container && this.popupTimer) {
+            this.popupTimerClear();
+        }
+    }
+
+    onMouseMove(e) {
+        this.lastMousePos = {x: e.clientX, y: e.clientY};
+        this.popupTimerClear();
+
+        if (!this.options.general.enable) {
+            return;
+        }
+
+        if (this.mouseDownLeft) {
+            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) {
+            return;
+        }
+
+        const searchFunc = () => this.searchAt(this.lastMousePos);
+        if (this.options.scanning.modifier === 'none') {
+            this.popupTimerSet(searchFunc);
+        } else {
+            searchFunc();
+        }
+    }
+
+    onMouseDown(e) {
+        this.lastMousePos = {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;
+        }
+    }
+
+    onFrameMessage(e) {
+        const handlers = {
+            popupClose: () => {
+                this.searchClear();
+            },
+
+            selectionCopy: () => {
+                document.execCommand('copy');
+            }
+        };
+
+        const handler = handlers[e.data];
+        if (handler) {
+            handler();
+        }
+    }
+
+    onBgMessage({action, params}, sender, callback) {
+        const handlers = {
+            optionsSet: options => {
+                this.options = options;
+                if (!this.options.enable) {
+                    this.searchClear();
+                }
+            }
+        };
+
+        const handler = handlers[action];
+        if (handler) {
+            handler(params);
+        }
+
+        callback();
+    }
+
+    searchAt(point) {
+        if (this.pendingLookup) {
+            return;
+        }
+
+        const textSource = docRangeFromPoint(point);
+        if (!textSource || !textSource.containsPoint(point)) {
+            docImposterDestroy();
+            return;
+        }
+
+        if (this.lastTextSource && this.lastTextSource.equals(textSource)) {
+            return;
+        }
+
+        this.pendingLookup = true;
+        this.searchTerms(textSource).then(found => {
+            if (!found) {
+                return this.searchKanji(textSource);
+            }
+        }).catch(error => {
+            this.handleError(error, textSource);
+        }).then(() => {
+            docImposterDestroy();
+            this.pendingLookup = false;
+        });
+    }
+
+    searchTerms(textSource) {
+        textSource.setEndOffset(this.options.scanning.length);
+
+        return bgTermsFind(textSource.text()).then(({definitions, length}) => {
+            if (definitions.length === 0) {
+                return false;
+            } else {
+                textSource.setEndOffset(length);
+
+                const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
+                const url = window.location.href;
+                this.popup.showTermDefs(
+                    textSource.getRect(),
+                    definitions,
+                    this.options,
+                    {sentence, url}
+                );
+
+                this.lastTextSource = textSource;
+                if (this.options.scanning.selectText) {
+                    textSource.select();
+                }
+
+                return true;
+            }
+        });
+    }
+
+    searchKanji(textSource) {
+        textSource.setEndOffset(1);
+
+        return bgKanjiFind(textSource.text()).then(definitions => {
+            if (definitions.length === 0) {
+                return false;
+            } else {
+                const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
+                const url = window.location.href;
+                this.popup.showKanjiDefs(
+                    textSource.getRect(),
+                    definitions,
+                    this.options,
+                    {sentence, url}
+                );
+
+                this.lastTextSource = textSource;
+                if (this.options.scanning.selectText) {
+                    textSource.select();
+                }
+
+                return true;
+            }
+        });
+    }
+
+    searchClear() {
+        docImposterDestroy();
+        this.popup.hide();
+
+        if (this.options.scanning.selectText && this.lastTextSource) {
+            this.lastTextSource.deselect();
+        }
+
+        this.lastTextSource = null;
+    }
+
+    handleError(error, textSource) {
+        if (window.orphaned) {
+            if (textSource && this.options.scanning.modifier !== 'none') {
+                this.popup.showOrphaned(textSource.getRect(), this.options);
+            }
+        } else {
+            window.alert(`Error: ${error}`);
+        }
+    }
+};
diff --git a/ext/manifest.json b/ext/manifest.json
index 7a277fb2..a318e539 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -20,7 +20,7 @@
             "fg/js/source-element.js",
             "fg/js/background.js",
             "fg/js/popup.js",
-            "fg/js/driver.js"
+            "fg/js/frontend.js"
         ],
         "css": ["fg/css/client.css"]
     }],
-- 
cgit v1.2.3


From a0e2d9cb721d44a89715f3a9df10135c2aebfe29 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Thu, 20 Jul 2017 21:32:17 -0700
Subject: cleanup

---
 ext/bg/background.html |   2 +-
 ext/bg/js/backend.js   | 250 +++++++++++++++++++++++++++++++++++++++++++++++++
 ext/bg/js/instance.js  |   2 +-
 ext/bg/js/yomichan.js  | 250 -------------------------------------------------
 ext/fg/js/frontend.js  |   2 +-
 5 files changed, 253 insertions(+), 253 deletions(-)
 create mode 100644 ext/bg/js/backend.js
 delete mode 100644 ext/bg/js/yomichan.js

(limited to 'ext/fg')

diff --git a/ext/bg/background.html b/ext/bg/background.html
index de3cbf20..27d9bc41 100644
--- a/ext/bg/background.html
+++ b/ext/bg/background.html
@@ -21,6 +21,6 @@
         <script src="/bg/js/database.js"></script>
         <script src="/bg/js/deinflector.js"></script>
         <script src="/bg/js/translator.js"></script>
-        <script src="/bg/js/yomichan.js"></script>
+        <script src="/bg/js/backend.js"></script>
     </body>
 </html>
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
new file mode 100644
index 00000000..f5415d93
--- /dev/null
+++ b/ext/bg/js/backend.js
@@ -0,0 +1,250 @@
+/*
+ * 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/>.
+ */
+
+
+window.yomichanBackend = new class {
+    constructor() {
+        handlebarsRegister();
+
+        this.translator = new Translator();
+        this.anki = new AnkiNull();
+        this.options = null;
+
+        this.translator.prepare().then(optionsLoad).then(options => {
+            this.optionsSet(options);
+
+            chrome.commands.onCommand.addListener(this.onCommand.bind(this));
+            chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
+
+            if (this.options.general.showGuide) {
+                chrome.tabs.create({url: chrome.extension.getURL('/bg/guide.html')});
+            }
+        });
+    }
+
+    optionsSet(options) {
+        // In Firefox, setting options from the options UI somehow carries references
+        // to the DOM across to the background page, causing the options object to
+        // become a "DeadObject" after the options page is closed. The workaround used
+        // here is to create a deep copy of the options object.
+        this.options = JSON.parse(JSON.stringify(options));
+
+        if (!this.options.general.enable) {
+            chrome.browserAction.setBadgeBackgroundColor({color: '#d9534f'});
+            chrome.browserAction.setBadgeText({text: 'off'});
+        } else if (!dictConfigured(this.options)) {
+            chrome.browserAction.setBadgeBackgroundColor({color: '#f0ad4e'});
+            chrome.browserAction.setBadgeText({text: '!'});
+        } else {
+            chrome.browserAction.setBadgeText({text: ''});
+        }
+
+        if (this.options.anki.enable) {
+            this.anki = new AnkiConnect(this.options.anki.server);
+        } else {
+            this.anki = new AnkiNull();
+        }
+
+        chrome.tabs.query({}, tabs => {
+            for (const tab of tabs) {
+                chrome.tabs.sendMessage(tab.id, {action: 'optionsSet', params: options}, () => null);
+            }
+        });
+    }
+
+    noteFormat(definition, mode) {
+        const note = {
+            fields: {},
+            tags: this.options.anki.tags
+        };
+
+        let fields = [];
+        if (mode === 'kanji') {
+            fields = this.options.anki.kanji.fields;
+            note.deckName = this.options.anki.kanji.deck;
+            note.modelName = this.options.anki.kanji.model;
+        } else {
+            fields = this.options.anki.terms.fields;
+            note.deckName = this.options.anki.terms.deck;
+            note.modelName = this.options.anki.terms.model;
+
+            if (definition.audio) {
+                const audio = {
+                    url: definition.audio.url,
+                    filename: definition.audio.filename,
+                    skipHash: '7e2c2f954ef6051373ba916f000168dc',
+                    fields: []
+                };
+
+                for (const name in fields) {
+                    if (fields[name].includes('{audio}')) {
+                        audio.fields.push(name);
+                    }
+                }
+
+                if (audio.fields.length > 0) {
+                    note.audio = audio;
+                }
+            }
+        }
+
+        for (const name in fields) {
+            note.fields[name] = dictFieldFormat(
+                fields[name],
+                definition,
+                mode,
+                this.options
+            );
+        }
+
+        return note;
+    }
+
+    termsFind(text) {
+        const searcher = this.options.general.groupResults ?
+            this.translator.findTermsGrouped.bind(this.translator) :
+            this.translator.findTerms.bind(this.translator);
+
+        return searcher(text, dictEnabledSet(this.options), this.options.scanning.alphanumeric).then(({definitions, length}) => {
+            return {length, definitions: definitions.slice(0, this.options.general.maxResults)};
+        });
+    }
+
+    kanjiFind(text) {
+        return this.translator.findKanji(text, dictEnabledSet(this.options)).then(definitions => {
+            return definitions.slice(0, this.options.general.maxResults);
+        });
+    }
+
+    definitionAdd(definition, mode) {
+        let promise = Promise.resolve();
+        if (mode !== 'kanji') {
+            promise = audioInject(definition, this.options.anki.terms.fields, this.options.general.audioSource);
+        }
+
+        return promise.then(() => {
+            const note = this.noteFormat(definition, mode);
+            return this.anki.addNote(note);
+        });
+    }
+
+    definitionsAddable(definitions, modes) {
+        const notes = [];
+        for (const definition of definitions) {
+            for (const mode of modes) {
+                notes.push(this.noteFormat(definition, mode));
+            }
+        }
+
+        return this.anki.canAddNotes(notes).then(raw => {
+            const states = [];
+            for (let resultBase = 0; resultBase < raw.length; resultBase += modes.length) {
+                const state = {};
+                for (let modeOffset = 0; modeOffset < modes.length; ++modeOffset) {
+                    state[modes[modeOffset]] = raw[resultBase + modeOffset];
+                }
+
+                states.push(state);
+            }
+
+            return states;
+        });
+    }
+
+    noteView(noteId) {
+        return this.anki.guiBrowse(`nid:${noteId}`);
+    }
+
+    templateRender(template, data) {
+        return Promise.resolve(handlebarsRender(template, data));
+    }
+
+    onCommand(command) {
+        const handlers = {
+            search: () => {
+                chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')});
+            },
+
+            help: () => {
+                chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'});
+            },
+
+            options: () => {
+                chrome.runtime.openOptionsPage();
+            },
+
+            toggle: () => {
+                this.options.general.enable = !this.options.general.enable;
+                optionsSave(this.options).then(() => this.optionsSet(this.options));
+            }
+        };
+
+        const handler = handlers[command];
+        if (handler) {
+            handler();
+        }
+    }
+
+    onMessage({action, params}, sender, callback) {
+        const promiseCallback = (promise, callback) => {
+            return promise.then(result => {
+                callback({result});
+            }).catch(error => {
+                callback({error});
+            });
+        };
+
+        const handlers = {
+            optionsGet: ({callback}) => {
+                promiseCallback(optionsLoad(), callback);
+            },
+
+            kanjiFind: ({text, callback}) => {
+                promiseCallback(this.kanjiFind(text), callback);
+            },
+
+            termsFind: ({text, callback}) => {
+                promiseCallback(this.termsFind(text), callback);
+            },
+
+            templateRender: ({template, data, callback}) => {
+                promiseCallback(this.templateRender(template, data), callback);
+            },
+
+            definitionAdd: ({definition, mode, callback}) => {
+                promiseCallback(this.definitionAdd(definition, mode), callback);
+            },
+
+            definitionsAddable: ({definitions, modes, callback}) => {
+                promiseCallback(this.definitionsAddable(definitions, modes), callback);
+            },
+
+            noteView: ({noteId}) => {
+                promiseCallback(this.noteView(noteId), callback);
+            }
+        };
+
+        const handler = handlers[action];
+        if (handler) {
+            params.callback = callback;
+            handler(params);
+        }
+
+        return true;
+    }
+};
diff --git a/ext/bg/js/instance.js b/ext/bg/js/instance.js
index 0df267cc..bf858fbf 100644
--- a/ext/bg/js/instance.js
+++ b/ext/bg/js/instance.js
@@ -18,7 +18,7 @@
 
 
 function instYomi() {
-    return chrome.extension.getBackgroundPage().yomichan;
+    return chrome.extension.getBackgroundPage().yomichanBackend;
 }
 
 function instDb() {
diff --git a/ext/bg/js/yomichan.js b/ext/bg/js/yomichan.js
deleted file mode 100644
index 214bdef3..00000000
--- a/ext/bg/js/yomichan.js
+++ /dev/null
@@ -1,250 +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/>.
- */
-
-
-window.yomichan = new class {
-    constructor() {
-        handlebarsRegister();
-
-        this.translator = new Translator();
-        this.anki = new AnkiNull();
-        this.options = null;
-
-        this.translator.prepare().then(optionsLoad).then(options => {
-            this.optionsSet(options);
-
-            chrome.commands.onCommand.addListener(this.onCommand.bind(this));
-            chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
-
-            if (this.options.general.showGuide) {
-                chrome.tabs.create({url: chrome.extension.getURL('/bg/guide.html')});
-            }
-        });
-    }
-
-    optionsSet(options) {
-        // In Firefox, setting options from the options UI somehow carries references
-        // to the DOM across to the background page, causing the options object to
-        // become a "DeadObject" after the options page is closed. The workaround used
-        // here is to create a deep copy of the options object.
-        this.options = JSON.parse(JSON.stringify(options));
-
-        if (!this.options.general.enable) {
-            chrome.browserAction.setBadgeBackgroundColor({color: '#d9534f'});
-            chrome.browserAction.setBadgeText({text: 'off'});
-        } else if (!dictConfigured(this.options)) {
-            chrome.browserAction.setBadgeBackgroundColor({color: '#f0ad4e'});
-            chrome.browserAction.setBadgeText({text: '!'});
-        } else {
-            chrome.browserAction.setBadgeText({text: ''});
-        }
-
-        if (this.options.anki.enable) {
-            this.anki = new AnkiConnect(this.options.anki.server);
-        } else {
-            this.anki = new AnkiNull();
-        }
-
-        chrome.tabs.query({}, tabs => {
-            for (const tab of tabs) {
-                chrome.tabs.sendMessage(tab.id, {action: 'optionsSet', params: options}, () => null);
-            }
-        });
-    }
-
-    noteFormat(definition, mode) {
-        const note = {
-            fields: {},
-            tags: this.options.anki.tags
-        };
-
-        let fields = [];
-        if (mode === 'kanji') {
-            fields = this.options.anki.kanji.fields;
-            note.deckName = this.options.anki.kanji.deck;
-            note.modelName = this.options.anki.kanji.model;
-        } else {
-            fields = this.options.anki.terms.fields;
-            note.deckName = this.options.anki.terms.deck;
-            note.modelName = this.options.anki.terms.model;
-
-            if (definition.audio) {
-                const audio = {
-                    url: definition.audio.url,
-                    filename: definition.audio.filename,
-                    skipHash: '7e2c2f954ef6051373ba916f000168dc',
-                    fields: []
-                };
-
-                for (const name in fields) {
-                    if (fields[name].includes('{audio}')) {
-                        audio.fields.push(name);
-                    }
-                }
-
-                if (audio.fields.length > 0) {
-                    note.audio = audio;
-                }
-            }
-        }
-
-        for (const name in fields) {
-            note.fields[name] = dictFieldFormat(
-                fields[name],
-                definition,
-                mode,
-                this.options
-            );
-        }
-
-        return note;
-    }
-
-    termsFind(text) {
-        const searcher = this.options.general.groupResults ?
-            this.translator.findTermsGrouped.bind(this.translator) :
-            this.translator.findTerms.bind(this.translator);
-
-        return searcher(text, dictEnabledSet(this.options), this.options.scanning.alphanumeric).then(({definitions, length}) => {
-            return {length, definitions: definitions.slice(0, this.options.general.maxResults)};
-        });
-    }
-
-    kanjiFind(text) {
-        return this.translator.findKanji(text, dictEnabledSet(this.options)).then(definitions => {
-            return definitions.slice(0, this.options.general.maxResults);
-        });
-    }
-
-    definitionAdd(definition, mode) {
-        let promise = Promise.resolve();
-        if (mode !== 'kanji') {
-            promise = audioInject(definition, this.options.anki.terms.fields, this.options.general.audioSource);
-        }
-
-        return promise.then(() => {
-            const note = this.noteFormat(definition, mode);
-            return this.anki.addNote(note);
-        });
-    }
-
-    definitionsAddable(definitions, modes) {
-        const notes = [];
-        for (const definition of definitions) {
-            for (const mode of modes) {
-                notes.push(this.noteFormat(definition, mode));
-            }
-        }
-
-        return this.anki.canAddNotes(notes).then(raw => {
-            const states = [];
-            for (let resultBase = 0; resultBase < raw.length; resultBase += modes.length) {
-                const state = {};
-                for (let modeOffset = 0; modeOffset < modes.length; ++modeOffset) {
-                    state[modes[modeOffset]] = raw[resultBase + modeOffset];
-                }
-
-                states.push(state);
-            }
-
-            return states;
-        });
-    }
-
-    noteView(noteId) {
-        return this.anki.guiBrowse(`nid:${noteId}`);
-    }
-
-    templateRender(template, data) {
-        return Promise.resolve(handlebarsRender(template, data));
-    }
-
-    onCommand(command) {
-        const handlers = {
-            search: () => {
-                chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')});
-            },
-
-            help: () => {
-                chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'});
-            },
-
-            options: () => {
-                chrome.runtime.openOptionsPage();
-            },
-
-            toggle: () => {
-                this.options.general.enable = !this.options.general.enable;
-                optionsSave(this.options).then(() => this.optionsSet(this.options));
-            }
-        };
-
-        const handler = handlers[command];
-        if (handler) {
-            handler();
-        }
-    }
-
-    onMessage({action, params}, sender, callback) {
-        const promiseCallback = (promise, callback) => {
-            return promise.then(result => {
-                callback({result});
-            }).catch(error => {
-                callback({error});
-            });
-        };
-
-        const handlers = {
-            optionsGet: ({callback}) => {
-                promiseCallback(optionsLoad(), callback);
-            },
-
-            kanjiFind: ({text, callback}) => {
-                promiseCallback(this.kanjiFind(text), callback);
-            },
-
-            termsFind: ({text, callback}) => {
-                promiseCallback(this.termsFind(text), callback);
-            },
-
-            templateRender: ({template, data, callback}) => {
-                promiseCallback(this.templateRender(template, data), callback);
-            },
-
-            definitionAdd: ({definition, mode, callback}) => {
-                promiseCallback(this.definitionAdd(definition, mode), callback);
-            },
-
-            definitionsAddable: ({definitions, modes, callback}) => {
-                promiseCallback(this.definitionsAddable(definitions, modes), callback);
-            },
-
-            noteView: ({noteId}) => {
-                promiseCallback(this.noteView(noteId), callback);
-            }
-        };
-
-        const handler = handlers[action];
-        if (handler) {
-            params.callback = callback;
-            handler(params);
-        }
-
-        return true;
-    }
-};
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 5a51af6d..e32a630d 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -17,7 +17,7 @@
  */
 
 
-window.frontend = new class {
+window.yomichanFrontend = new class {
     constructor() {
         this.popup = new Popup();
         this.popupTimer = null;
-- 
cgit v1.2.3


From 611b4420af9aa5c8ad6287996b73ae8fd2eb2f4b Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Thu, 20 Jul 2017 21:34:10 -0700
Subject: cleanup

---
 ext/fg/js/background.js    | 2 +-
 ext/fg/js/display-frame.js | 2 +-
 ext/fg/js/frontend.js      | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/fg/js/background.js b/ext/fg/js/background.js
index 52b533be..c072468b 100644
--- a/ext/fg/js/background.js
+++ b/ext/fg/js/background.js
@@ -28,7 +28,7 @@ function bgInvoke(action, params={}) {
                 }
             });
         } catch (e) {
-            window.orphaned = true;
+            window.yomichanOrphaned = true;
             reject(e.message);
         }
     });
diff --git a/ext/fg/js/display-frame.js b/ext/fg/js/display-frame.js
index b29a0379..c7da43e8 100644
--- a/ext/fg/js/display-frame.js
+++ b/ext/fg/js/display-frame.js
@@ -44,7 +44,7 @@ window.displayFrame = new class extends Display {
     }
 
     handleError(error) {
-        if (window.orphaned) {
+        if (window.yomichanOrphaned) {
             this.showOrphaned();
         } else {
             window.alert(`Error: ${error}`);
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index e32a630d..8b4c182c 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -238,7 +238,7 @@ window.yomichanFrontend = new class {
     }
 
     handleError(error, textSource) {
-        if (window.orphaned) {
+        if (window.yomichanOrphaned) {
             if (textSource && this.options.scanning.modifier !== 'none') {
                 this.popup.showOrphaned(textSource.getRect(), this.options);
             }
-- 
cgit v1.2.3


From edf1c0ff6d9eadd17c98f00ef027c27d1b89a8ee Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sat, 22 Jul 2017 23:19:38 -0700
Subject: cleanup

---
 ext/fg/frame.html          |  2 +-
 ext/fg/js/api.js           | 63 ++++++++++++++++++++++++++++++++++++++++++++++
 ext/fg/js/background.js    | 63 ----------------------------------------------
 ext/fg/js/display-frame.js | 10 ++++----
 ext/fg/js/frontend.js      |  6 ++---
 ext/manifest.json          |  2 +-
 6 files changed, 73 insertions(+), 73 deletions(-)
 create mode 100644 ext/fg/js/api.js
 delete mode 100644 ext/fg/js/background.js

(limited to 'ext/fg')

diff --git a/ext/fg/frame.html b/ext/fg/frame.html
index 80e69967..9ff2c585 100644
--- a/ext/fg/frame.html
+++ b/ext/fg/frame.html
@@ -35,7 +35,7 @@
         <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
         <script src="/fg/js/dictionary.js"></script>
-        <script src="/fg/js/background.js"></script>
+        <script src="/fg/js/api.js"></script>
         <script src="/fg/js/display-frame.js"></script>
     </body>
 </html>
diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js
new file mode 100644
index 00000000..e252637e
--- /dev/null
+++ b/ext/fg/js/api.js
@@ -0,0 +1,63 @@
+/*
+ * 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 apiInvoke(action, params={}) {
+    return new Promise((resolve, reject) => {
+        try {
+            chrome.runtime.sendMessage({action, params}, ({result, error}) => {
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(result);
+                }
+            });
+        } catch (e) {
+            window.yomichanOrphaned = true;
+            reject(e.message);
+        }
+    });
+}
+
+function apiOptionsGet() {
+    return apiInvoke('optionsGet');
+}
+
+function apiTermsFind(text) {
+    return apiInvoke('termsFind', {text});
+}
+
+function apiKanjiFind(text) {
+    return apiInvoke('kanjiFind', {text});
+}
+
+function apiTemplateRender(template, data) {
+    return apiInvoke('templateRender', {data, template});
+}
+
+function apiDefinitionsAddable(definitions, modes) {
+    return apiInvoke('definitionsAddable', {definitions, modes}).catch(() => null);
+}
+
+function apiDefinitionAdd(definition, mode) {
+    return apiInvoke('definitionAdd', {definition, mode});
+}
+
+function apiNoteView(noteId) {
+    return apiInvoke('noteView', {noteId});
+}
diff --git a/ext/fg/js/background.js b/ext/fg/js/background.js
deleted file mode 100644
index c072468b..00000000
--- a/ext/fg/js/background.js
+++ /dev/null
@@ -1,63 +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 bgInvoke(action, params={}) {
-    return new Promise((resolve, reject) => {
-        try {
-            chrome.runtime.sendMessage({action, params}, ({result, error}) => {
-                if (error) {
-                    reject(error);
-                } else {
-                    resolve(result);
-                }
-            });
-        } catch (e) {
-            window.yomichanOrphaned = true;
-            reject(e.message);
-        }
-    });
-}
-
-function bgOptionsGet() {
-    return bgInvoke('optionsGet');
-}
-
-function bgTermsFind(text) {
-    return bgInvoke('termsFind', {text});
-}
-
-function bgKanjiFind(text) {
-    return bgInvoke('kanjiFind', {text});
-}
-
-function bgTemplateRender(template, data) {
-    return bgInvoke('templateRender', {data, template});
-}
-
-function bgDefinitionsAddable(definitions, modes) {
-    return bgInvoke('definitionsAddable', {definitions, modes}).catch(() => null);
-}
-
-function bgDefinitionAdd(definition, mode) {
-    return bgInvoke('definitionAdd', {definition, mode});
-}
-
-function bgNoteView(noteId) {
-    return bgInvoke('noteView', {noteId});
-}
diff --git a/ext/fg/js/display-frame.js b/ext/fg/js/display-frame.js
index c7da43e8..09bd9255 100644
--- a/ext/fg/js/display-frame.js
+++ b/ext/fg/js/display-frame.js
@@ -24,23 +24,23 @@ window.displayFrame = new class extends Display {
     }
 
     definitionAdd(definition, mode) {
-        return bgDefinitionAdd(definition, mode);
+        return apiDefinitionAdd(definition, mode);
     }
 
     definitionsAddable(definitions, modes) {
-        return bgDefinitionsAddable(definitions, modes);
+        return apiDefinitionsAddable(definitions, modes);
     }
 
     noteView(noteId) {
-        return bgNoteView(noteId);
+        return apiNoteView(noteId);
     }
 
     templateRender(template, data) {
-        return bgTemplateRender(template, data);
+        return apiTemplateRender(template, data);
     }
 
     kanjiFind(character) {
-        return bgKanjiFind(character);
+        return apiKanjiFind(character);
     }
 
     handleError(error) {
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 8b4c182c..9974d878 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -28,7 +28,7 @@ window.yomichanFrontend = new class {
         this.pendingLookup = false;
         this.options = null;
 
-        bgOptionsGet().then(options => {
+        apiOptionsGet().then(options => {
             this.options = options;
             window.addEventListener('mouseover', this.onMouseOver.bind(this));
             window.addEventListener('mousedown', this.onMouseDown.bind(this));
@@ -175,7 +175,7 @@ window.yomichanFrontend = new class {
     searchTerms(textSource) {
         textSource.setEndOffset(this.options.scanning.length);
 
-        return bgTermsFind(textSource.text()).then(({definitions, length}) => {
+        return apiTermsFind(textSource.text()).then(({definitions, length}) => {
             if (definitions.length === 0) {
                 return false;
             } else {
@@ -203,7 +203,7 @@ window.yomichanFrontend = new class {
     searchKanji(textSource) {
         textSource.setEndOffset(1);
 
-        return bgKanjiFind(textSource.text()).then(definitions => {
+        return apiKanjiFind(textSource.text()).then(definitions => {
             if (definitions.length === 0) {
                 return false;
             } else {
diff --git a/ext/manifest.json b/ext/manifest.json
index a318e539..288976f3 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -18,7 +18,7 @@
             "fg/js/document.js",
             "fg/js/source-range.js",
             "fg/js/source-element.js",
-            "fg/js/background.js",
+            "fg/js/api.js",
             "fg/js/popup.js",
             "fg/js/frontend.js"
         ],
-- 
cgit v1.2.3


From b061cc914210e6c11bc419c38ec850b59d72dd4d Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sat, 29 Jul 2017 09:55:54 -0700
Subject: cleanup

---
 ext/bg/js/backend.js        |   2 -
 ext/bg/js/dictionary.js     |   2 +-
 ext/bg/js/display-window.js |  14 +++--
 ext/bg/js/handlebars.js     |  14 ++---
 ext/bg/js/settings.js       |   2 -
 ext/bg/js/templates.js      | 136 ++++++++++++++++++++++----------------------
 ext/bg/search.html          |  12 +++-
 ext/fg/frame.html           |   9 +--
 8 files changed, 99 insertions(+), 92 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index 2763a141..f7823047 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -19,8 +19,6 @@
 
 window.yomichan = new class {
     constructor() {
-        handlebarsRegister();
-
         this.translator = new Translator();
         this.anki = new AnkiNull();
         this.options = null;
diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js
index 6f4ec809..73efe7d1 100644
--- a/ext/bg/js/dictionary.js
+++ b/ext/bg/js/dictionary.js
@@ -225,7 +225,7 @@ function dictFieldFormat(field, definition, mode, options) {
 
         field = field.replace(
             `{${marker}}`,
-            Handlebars.templates['fields.html'](data).trim()
+            handlebarsRender('fields.html', data)
         );
     }
 
diff --git a/ext/bg/js/display-window.js b/ext/bg/js/display-window.js
index e5357bf9..1f607510 100644
--- a/ext/bg/js/display-window.js
+++ b/ext/bg/js/display-window.js
@@ -37,11 +37,15 @@ window.displayWindow = new class extends Display {
         $('#query').focus().select();
     }
 
-    onSearch(e) {
+    async onSearch(e) {
         e.preventDefault();
-        $('#intro').slideUp();
-        instYomi().termsFind($('#query').val()).then(({length, definitions}) => {
-            super.showTermDefs(definitions, instYomi().options);
-        }).catch(this.handleError.bind(this));
+
+        try {
+            $('#intro').slideUp();
+            const {length, definitions} = await apiTermsFind($('#query').val());
+            super.showTermDefs(definitions, await apiOptionsGet());
+        } catch (e) {
+            this.handleError(e);
+        }
     }
 };
diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js
index 42b36927..df98bef1 100644
--- a/ext/bg/js/handlebars.js
+++ b/ext/bg/js/handlebars.js
@@ -43,13 +43,13 @@ function handlebarsMultiLine(options) {
     return options.fn(this).split('\n').join('<br>');
 }
 
-function handlebarsRegister() {
-    Handlebars.partials = Handlebars.templates;
-    Handlebars.registerHelper('dumpObject', handlebarsDumpObject);
-    Handlebars.registerHelper('kanjiLinks', handlebarsKanjiLinks);
-    Handlebars.registerHelper('multiLine', handlebarsMultiLine);
-}
-
 function handlebarsRender(template, data) {
+    if (Handlebars.partials !== Handlebars.templates) {
+        Handlebars.partials = Handlebars.templates;
+        Handlebars.registerHelper('dumpObject', handlebarsDumpObject);
+        Handlebars.registerHelper('kanjiLinks', handlebarsKanjiLinks);
+        Handlebars.registerHelper('multiLine', handlebarsMultiLine);
+    }
+
     return Handlebars.templates[template](data);
 }
diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js
index a0406e0c..fa44c8da 100644
--- a/ext/bg/js/settings.js
+++ b/ext/bg/js/settings.js
@@ -158,8 +158,6 @@ async function onFormOptionsChanged(e) {(async () => {
 })();}
 
 function onReady() {(async () => {
-    handlebarsRegister();
-
     const options = await optionsLoad();
 
     $('#show-usage-guide').prop('checked', options.general.showGuide);
diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js
index 6fa1dad0..7e05015a 100644
--- a/ext/bg/js/templates.js
+++ b/ext/bg/js/templates.js
@@ -3,7 +3,7 @@
 templates['dictionary.html'] = template({"1":function(container,depth0,helpers,partials,data) {
     return "checked";
 },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
-    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+    var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
 
   return "<div class=\"dict-group well well-sm\" data-title=\""
     + alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
@@ -24,24 +24,24 @@ templates['dictionary.html'] = template({"1":function(container,depth0,helpers,p
 templates['fields.html'] = template({"1":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(15, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(15, data, 0),"data":data})) != null ? stack1 : "");
 },"2":function(container,depth0,helpers,partials,data) {
-    var stack1, alias1=depth0 != null ? depth0 : {};
+    var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
 
   return ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.brief : depth0),{"name":"unless","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.program(12, data, 0),"data":data})) != null ? stack1 : "");
 },"3":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"4":function(container,depth0,helpers,partials,data) {
     var stack1;
 
   return "<i>("
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + ")</i> ";
 },"5":function(container,depth0,helpers,partials,data) {
-    var stack1, helper, alias1=depth0 != null ? depth0 : {};
+    var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
 
   return container.escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
     + ((stack1 = helpers.unless.call(alias1,(data && data.last),{"name":"unless","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
@@ -51,12 +51,12 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
     var stack1;
 
   return "<ul>"
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "</ul>";
 },"9":function(container,depth0,helpers,partials,data) {
     var stack1, helper, options, buffer = 
   "<li>";
-  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
   if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
   if (stack1 != null) { buffer += stack1; }
   return buffer + "</li>";
@@ -65,7 +65,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"12":function(container,depth0,helpers,partials,data) {
     var stack1, helper, options, buffer = "";
 
-  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
   if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
   if (stack1 != null) { buffer += stack1; }
   return buffer;
@@ -74,29 +74,29 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 
   return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
 },"15":function(container,depth0,helpers,partials,data) {
-    var stack1, alias1=depth0 != null ? depth0 : {};
+    var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
 
   return ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.brief : depth0),{"name":"unless","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(19, data, 0),"inverse":container.program(13, data, 0),"data":data})) != null ? stack1 : "");
 },"16":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"17":function(container,depth0,helpers,partials,data) {
     var stack1;
 
   return "("
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + ") ";
 },"19":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"20":function(container,depth0,helpers,partials,data) {
     var stack1;
 
   return container.escapeExpression(container.lambda(depth0, depth0))
-    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.last),{"name":"unless","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"22":function(container,depth0,helpers,partials,data) {
     return "";
 },"24":function(container,depth0,helpers,partials,data) {
@@ -110,11 +110,11 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"28":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.modeTermKana : depth0),{"name":"if","hash":{},"fn":container.program(29, data, 0),"inverse":container.program(32, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.modeTermKana : depth0),{"name":"if","hash":{},"fn":container.program(29, data, 0),"inverse":container.program(32, data, 0),"data":data})) != null ? stack1 : "");
 },"29":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.reading : stack1),{"name":"if","hash":{},"fn":container.program(30, data, 0),"inverse":container.program(32, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.reading : stack1),{"name":"if","hash":{},"fn":container.program(30, data, 0),"inverse":container.program(32, data, 0),"data":data})) != null ? stack1 : "");
 },"30":function(container,depth0,helpers,partials,data) {
     var stack1;
 
@@ -126,11 +126,11 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"34":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(35, data, 0),"inverse":container.program(38, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(35, data, 0),"inverse":container.program(38, data, 0),"data":data})) != null ? stack1 : "");
 },"35":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.reading : stack1),{"name":"if","hash":{},"fn":container.program(36, data, 0),"inverse":container.program(32, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.reading : stack1),{"name":"if","hash":{},"fn":container.program(36, data, 0),"inverse":container.program(32, data, 0),"data":data})) != null ? stack1 : "");
 },"36":function(container,depth0,helpers,partials,data) {
     var stack1, alias1=container.lambda, alias2=container.escapeExpression;
 
@@ -142,7 +142,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"38":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.reading : stack1),{"name":"if","hash":{},"fn":container.program(39, data, 0),"inverse":container.program(32, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.reading : stack1),{"name":"if","hash":{},"fn":container.program(39, data, 0),"inverse":container.program(32, data, 0),"data":data})) != null ? stack1 : "");
 },"39":function(container,depth0,helpers,partials,data) {
     var stack1, alias1=container.lambda, alias2=container.escapeExpression;
 
@@ -151,7 +151,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
     + alias2(alias1(((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.reading : stack1), depth0))
     + "]";
 },"41":function(container,depth0,helpers,partials,data,blockParams,depths) {
-    var stack1, alias1=depth0 != null ? depth0 : {};
+    var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
 
   return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(42, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.modeKanji : depth0),{"name":"if","hash":{},"fn":container.program(44, data, 0, blockParams, depths),"inverse":container.program(53, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
@@ -161,16 +161,16 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"44":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = ((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.glossary : stack1)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(45, data, 0),"inverse":container.program(51, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = ((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.glossary : stack1)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(45, data, 0),"inverse":container.program(51, data, 0),"data":data})) != null ? stack1 : "");
 },"45":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(46, data, 0),"inverse":container.program(49, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(46, data, 0),"inverse":container.program(49, data, 0),"data":data})) != null ? stack1 : "");
 },"46":function(container,depth0,helpers,partials,data) {
     var stack1;
 
   return "<ol>"
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.glossary : stack1),{"name":"each","hash":{},"fn":container.program(47, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.glossary : stack1),{"name":"each","hash":{},"fn":container.program(47, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "</ol>";
 },"47":function(container,depth0,helpers,partials,data) {
     return "<li>"
@@ -179,7 +179,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"49":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.glossary : stack1),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.glossary : stack1),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"51":function(container,depth0,helpers,partials,data) {
     var stack1;
 
@@ -187,20 +187,20 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"53":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.group : depth0),{"name":"if","hash":{},"fn":container.program(54, data, 0, blockParams, depths),"inverse":container.program(64, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.group : depth0),{"name":"if","hash":{},"fn":container.program(54, data, 0, blockParams, depths),"inverse":container.program(64, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
 },"54":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = ((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.definitions : stack1)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(55, data, 0, blockParams, depths),"inverse":container.program(62, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = ((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.definitions : stack1)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(55, data, 0, blockParams, depths),"inverse":container.program(62, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
 },"55":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(56, data, 0, blockParams, depths),"inverse":container.program(59, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(56, data, 0, blockParams, depths),"inverse":container.program(59, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
 },"56":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
   return "<ol>"
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.definitions : stack1),{"name":"each","hash":{},"fn":container.program(57, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.definitions : stack1),{"name":"each","hash":{},"fn":container.program(57, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "</ol>";
 },"57":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
@@ -211,7 +211,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"59":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
-  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.definitions : stack1),{"name":"each","hash":{},"fn":container.program(60, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.definitions : stack1),{"name":"each","hash":{},"fn":container.program(60, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"60":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
@@ -234,19 +234,19 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"70":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.kunyomi : stack1),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.kunyomi : stack1),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"72":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.onyomi : stack1),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.onyomi : stack1),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"74":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.modeTermKana : depth0),{"name":"unless","hash":{},"fn":container.program(30, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.modeTermKana : depth0),{"name":"unless","hash":{},"fn":container.program(30, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"76":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.cloze : stack1),{"name":"if","hash":{},"fn":container.program(77, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.cloze : stack1),{"name":"if","hash":{},"fn":container.program(77, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"77":function(container,depth0,helpers,partials,data) {
     var stack1;
 
@@ -254,7 +254,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"79":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.cloze : stack1),{"name":"if","hash":{},"fn":container.program(80, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.cloze : stack1),{"name":"if","hash":{},"fn":container.program(80, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"80":function(container,depth0,helpers,partials,data) {
     var stack1;
 
@@ -262,7 +262,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"82":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.cloze : stack1),{"name":"if","hash":{},"fn":container.program(83, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.cloze : stack1),{"name":"if","hash":{},"fn":container.program(83, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"83":function(container,depth0,helpers,partials,data) {
     var stack1;
 
@@ -270,7 +270,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"85":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.cloze : stack1),{"name":"if","hash":{},"fn":container.program(86, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.cloze : stack1),{"name":"if","hash":{},"fn":container.program(86, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"86":function(container,depth0,helpers,partials,data) {
     var stack1;
 
@@ -278,11 +278,11 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 },"88":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.tags : stack1),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definition : depth0)) != null ? stack1.tags : stack1),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"90":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(91, data, 0),"inverse":container.program(93, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(91, data, 0),"inverse":container.program(93, data, 0),"data":data})) != null ? stack1 : "");
 },"91":function(container,depth0,helpers,partials,data) {
     var stack1, alias1=container.lambda, alias2=container.escapeExpression;
 
@@ -299,7 +299,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
     var stack1;
 
   return "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
-    + ((stack1 = container.invokePartial(helpers.lookup.call(depth0 != null ? depth0 : {},depth0,"marker",{"name":"lookup","hash":{},"data":data}),depth0,{"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+    + ((stack1 = container.invokePartial(helpers.lookup.call(depth0 != null ? depth0 : (container.nullContext || {}),depth0,"marker",{"name":"lookup","hash":{},"data":data}),depth0,{"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
 },"main_d":  function(fn, props, container, depth0, data, blockParams, depths) {
 
   var decorators = container.decorators;
@@ -326,7 +326,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
 
 ,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true});
 templates['kanji.html'] = template({"1":function(container,depth0,helpers,partials,data) {
-    var stack1, helper, alias1=depth0 != null ? depth0 : {};
+    var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
 
   return "<div class=\"entry\" data-type=\"kanji\">\n    <div class=\"actions\">\n"
     + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
@@ -353,12 +353,12 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
 
   return "                    "
     + container.escapeExpression(container.lambda(depth0, depth0))
-    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.last),{"name":"unless","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "\n";
 },"7":function(container,depth0,helpers,partials,data) {
     return ", ";
 },"9":function(container,depth0,helpers,partials,data) {
-    var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+    var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
 
   return "        <span class=\"label label-default tag-"
     + alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
@@ -371,12 +371,12 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
     var stack1;
 
   return "        <ol>\n"
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(12, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(12, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "        </ol>\n";
 },"12":function(container,depth0,helpers,partials,data) {
     var stack1, helper, options, buffer = 
   "            <li><span class=\"glossary-item\">";
-  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
   if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
   if (stack1 != null) { buffer += stack1; }
   return buffer + "</span></li>\n";
@@ -385,7 +385,7 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
 },"15":function(container,depth0,helpers,partials,data) {
     var stack1, helper, options, buffer = 
   "        <div class=\"glossary-item\">";
-  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
   if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
   if (stack1 != null) { buffer += stack1; }
   return buffer + "</div>\n";
@@ -396,7 +396,7 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
 },"18":function(container,depth0,helpers,partials,data) {
     var stack1, helper, options, buffer = 
   "    <pre>";
-  stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(19, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+  stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(19, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
   if (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
   if (stack1 != null) { buffer += stack1; }
   return buffer + "</pre>\n";
@@ -407,11 +407,11 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
 },"21":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
-  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(22, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(22, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"22":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
-  return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.first),{"name":"unless","hash":{},"fn":container.program(23, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+  return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(23, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "\n"
     + ((stack1 = container.invokePartial(partials.kanji,depth0,{"name":"kanji","hash":{"root":(depths[1] != null ? depths[1].root : depths[1]),"source":(depths[1] != null ? depths[1].source : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
 },"23":function(container,depth0,helpers,partials,data) {
@@ -422,7 +422,7 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
     var stack1;
 
   return "\n"
-    + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(21, data, 0, blockParams, depths),"inverse":container.program(25, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+    + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(21, data, 0, blockParams, depths),"inverse":container.program(25, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
 },"main_d":  function(fn, props, container, depth0, data, blockParams, depths) {
 
   var decorators = container.decorators;
@@ -437,7 +437,7 @@ templates['model.html'] = template({"1":function(container,depth0,helpers,partia
     + container.escapeExpression(container.lambda(depth0, depth0))
     + "</a></li>\n";
 },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
-    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+    var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
 
   return "<tr>\n    <td class=\"col-sm-2\">"
     + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
@@ -450,7 +450,7 @@ templates['model.html'] = template({"1":function(container,depth0,helpers,partia
     + "                </ul>\n            </div>\n        </div>\n    </td>\n</tr>\n";
 },"useData":true});
 templates['terms.html'] = template({"1":function(container,depth0,helpers,partials,data) {
-    var stack1, alias1=depth0 != null ? depth0 : {};
+    var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
 
   return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.program(9, data, 0),"data":data})) != null ? stack1 : "");
@@ -458,10 +458,10 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
     var stack1;
 
   return "<div>\n"
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "</div>\n";
 },"3":function(container,depth0,helpers,partials,data) {
-    var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+    var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
 
   return "    <span class=\"label label-default tag-"
     + alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
@@ -474,12 +474,12 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
     var stack1;
 
   return "<ul>\n"
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "</ul>\n";
 },"6":function(container,depth0,helpers,partials,data) {
     var stack1, helper, options, buffer = 
   "    <li><span class=\"glossary-item\">";
-  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
   if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
   if (stack1 != null) { buffer += stack1; }
   return buffer + "</span></li>\n";
@@ -488,7 +488,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
 },"9":function(container,depth0,helpers,partials,data) {
     var stack1, helper, options, buffer = 
   "<div class=\"glossary-item\">";
-  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
   if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
   if (stack1 != null) { buffer += stack1; }
   return buffer + "</div>\n";
@@ -497,7 +497,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
 
   return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
 },"12":function(container,depth0,helpers,partials,data) {
-    var stack1, alias1=depth0 != null ? depth0 : {};
+    var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
 
   return "<div class=\"entry\" data-type=\"term\">\n    <div class=\"actions\">\n"
     + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
@@ -516,7 +516,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
 },"15":function(container,depth0,helpers,partials,data) {
     return "        <a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" title=\"Play audio (Alt + P)\" alt></a>\n";
 },"17":function(container,depth0,helpers,partials,data) {
-    var stack1, helper, options, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", buffer = 
+    var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", buffer = 
   "    <div class=\"expression\"><ruby>";
   stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : alias2),(options={"name":"kanjiLinks","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
   if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
@@ -527,11 +527,11 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
 },"18":function(container,depth0,helpers,partials,data) {
     var helper;
 
-  return container.escapeExpression(((helper = (helper = helpers.expression || (depth0 != null ? depth0.expression : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"expression","hash":{},"data":data}) : helper)));
+  return container.escapeExpression(((helper = (helper = helpers.expression || (depth0 != null ? depth0.expression : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"expression","hash":{},"data":data}) : helper)));
 },"20":function(container,depth0,helpers,partials,data) {
     var stack1, helper, options, buffer = 
   "    <div class=\"expression\">";
-  stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+  stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
   if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
   if (stack1 != null) { buffer += stack1; }
   return buffer + "</div>\n";
@@ -539,7 +539,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
     var stack1;
 
   return "    <div class=\"reasons\">\n"
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(23, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(23, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "    </div>\n";
 },"23":function(container,depth0,helpers,partials,data) {
     var stack1;
@@ -547,19 +547,19 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
   return "        <span class=\"reasons\">"
     + container.escapeExpression(container.lambda(depth0, depth0))
     + "</span> "
-    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.last),{"name":"unless","hash":{},"fn":container.program(24, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(24, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "\n";
 },"24":function(container,depth0,helpers,partials,data) {
     return "&laquo;";
 },"26":function(container,depth0,helpers,partials,data) {
     var stack1;
 
-  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(27, data, 0),"inverse":container.program(30, data, 0),"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(27, data, 0),"inverse":container.program(30, data, 0),"data":data})) != null ? stack1 : "");
 },"27":function(container,depth0,helpers,partials,data) {
     var stack1;
 
   return "        <ol>\n"
-    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(28, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(28, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "        </ol>\n";
 },"28":function(container,depth0,helpers,partials,data) {
     var stack1;
@@ -578,7 +578,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
 },"34":function(container,depth0,helpers,partials,data) {
     var stack1, helper, options, buffer = 
   "    <pre>";
-  stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(35, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+  stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(35, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
   if (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
   if (stack1 != null) { buffer += stack1; }
   return buffer + "</pre>\n";
@@ -589,11 +589,11 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
 },"37":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
-  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(38, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(38, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
 },"38":function(container,depth0,helpers,partials,data,blockParams,depths) {
     var stack1;
 
-  return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.first),{"name":"unless","hash":{},"fn":container.program(39, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+  return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(39, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
     + "\n"
     + ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"grouped":(depths[1] != null ? depths[1].grouped : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
 },"39":function(container,depth0,helpers,partials,data) {
@@ -604,7 +604,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
     var stack1;
 
   return "\n\n"
-    + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.program(41, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+    + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.program(41, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
 },"main_d":  function(fn, props, container, depth0, data, blockParams, depths) {
 
   var decorators = container.decorators;
diff --git a/ext/bg/search.html b/ext/bg/search.html
index 7cbae392..655d7819 100644
--- a/ext/bg/search.html
+++ b/ext/bg/search.html
@@ -32,13 +32,19 @@
             <div id="content"></div>
         </div>
 
+        <script src="/mixed/lib/handlebars.min.js"></script>
         <script src="/mixed/lib/jquery.min.js"></script>
+        <script src="/mixed/lib/wanakana.min.js"></script>
+
+        <script src="/bg/js/api.js"></script>
         <script src="/bg/js/dictionary.js"></script>
-        <script src="/mixed/js/request.js"></script>
-        <script src="/mixed/js/japanese.js"></script>
+        <script src="/bg/js/handlebars.js"></script>
+        <script src="/bg/js/templates.js"></script>
         <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
-        <script src="/mixed/lib/wanakana.min.js"></script>
+        <script src="/mixed/js/japanese.js"></script>
+        <script src="/mixed/js/request.js"></script>
+
         <script src="/bg/js/display-window.js"></script>
     </body>
 </html>
diff --git a/ext/fg/frame.html b/ext/fg/frame.html
index 9ff2c585..e0b9eac5 100644
--- a/ext/fg/frame.html
+++ b/ext/fg/frame.html
@@ -30,12 +30,13 @@
             </div>
         </div>
 
-        <script src="/mixed/lib/jquery.min.js"></script>
-        <script src="/mixed/lib/wanakana.min.js"></script>
+        <script src="/fg/js/api.js"></script>
+        <script src="/fg/js/dictionary.js"></script>
         <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
-        <script src="/fg/js/dictionary.js"></script>
-        <script src="/fg/js/api.js"></script>
+        <script src="/mixed/lib/jquery.min.js"></script>
+        <script src="/mixed/lib/wanakana.min.js"></script>
+
         <script src="/fg/js/display-frame.js"></script>
     </body>
 </html>
-- 
cgit v1.2.3


From 7fbe2ddaf33bad05fb26aec759806e0f6ae250d2 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sat, 5 Aug 2017 20:20:22 -0700
Subject: more cleanup

---
 ext/fg/frame.html |  1 +
 ext/fg/js/api.js  | 31 +++++++------------------------
 ext/fg/js/util.js | 35 +++++++++++++++++++++++++++++++++++
 ext/manifest.json |  8 +++++---
 4 files changed, 48 insertions(+), 27 deletions(-)
 create mode 100644 ext/fg/js/util.js

(limited to 'ext/fg')

diff --git a/ext/fg/frame.html b/ext/fg/frame.html
index e0b9eac5..3fe42eb2 100644
--- a/ext/fg/frame.html
+++ b/ext/fg/frame.html
@@ -32,6 +32,7 @@
 
         <script src="/fg/js/api.js"></script>
         <script src="/fg/js/dictionary.js"></script>
+        <script src="/fg/js/util.js"></script>
         <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
         <script src="/mixed/lib/jquery.min.js"></script>
diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js
index e252637e..b4d75c3c 100644
--- a/ext/fg/js/api.js
+++ b/ext/fg/js/api.js
@@ -17,47 +17,30 @@
  */
 
 
-function apiInvoke(action, params={}) {
-    return new Promise((resolve, reject) => {
-        try {
-            chrome.runtime.sendMessage({action, params}, ({result, error}) => {
-                if (error) {
-                    reject(error);
-                } else {
-                    resolve(result);
-                }
-            });
-        } catch (e) {
-            window.yomichanOrphaned = true;
-            reject(e.message);
-        }
-    });
-}
-
 function apiOptionsGet() {
-    return apiInvoke('optionsGet');
+    return utilInvoke('optionsGet');
 }
 
 function apiTermsFind(text) {
-    return apiInvoke('termsFind', {text});
+    return utilInvoke('termsFind', {text});
 }
 
 function apiKanjiFind(text) {
-    return apiInvoke('kanjiFind', {text});
+    return utilInvoke('kanjiFind', {text});
 }
 
 function apiTemplateRender(template, data) {
-    return apiInvoke('templateRender', {data, template});
+    return utilInvoke('templateRender', {data, template});
 }
 
 function apiDefinitionsAddable(definitions, modes) {
-    return apiInvoke('definitionsAddable', {definitions, modes}).catch(() => null);
+    return utilInvoke('definitionsAddable', {definitions, modes}).catch(() => null);
 }
 
 function apiDefinitionAdd(definition, mode) {
-    return apiInvoke('definitionAdd', {definition, mode});
+    return utilInvoke('definitionAdd', {definition, mode});
 }
 
 function apiNoteView(noteId) {
-    return apiInvoke('noteView', {noteId});
+    return utilInvoke('noteView', {noteId});
 }
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
new file mode 100644
index 00000000..311fc065
--- /dev/null
+++ b/ext/fg/js/util.js
@@ -0,0 +1,35 @@
+/*
+ * 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 utilInvoke(action, params={}) {
+    return new Promise((resolve, reject) => {
+        try {
+            chrome.runtime.sendMessage({action, params}, ({result, error}) => {
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(result);
+                }
+            });
+        } catch (e) {
+            window.yomichanOrphaned = true;
+            reject(e.message);
+        }
+    });
+}
diff --git a/ext/manifest.json b/ext/manifest.json
index 288976f3..48308b17 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -15,11 +15,13 @@
     "content_scripts": [{
         "matches": ["http://*/*", "https://*/*", "file://*/*"],
         "js": [
-            "fg/js/document.js",
-            "fg/js/source-range.js",
-            "fg/js/source-element.js",
             "fg/js/api.js",
+            "fg/js/document.js",
             "fg/js/popup.js",
+            "fg/js/source-element.js",
+            "fg/js/source-range.js",
+            "fg/js/util.js",
+
             "fg/js/frontend.js"
         ],
         "css": ["fg/css/client.css"]
-- 
cgit v1.2.3


From aac2a58b5f821c6f90c95bb19f3b0a755d5e1739 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 13 Aug 2017 16:11:51 -0700
Subject: wip

---
 ext/bg/background.html      |   1 +
 ext/bg/js/display-window.js |  14 +-
 ext/bg/search.html          |   2 +-
 ext/fg/frame.html           |   4 +-
 ext/fg/js/display-frame.js  |  58 +++-----
 ext/fg/js/frontend.js       | 141 +++++++++---------
 ext/fg/js/util.js           |   2 +-
 ext/mixed/js/display.js     | 344 ++++++++++++++++++++++++--------------------
 8 files changed, 291 insertions(+), 275 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/bg/background.html b/ext/bg/background.html
index 40f37b11..fd3b7dd1 100644
--- a/ext/bg/background.html
+++ b/ext/bg/background.html
@@ -20,6 +20,7 @@
         <script src="/bg/js/templates.js"></script>
         <script src="/bg/js/translator.js"></script>
         <script src="/bg/js/util.js"></script>
+        <script src="/bg/js/util.js"></script>
         <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/japanese.js"></script>
         <script src="/mixed/js/request.js"></script>
diff --git a/ext/bg/js/display-window.js b/ext/bg/js/display-window.js
index 52c0cafa..cbb96681 100644
--- a/ext/bg/js/display-window.js
+++ b/ext/bg/js/display-window.js
@@ -17,26 +17,26 @@
  */
 
 
-window.displayWindow = new class extends Display {
+window.yomichan_window = new class extends Display {
     constructor() {
         super($('#spinner'), $('#content'));
 
         this.search = $('#search').click(this.onSearch.bind(this));
-        this.query = $('#query').on('input', this.inputSearch.bind(this));
+        this.query = $('#query').on('input', this.onSearchInput.bind(this));
         this.intro = $('#intro');
 
         window.wanakana.bind(this.query.get(0));
     }
 
-    handleError(error) {
+    onError(error) {
         window.alert(`Error: ${error}`);
     }
 
-    clearSearch() {
+    onSearchClear() {
         this.query.focus().select();
     }
 
-    inputSearch() {
+    onSearchInput() {
         this.search.prop('disabled', this.query.val().length === 0);
     }
 
@@ -46,9 +46,9 @@ window.displayWindow = new class extends Display {
         try {
             this.intro.slideUp();
             const {length, definitions} = await apiTermsFind(this.query.val());
-            super.showTermDefs(definitions, await apiOptionsGet());
+            super.termsShow(definitions, await apiOptionsGet());
         } catch (e) {
-            this.handleError(e);
+            this.onError(e);
         }
     }
 };
diff --git a/ext/bg/search.html b/ext/bg/search.html
index 655d7819..fe44d74e 100644
--- a/ext/bg/search.html
+++ b/ext/bg/search.html
@@ -40,10 +40,10 @@
         <script src="/bg/js/dictionary.js"></script>
         <script src="/bg/js/handlebars.js"></script>
         <script src="/bg/js/templates.js"></script>
+        <script src="/bg/js/util.js"></script>
         <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
         <script src="/mixed/js/japanese.js"></script>
-        <script src="/mixed/js/request.js"></script>
 
         <script src="/bg/js/display-window.js"></script>
     </body>
diff --git a/ext/fg/frame.html b/ext/fg/frame.html
index 3fe42eb2..dda3ef06 100644
--- a/ext/fg/frame.html
+++ b/ext/fg/frame.html
@@ -18,9 +18,9 @@
             <img src="/mixed/img/spinner.gif">
         </div>
 
-        <div id="content"></div>
+        <div id="definitions"></div>
 
-        <div id="orphan">
+        <div id="error-orphaned">
             <div class="container-fluid">
                 <h1>Yomichan Updated!</h1>
                 <p>
diff --git a/ext/fg/js/display-frame.js b/ext/fg/js/display-frame.js
index 09bd9255..5ea376c2 100644
--- a/ext/fg/js/display-frame.js
+++ b/ext/fg/js/display-frame.js
@@ -17,65 +17,45 @@
  */
 
 
-window.displayFrame = new class extends Display {
+window.yomichan_frame = new class extends Display {
     constructor() {
         super($('#spinner'), $('#content'));
         $(window).on('message', this.onMessage.bind(this));
     }
 
-    definitionAdd(definition, mode) {
-        return apiDefinitionAdd(definition, mode);
-    }
-
-    definitionsAddable(definitions, modes) {
-        return apiDefinitionsAddable(definitions, modes);
-    }
-
-    noteView(noteId) {
-        return apiNoteView(noteId);
-    }
-
-    templateRender(template, data) {
-        return apiTemplateRender(template, data);
-    }
-
-    kanjiFind(character) {
-        return apiKanjiFind(character);
-    }
-
-    handleError(error) {
-        if (window.yomichanOrphaned) {
-            this.showOrphaned();
+    onError(error) {
+        if (window.yomichan_orphaned) {
+            this.onOrphaned();
         } else {
             window.alert(`Error: ${error}`);
         }
     }
 
-    clearSearch() {
-        window.parent.postMessage('popupClose', '*');
+    onOrphaned() {
+        $('#definitions').hide();
+        $('#error-orphaned').show();
     }
 
-    selectionCopy() {
-        window.parent.postMessage('selectionCopy', '*');
+    onSearchClear() {
+        window.parent.postMessage('popupClose', '*');
     }
 
-    showOrphaned() {
-        $('#content').hide();
-        $('#orphan').show();
+    onSelectionCopy() {
+        window.parent.postMessage('selectionCopy', '*');
     }
 
     onMessage(e) {
         const handlers = {
-            showTermDefs: ({definitions, options, context}) => {
-                this.showTermDefs(definitions, options, context);
+            termsShow: ({definitions, options, context}) => {
+                this.termsShow(definitions, options, context);
             },
 
-            showKanjiDefs: ({definitions, options, context}) => {
-                this.showKanjiDefs(definitions, options, context);
+            kanjiShow: ({definitions, options, context}) => {
+                this.kanjiShow(definitions, options, context);
             },
 
-            showOrphaned: () => {
-                this.showOrphaned();
+            orphaned: () => {
+                this.onOrphaned();
             }
         };
 
@@ -89,8 +69,8 @@ window.displayFrame = new class extends Display {
     onKeyDown(e) {
         const handlers = {
             67: /* c */ () => {
-                if (e.ctrlKey && window.getSelection().toString() === '') {
-                    this.selectionCopy();
+                if (e.ctrlKey && !window.getSelection().toString()) {
+                    this.onSelectionCopy();
                     return true;
                 }
             }
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 9974d878..37389766 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -17,7 +17,7 @@
  */
 
 
-window.yomichanFrontend = new class {
+window.yomichan_frontend = new class {
     constructor() {
         this.popup = new Popup();
         this.popupTimer = null;
@@ -27,17 +27,23 @@ window.yomichanFrontend = new class {
         this.lastTextSource = null;
         this.pendingLookup = false;
         this.options = null;
+    }
+
+    async prepare() {
+        try {
+            this.options = await apiOptionsGet();
+        } catch (e) {
+            this.onError(e);
+        }
 
-        apiOptionsGet().then(options => {
-            this.options = options;
-            window.addEventListener('mouseover', this.onMouseOver.bind(this));
-            window.addEventListener('mousedown', this.onMouseDown.bind(this));
-            window.addEventListener('mouseup', this.onMouseUp.bind(this));
-            window.addEventListener('mousemove', this.onMouseMove.bind(this));
-            window.addEventListener('resize', e => this.searchClear());
-            window.addEventListener('message', this.onFrameMessage.bind(this));
-            chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
-        }).catch(this.handleError.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('mouseover', this.onMouseOver.bind(this));
+        window.addEventListener('mouseup', this.onMouseUp.bind(this));
+        window.addEventListener('resize', this.onResize.bind(this));
+
+        chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
     }
 
     popupTimerSet(callback) {
@@ -144,7 +150,11 @@ window.yomichanFrontend = new class {
         callback();
     }
 
-    searchAt(point) {
+    onResize() {
+        this.onSearchClear();
+    }
+
+    async searchAt(point) {
         if (this.pendingLookup) {
             return;
         }
@@ -160,70 +170,69 @@ window.yomichanFrontend = new class {
         }
 
         this.pendingLookup = true;
-        this.searchTerms(textSource).then(found => {
-            if (!found) {
-                return this.searchKanji(textSource);
+
+        try {
+            if (!await this.searchTerms(textSource)) {
+                await this.searchKanji(textSource);
             }
-        }).catch(error => {
-            this.handleError(error, textSource);
-        }).then(() => {
-            docImposterDestroy();
-            this.pendingLookup = false;
-        });
+        } catch (e) {
+            this.onError(e);
+        }
+
+        docImposterDestroy();
+        this.pendingLookup = false;
     }
 
-    searchTerms(textSource) {
+    async searchTerms(textSource) {
         textSource.setEndOffset(this.options.scanning.length);
 
-        return apiTermsFind(textSource.text()).then(({definitions, length}) => {
-            if (definitions.length === 0) {
-                return false;
-            } else {
-                textSource.setEndOffset(length);
-
-                const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
-                const url = window.location.href;
-                this.popup.showTermDefs(
-                    textSource.getRect(),
-                    definitions,
-                    this.options,
-                    {sentence, url}
-                );
-
-                this.lastTextSource = textSource;
-                if (this.options.scanning.selectText) {
-                    textSource.select();
-                }
+        const {definitions, length} = await apiTermsFind(textSource.text());
+        if (definitions.length === 0) {
+            return false;
+        }
 
-                return true;
-            }
-        });
+        textSource.setEndOffset(length);
+
+        const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
+        const url = window.location.href;
+        this.popup.termsShow(
+            textSource.getRect(),
+            definitions,
+            this.options,
+            {sentence, url}
+        );
+
+        this.lastTextSource = textSource;
+        if (this.options.scanning.selectText) {
+            textSource.select();
+        }
+
+        return true;
     }
 
-    searchKanji(textSource) {
+    async searchKanji(textSource) {
         textSource.setEndOffset(1);
 
-        return apiKanjiFind(textSource.text()).then(definitions => {
-            if (definitions.length === 0) {
-                return false;
-            } else {
-                const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
-                const url = window.location.href;
-                this.popup.showKanjiDefs(
-                    textSource.getRect(),
-                    definitions,
-                    this.options,
-                    {sentence, url}
-                );
-
-                this.lastTextSource = textSource;
-                if (this.options.scanning.selectText) {
-                    textSource.select();
-                }
+        const definitions = await apiKanjiFind(textSource.text());
+        if (definitions.length === 0) {
+            return false;
+        }
 
-                return true;
-            }
-        });
+        const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
+        const url = window.location.href;
+        this.popup.showKanji(
+            textSource.getRect(),
+            definitions,
+            this.options,
+            {sentence, url}
+        );
+
+        this.lastTextSource = textSource;
+        if (this.options.scanning.selectText) {
+            textSource.select();
+        }
+
+        return true;
     }
 
     searchClear() {
@@ -238,7 +247,7 @@ window.yomichanFrontend = new class {
     }
 
     handleError(error, textSource) {
-        if (window.yomichanOrphaned) {
+        if (window.yomichan_orphaned) {
             if (textSource && this.options.scanning.modifier !== 'none') {
                 this.popup.showOrphaned(textSource.getRect(), this.options);
             }
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
index 311fc065..afa895ba 100644
--- a/ext/fg/js/util.js
+++ b/ext/fg/js/util.js
@@ -28,7 +28,7 @@ function utilInvoke(action, params={}) {
                 }
             });
         } catch (e) {
-            window.yomichanOrphaned = true;
+            window.yomichan_orphaned = true;
             reject(e.message);
         }
     });
diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js
index f408fb25..97dd7d5c 100644
--- a/ext/mixed/js/display.js
+++ b/ext/mixed/js/display.js
@@ -32,171 +32,57 @@ class Display {
         $(document).keydown(this.onKeyDown.bind(this));
     }
 
-    handleError(error) {
+    onError(error) {
         throw 'override me';
     }
 
-    clearSearch() {
+    onSearchClear() {
         throw 'override me';
     }
 
-    showTermDefs(definitions, options, context) {
-        window.focus();
-
-        this.spinner.hide();
-        this.definitions = definitions;
-        this.options = options;
-        this.context = context;
-
-        const sequence = ++this.sequence;
-        const params = {
-            definitions,
-            addable: options.anki.enable,
-            grouped: options.general.groupResults,
-            playback: options.general.audioSource !== 'disabled',
-            debug: options.general.debugInfo
-        };
-
-        if (context) {
-            for (const definition of definitions) {
-                if (context.sentence) {
-                    definition.cloze = Display.clozeBuild(context.sentence, definition.source);
-                }
-
-                definition.url = context.url;
-            }
-        }
-
-        apiTemplateRender('terms.html', params).then(content => {
-            this.container.html(content);
-            this.entryScroll(context && context.index || 0);
-
-            $('.action-add-note').click(this.onAddNote.bind(this));
-            $('.action-view-note').click(this.onViewNote.bind(this));
-            $('.action-play-audio').click(this.onPlayAudio.bind(this));
-            $('.kanji-link').click(this.onKanjiLookup.bind(this));
-
-            return this.adderButtonsUpdate(['term-kanji', 'term-kana'], sequence);
-        }).catch(this.handleError.bind(this));
-    }
-
-    showKanjiDefs(definitions, options, context) {
-        window.focus();
-
-        this.spinner.hide();
-        this.definitions = definitions;
-        this.options = options;
-        this.context = context;
-
-        const sequence = ++this.sequence;
-        const params = {
-            definitions,
-            source: context && context.source,
-            addable: options.anki.enable,
-            debug: options.general.debugInfo
-        };
-
-        if (context) {
-            for (const definition of definitions) {
-                if (context.sentence) {
-                    definition.cloze = Display.clozeBuild(context.sentence);
-                }
-
-                definition.url = context.url;
-            }
-        }
-
-        apiTemplateRender('kanji.html', params).then(content => {
-            this.container.html(content);
-            this.entryScroll(context && context.index || 0);
-
-            $('.action-add-note').click(this.onAddNote.bind(this));
-            $('.source-term').click(this.onSourceTerm.bind(this));
-
-            return this.adderButtonsUpdate(['kanji'], sequence);
-        }).catch(this.handleError.bind(this));
-    }
-
-    adderButtonsUpdate(modes, sequence) {
-        return apiDefinitionsAddable(this.definitions, modes).then(states => {
-            if (!states || sequence !== this.sequence) {
-                return;
-            }
-
-            states.forEach((state, index) => {
-                for (const mode in state) {
-                    const button = Display.adderButtonFind(index, mode);
-                    if (state[mode]) {
-                        button.removeClass('disabled');
-                    } else {
-                        button.addClass('disabled');
-                    }
-
-                    button.removeClass('pending');
-                }
-            });
-        });
-    }
-
-    entryScroll(index, smooth) {
-        index = Math.min(index, this.definitions.length - 1);
-        index = Math.max(index, 0);
-
-        $('.current').hide().eq(index).show();
-
-        const container = $('html,body').stop();
-        const entry = $('.entry').eq(index);
-        const target = index === 0 ? 0 : entry.offset().top;
-
-        if (smooth) {
-            container.animate({scrollTop: target}, 200);
-        } else {
-            container.scrollTop(target);
-        }
-
-        this.index = index;
-    }
-
-    onSourceTerm(e) {
+    onSourceTermView(e) {
         e.preventDefault();
         this.sourceBack();
     }
 
-    onKanjiLookup(e) {
-        e.preventDefault();
+    async onKanjiLookup(e) {
+        try {
+            e.preventDefault();
 
-        const link = $(e.target);
-        const context = {
-            source: {
-                definitions: this.definitions,
-                index: Display.entryIndexFind(link)
+            const link = $(e.target);
+            const context = {
+                source: {
+                    definitions: this.definitions,
+                    index: Display.entryIndexFind(link)
+                }
+            };
+
+            if (this.context) {
+                context.sentence = this.context.sentence;
+                context.url = this.context.url;
             }
-        };
 
-        if (this.context) {
-            context.sentence = this.context.sentence;
-            context.url = this.context.url;
+            const kanjiDefs = await apiKanjiFind(link.text());
+            this.kanjiShow(kanjiDefs, this.options, context);
+        } catch (e) {
+            this.onError(e);
         }
-
-        apiKanjiFind(link.text()).then(kanjiDefs => {
-            this.showKanjiDefs(kanjiDefs, this.options, context);
-        }).catch(this.handleError.bind(this));
     }
 
-    onPlayAudio(e) {
+    onAudioPlay(e) {
         e.preventDefault();
         const index = Display.entryIndexFind($(e.currentTarget));
         this.audioPlay(this.definitions[index]);
     }
 
-    onAddNote(e) {
+    onNoteAdd(e) {
         e.preventDefault();
         const link = $(e.currentTarget);
         const index = Display.entryIndexFind(link);
         this.noteAdd(this.definitions[index], link.data('mode'));
     }
 
-    onViewNote(e) {
+    onNoteView(e) {
         e.preventDefault();
         const link = $(e.currentTarget);
         const index = Display.entryIndexFind(link);
@@ -220,48 +106,48 @@ class Display {
 
         const handlers = {
             27: /* escape */ () => {
-                this.clearSearch();
+                this.onSearchClear();
                 return true;
             },
 
             33: /* page up */ () => {
                 if (e.altKey) {
-                    this.entryScroll(this.index - 3, true);
+                    this.entryScrollIntoView(this.index - 3, true);
                     return true;
                 }
             },
 
             34: /* page down */ () => {
                 if (e.altKey) {
-                    this.entryScroll(this.index + 3, true);
+                    this.entryScrollIntoView(this.index + 3, true);
                     return true;
                 }
             },
 
             35: /* end */ () => {
                 if (e.altKey) {
-                    this.entryScroll(this.definitions.length - 1, true);
+                    this.entryScrollIntoView(this.definitions.length - 1, true);
                     return true;
                 }
             },
 
             36: /* home */ () => {
                 if (e.altKey) {
-                    this.entryScroll(0, true);
+                    this.entryScrollIntoView(0, true);
                     return true;
                 }
             },
 
             38: /* up */ () => {
                 if (e.altKey) {
-                    this.entryScroll(this.index - 1, true);
+                    this.entryScrollIntoView(this.index - 1, true);
                     return true;
                 }
             },
 
             40: /* down */ () => {
                 if (e.altKey) {
-                    this.entryScroll(this.index + 1, true);
+                    this.entryScrollIntoView(this.index + 1, true);
                     return true;
                 }
             },
@@ -317,6 +203,135 @@ class Display {
         }
     }
 
+    async termsShow(definitions, options, context) {
+        try {
+            window.focus();
+
+            this.definitions = definitions;
+            this.options = options;
+            this.context = context;
+
+            const sequence = ++this.sequence;
+            const params = {
+                definitions,
+                addable: options.anki.enable,
+                grouped: options.general.groupResults,
+                playback: options.general.audioSource !== 'disabled',
+                debug: options.general.debugInfo
+            };
+
+            if (context) {
+                for (const definition of definitions) {
+                    if (context.sentence) {
+                        definition.cloze = Display.clozeBuild(context.sentence, definition.source);
+                    }
+
+                    definition.url = context.url;
+                }
+            }
+
+            const content = await apiTemplateRender('terms.html', params);
+            this.container.html(content);
+            this.entryScrollIntoView(context && context.index || 0);
+
+            $('.action-add-note').click(this.onNoteAdd.bind(this));
+            $('.action-view-note').click(this.onNoteView.bind(this));
+            $('.action-play-audio').click(this.onAudioPlay.bind(this));
+            $('.kanji-link').click(this.onKanjiLookup.bind(this));
+
+            await this.adderButtonUpdate(['term-kanji', 'term-kana'], sequence);
+        } catch (e) {
+            this.onError(e);
+        }
+    }
+
+    async kanjiShow(definitions, options, context) {
+        try {
+            window.focus();
+
+            this.definitions = definitions;
+            this.options = options;
+            this.context = context;
+
+            const sequence = ++this.sequence;
+            const params = {
+                definitions,
+                source: context && context.source,
+                addable: options.anki.enable,
+                debug: options.general.debugInfo
+            };
+
+            if (context) {
+                for (const definition of definitions) {
+                    if (context.sentence) {
+                        definition.cloze = Display.clozeBuild(context.sentence);
+                    }
+
+                    definition.url = context.url;
+                }
+            }
+
+            const content = await apiTemplateRender('kanji.html', params);
+            this.container.html(content);
+            this.entryScrollIntoView(context && context.index || 0);
+
+            $('.action-add-note').click(this.onNoteAdd.bind(this));
+            $('.source-term').click(this.onSourceTermView.bind(this));
+
+            await this.adderButtonUpdate(['kanji'], sequence);
+        } catch (e) {
+            this.onError(e);
+        }
+    }
+
+    async adderButtonUpdate(modes, sequence) {
+        try {
+            this.spinner.show();
+
+            const states = apiDefinitionsAddable(this.definitions, modes);
+            if (!states || sequence !== this.sequence) {
+                return;
+            }
+
+            for (let i = 0; i < states.length; ++i) {
+                const state = states[i];
+                for (const mode in state) {
+                    const button = Display.adderButtonFind(i, mode);
+                    if (state[mode]) {
+                        button.removeClass('disabled');
+                    } else {
+                        button.addClass('disabled');
+                    }
+
+                    button.removeClass('pending');
+                }
+            }
+        } catch (e) {
+            this.onError(e);
+        } finally {
+            this.spinner.hide();
+        }
+    }
+
+    entryScrollIntoView(index, smooth) {
+        index = Math.min(index, this.definitions.length - 1);
+        index = Math.max(index, 0);
+
+        $('.current').hide().eq(index).show();
+
+        const container = $('html,body').stop();
+        const entry = $('.entry').eq(index);
+        const target = index === 0 ? 0 : entry.offset().top;
+
+        if (smooth) {
+            container.animate({scrollTop: target}, 200);
+        } else {
+            container.scrollTop(target);
+        }
+
+        this.index = index;
+    }
+
     sourceBack() {
         if (this.context && this.context.source) {
             const context = {
@@ -325,35 +340,42 @@ class Display {
                 index: this.context.source.index
             };
 
-            this.showTermDefs(this.context.source.definitions, this.options, context);
+            this.termsShow(this.context.source.definitions, this.options, context);
         }
     }
 
-    noteAdd(definition, mode) {
-        this.spinner.show();
-        return apiDefinitionAdd(definition, mode).then(noteId => {
+    async noteAdd(definition, mode) {
+        try {
+            this.spinner.show();
+
+            const noteId = await apiDefinitionAdd(definition, mode);
             if (noteId) {
                 const index = this.definitions.indexOf(definition);
                 Display.adderButtonFind(index, mode).addClass('disabled');
                 Display.viewerButtonFind(index).removeClass('pending disabled').data('noteId', noteId);
             } else {
-                this.handleError('note could not be added');
+                throw 'note could note be added';
             }
-        }).catch(this.handleError.bind(this)).then(() => this.spinner.hide());
+        } catch (e) {
+            this.onError(e);
+        } finally {
+            this.spinner.hide();
+        }
     }
 
-    audioPlay(definition) {
-        this.spinner.show();
-
-        for (const key in this.audioCache) {
-            this.audioCache[key].pause();
-        }
+    async audioPlay(definition) {
+        try {
+            this.spinner.show();
 
-        audioBuildUrl(definition, this.options.general.audioSource, this.responseCache).then(url => {
+            let url = await audioBuildUrl(definition, this.options.general.audioSource, this.responseCache);
             if (!url) {
                 url = '/mixed/mp3/button.mp3';
             }
 
+            for (const key in this.audioCache) {
+                this.audioCache[key].pause();
+            }
+
             let audio = this.audioCache[url];
             if (audio) {
                 audio.currentTime = 0;
@@ -371,7 +393,11 @@ class Display {
                     audio.play();
                 };
             }
-        }).catch(this.handleError.bind(this)).then(() => this.spinner.hide());
+        } catch (e) {
+            this.onError(e);
+        } finally {
+            this.spinner.hide();
+        }
     }
 
     static clozeBuild(sentence, source) {
-- 
cgit v1.2.3


From 3ca28a93746ed0860bf19ede83e3e9bac979bfb5 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 13 Aug 2017 16:42:22 -0700
Subject: wip

---
 ext/fg/js/frontend.js | 102 ++++++++++++++++++++++++------------------------
 ext/fg/js/popup.js    | 105 ++++++++++++++++++++++++--------------------------
 2 files changed, 103 insertions(+), 104 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 37389766..de5fa953 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -32,29 +32,17 @@ window.yomichan_frontend = new class {
     async prepare() {
         try {
             this.options = await apiOptionsGet();
-        } catch (e) {
-            this.onError(e);
-        }
-
-        window.addEventListener('message', this.onFrameMessage.bind(this));
-        window.addEventListener('mousedown', this.onMouseDown.bind(this));
-        window.addEventListener('mousemove', this.onMouseMove.bind(this));
-        window.addEventListener('mouseover', this.onMouseOver.bind(this));
-        window.addEventListener('mouseup', this.onMouseUp.bind(this));
-        window.addEventListener('resize', this.onResize.bind(this));
 
-        chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
-    }
-
-    popupTimerSet(callback) {
-        this.popupTimerClear();
-        this.popupTimer = window.setTimeout(callback, this.options.scanning.delay);
-    }
+            window.addEventListener('message', e => this.onFrameMessage(e));
+            window.addEventListener('mousedown', e => this.onMouseDown(e));
+            window.addEventListener('mousemove', e => this.onMouseMove(e));
+            window.addEventListener('mouseover', e => this.onMouseOver(e));
+            window.addEventListener('mouseup', e => this.onMouseUp(e));
+            window.addEventListener('resize', e => this.onResize(e));
 
-    popupTimerClear() {
-        if (this.popupTimer) {
-            window.clearTimeout(this.popupTimer);
-            this.popupTimer = null;
+            chrome.runtime.onMessage.addListener(({action, params}, sender, callback) => this.onBgMessage(action, params, sender, callback));
+        } catch (e) {
+            this.onError(e);
         }
     }
 
@@ -132,7 +120,11 @@ window.yomichan_frontend = new class {
         }
     }
 
-    onBgMessage({action, params}, sender, callback) {
+    onResize() {
+        this.onSearchClear();
+    }
+
+    onBgMessage(action, params, sender, callback) {
         const handlers = {
             optionsSet: options => {
                 this.options = options;
@@ -150,37 +142,55 @@ window.yomichan_frontend = new class {
         callback();
     }
 
-    onResize() {
-        this.onSearchClear();
+    onError(error) {
+        if (window.yomichan_orphaned) {
+            if (this.lastTextSource && this.options.scanning.modifier !== 'none') {
+                this.popup.showOrphaned(this.lastTextSource.getRect(), this.options);
+            }
+        } else {
+            window.alert(`Error: ${error}`);
+        }
     }
 
-    async searchAt(point) {
-        if (this.pendingLookup) {
-            return;
-        }
+    popupTimerSet(callback) {
+        this.popupTimerClear();
+        this.popupTimer = window.setTimeout(callback, this.options.scanning.delay);
+    }
 
-        const textSource = docRangeFromPoint(point);
-        if (!textSource || !textSource.containsPoint(point)) {
-            docImposterDestroy();
-            return;
+    popupTimerClear() {
+        if (this.popupTimer) {
+            window.clearTimeout(this.popupTimer);
+            this.popupTimer = null;
         }
+    }
 
-        if (this.lastTextSource && this.lastTextSource.equals(textSource)) {
-            return;
-        }
+    async searchAt(point) {
+        try {
+            if (this.pendingLookup) {
+                return;
+            }
 
-        this.pendingLookup = true;
+            const textSource = docRangeFromPoint(point);
+            if (!textSource || !textSource.containsPoint(point)) {
+                docImposterDestroy();
+                return;
+            }
+
+            if (this.lastTextSource && this.lastTextSource.equals(textSource)) {
+                return;
+            }
+
+            this.pendingLookup = true;
 
-        try {
             if (!await this.searchTerms(textSource)) {
                 await this.searchKanji(textSource);
             }
         } catch (e) {
             this.onError(e);
+        } finally {
+            docImposterDestroy();
+            this.pendingLookup = false;
         }
-
-        docImposterDestroy();
-        this.pendingLookup = false;
     }
 
     async searchTerms(textSource) {
@@ -245,14 +255,6 @@ window.yomichan_frontend = new class {
 
         this.lastTextSource = null;
     }
+}();
 
-    handleError(error, textSource) {
-        if (window.yomichan_orphaned) {
-            if (textSource && this.options.scanning.modifier !== 'none') {
-                this.popup.showOrphaned(textSource.getRect(), this.options);
-            }
-        } else {
-            window.alert(`Error: ${error}`);
-        }
-    }
-};
+window.yomichan_frontend.prepare();
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index cd7e846a..ba3289d4 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -40,51 +40,51 @@ class Popup {
         return this.injected;
     }
 
-    show(elementRect, options) {
-        return this.inject().then(() => {
-            const containerStyle = window.getComputedStyle(this.container);
-            const containerHeight = parseInt(containerStyle.height);
-            const containerWidth = parseInt(containerStyle.width);
-
-            const limitX = document.body.clientWidth;
-            const limitY = window.innerHeight;
-
-            let x = elementRect.left;
-            let width = Math.max(containerWidth, options.general.popupWidth);
-            const overflowX = Math.max(x + width - limitX, 0);
-            if (overflowX > 0) {
-                if (x >= overflowX) {
-                    x -= overflowX;
-                } else {
-                    width = limitX;
-                    x = 0;
-                }
-            }
+    async show(elementRect, options) {
+        await this.inject();
+
+        const containerStyle = window.getComputedStyle(this.container);
+        const containerHeight = parseInt(containerStyle.height);
+        const containerWidth = parseInt(containerStyle.width);
+
+        const limitX = document.body.clientWidth;
+        const limitY = window.innerHeight;
 
-            let y = 0;
-            let height = Math.max(containerHeight, options.general.popupHeight);
-            const yBelow = elementRect.bottom + options.general.popupOffset;
-            const yAbove = elementRect.top - options.general.popupOffset;
-            const overflowBelow = Math.max(yBelow + height - limitY, 0);
-            const overflowAbove = Math.max(height - yAbove, 0);
-            if (overflowBelow > 0 || overflowAbove > 0) {
-                if (overflowBelow < overflowAbove) {
-                    height = Math.max(height - overflowBelow, 0);
-                    y = yBelow;
-                } else {
-                    height = Math.max(height - overflowAbove, 0);
-                    y = Math.max(yAbove - height, 0);
-                }
+        let x = elementRect.left;
+        let width = Math.max(containerWidth, options.general.popupWidth);
+        const overflowX = Math.max(x + width - limitX, 0);
+        if (overflowX > 0) {
+            if (x >= overflowX) {
+                x -= overflowX;
             } else {
+                width = limitX;
+                x = 0;
+            }
+        }
+
+        let y = 0;
+        let height = Math.max(containerHeight, options.general.popupHeight);
+        const yBelow = elementRect.bottom + options.general.popupOffset;
+        const yAbove = elementRect.top - options.general.popupOffset;
+        const overflowBelow = Math.max(yBelow + height - limitY, 0);
+        const overflowAbove = Math.max(height - yAbove, 0);
+        if (overflowBelow > 0 || overflowAbove > 0) {
+            if (overflowBelow < overflowAbove) {
+                height = Math.max(height - overflowBelow, 0);
                 y = yBelow;
+            } else {
+                height = Math.max(height - overflowAbove, 0);
+                y = Math.max(yAbove - height, 0);
             }
+        } else {
+            y = yBelow;
+        }
 
-            this.container.style.left = `${x}px`;
-            this.container.style.top = `${y}px`;
-            this.container.style.width = `${width}px`;
-            this.container.style.height = `${height}px`;
-            this.container.style.visibility = 'visible';
-        });
+        this.container.style.left = `${x}px`;
+        this.container.style.top = `${y}px`;
+        this.container.style.width = `${width}px`;
+        this.container.style.height = `${height}px`;
+        this.container.style.visibility = 'visible';
     }
 
     hide() {
@@ -95,25 +95,22 @@ class Popup {
         return this.injected && this.container.style.visibility !== 'hidden';
     }
 
-    showTermDefs(elementRect, definitions, options, context) {
-        this.show(elementRect, options).then(() => {
-            this.invokeApi('showTermDefs', {definitions, options, context});
-        });
-    }
-
-    showKanjiDefs(elementRect, definitions, options, context) {
-        this.show(elementRect, options).then(() => {
-            this.invokeApi('showKanjiDefs', {definitions, options, context});
-        });
+    async termsShow(elementRect, definitions, options, context) {
+        await this.show(elementRect, options);
+        this.invokeApi('termsShow', {definitions, options, context});
     }
 
-    showOrphaned(elementRect, options) {
-        this.show(elementRect, options).then(() => {
-            this.invokeApi('showOrphaned');
-        });
+    async kanjiShow(elementRect, definitions, options, context) {
+        await this.show(elementRect, options);
+        this.invokeApi('termsShow', {definitions, options, context});
     }
 
     invokeApi(action, params={}) {
         this.container.contentWindow.postMessage({action, params}, '*');
     }
+
+    async onOrphaned(elementRect, options) {
+        await this.show(elementRect, options);
+        this.invokeApi('orphaned');
+    }
 }
-- 
cgit v1.2.3


From a202817b987e0af82607d814f775bde26947747a Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 13 Aug 2017 20:50:43 -0700
Subject: wip

---
 ext/bg/popup.html          | 3 ++-
 ext/fg/js/display-frame.js | 2 +-
 ext/fg/js/frontend.js      | 7 ++++---
 ext/mixed/css/frame.css    | 2 +-
 4 files changed, 8 insertions(+), 6 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/bg/popup.html b/ext/bg/popup.html
index 4113b008..60253d6f 100644
--- a/ext/bg/popup.html
+++ b/ext/bg/popup.html
@@ -35,8 +35,9 @@
         <script src="/bg/js/api.js"></script>
         <script src="/bg/js/dictionary.js"></script>
         <script src="/bg/js/options.js"></script>
-        <script src="/bg/js/popup.js"></script>
         <script src="/mixed/js/japanese.js"></script>
         <script src="/mixed/js/request.js"></script>
+
+        <script src="/bg/js/popup.js"></script>
     </body>
 </html>
diff --git a/ext/fg/js/display-frame.js b/ext/fg/js/display-frame.js
index 5ea376c2..e3f3e692 100644
--- a/ext/fg/js/display-frame.js
+++ b/ext/fg/js/display-frame.js
@@ -19,7 +19,7 @@
 
 window.yomichan_frame = new class extends Display {
     constructor() {
-        super($('#spinner'), $('#content'));
+        super($('#spinner'), $('#definitions'));
         $(window).on('message', this.onMessage.bind(this));
     }
 
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index de5fa953..005139e6 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -17,7 +17,7 @@
  */
 
 
-window.yomichan_frontend = new class {
+class Frontend {
     constructor() {
         this.popup = new Popup();
         this.popupTimer = null;
@@ -121,7 +121,7 @@ window.yomichan_frontend = new class {
     }
 
     onResize() {
-        this.onSearchClear();
+        this.searchClear();
     }
 
     onBgMessage(action, params, sender, callback) {
@@ -255,6 +255,7 @@ window.yomichan_frontend = new class {
 
         this.lastTextSource = null;
     }
-}();
+}
 
+window.yomichan_frontend = new Frontend();
 window.yomichan_frontend.prepare();
diff --git a/ext/mixed/css/frame.css b/ext/mixed/css/frame.css
index a0b45fa4..8b1819bd 100644
--- a/ext/mixed/css/frame.css
+++ b/ext/mixed/css/frame.css
@@ -42,7 +42,7 @@ hr {
     right: 5px;
 }
 
-#orphan {
+#error-orphaned {
     display: none;
 }
 
-- 
cgit v1.2.3


From 8b50dfe1e9a8be7b8d2a7c69b25bc04babfc1c0c Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Sun, 13 Aug 2017 21:11:10 -0700
Subject: unify files

---
 ext/bg/background.html      |   4 +-
 ext/bg/js/anki-connect.js   |  69 -------------
 ext/bg/js/anki-null.js      |  44 ---------
 ext/bg/js/anki.js           |  95 ++++++++++++++++++
 ext/bg/js/backend.js        |   5 +-
 ext/fg/js/source-element.js |  77 ---------------
 ext/fg/js/source-range.js   | 176 ---------------------------------
 ext/fg/js/source.js         | 236 ++++++++++++++++++++++++++++++++++++++++++++
 ext/manifest.json           |   3 +-
 9 files changed, 336 insertions(+), 373 deletions(-)
 delete mode 100644 ext/bg/js/anki-connect.js
 delete mode 100644 ext/bg/js/anki-null.js
 create mode 100644 ext/bg/js/anki.js
 delete mode 100644 ext/fg/js/source-element.js
 delete mode 100644 ext/fg/js/source-range.js
 create mode 100644 ext/fg/js/source.js

(limited to 'ext/fg')

diff --git a/ext/bg/background.html b/ext/bg/background.html
index fd3b7dd1..90ad9709 100644
--- a/ext/bg/background.html
+++ b/ext/bg/background.html
@@ -9,8 +9,7 @@
         <script src="/mixed/lib/jszip.min.js"></script>
         <script src="/mixed/lib/wanakana.min.js"></script>
 
-        <script src="/bg/js/anki-connect.js"></script>
-        <script src="/bg/js/anki-null.js"></script>
+        <script src="/bg/js/anki.js"></script>
         <script src="/bg/js/api.js"></script>
         <script src="/bg/js/database.js"></script>
         <script src="/bg/js/deinflector.js"></script>
@@ -20,7 +19,6 @@
         <script src="/bg/js/templates.js"></script>
         <script src="/bg/js/translator.js"></script>
         <script src="/bg/js/util.js"></script>
-        <script src="/bg/js/util.js"></script>
         <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/japanese.js"></script>
         <script src="/mixed/js/request.js"></script>
diff --git a/ext/bg/js/anki-connect.js b/ext/bg/js/anki-connect.js
deleted file mode 100644
index 80c075fd..00000000
--- a/ext/bg/js/anki-connect.js
+++ /dev/null
@@ -1,69 +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 AnkiConnect {
-    constructor(server) {
-        this.server = server;
-        this.localVersion = 2;
-        this.remoteVersion = 0;
-    }
-
-    async addNote(note) {
-        await this.checkVersion();
-        return await this.ankiInvoke('addNote', {note});
-    }
-
-    async canAddNotes(notes) {
-        await this.checkVersion();
-        return await this.ankiInvoke('canAddNotes', {notes});
-    }
-
-    async getDeckNames() {
-        await this.checkVersion();
-        return await this.ankiInvoke('deckNames');
-    }
-
-    async getModelNames() {
-        await this.checkVersion();
-        return await this.ankiInvoke('modelNames');
-    }
-
-    async getModelFieldNames(modelName) {
-        await this.checkVersion();
-        return await this.ankiInvoke('modelFieldNames', {modelName});
-    }
-
-    async guiBrowse(query) {
-        await this.checkVersion();
-        return await this.ankiInvoke('guiBrowse', {query});
-    }
-
-    async checkVersion() {
-        if (this.remoteVersion < this.localVersion) {
-            this.remoteVersion = await this.ankiInvoke('version');
-            if (this.remoteVersion < this.localVersion) {
-                throw 'extension and plugin versions incompatible';
-            }
-        }
-    }
-
-    ankiInvoke(action, params) {
-        return requestJson(this.server, 'POST', {action, params, version: this.localVersion});
-    }
-}
diff --git a/ext/bg/js/anki-null.js b/ext/bg/js/anki-null.js
deleted file mode 100644
index d82f0e68..00000000
--- a/ext/bg/js/anki-null.js
+++ /dev/null
@@ -1,44 +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 AnkiNull {
-    async addNote(note) {
-        return null;
-    }
-
-    async canAddNotes(notes) {
-        return [];
-    }
-
-    async getDeckNames() {
-        return [];
-    }
-
-    async getModelNames() {
-        return [];
-    }
-
-    async getModelFieldNames(modelName) {
-        return [];
-    }
-
-    async guiBrowse(query) {
-        return [];
-    }
-}
diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js
new file mode 100644
index 00000000..f0ec4571
--- /dev/null
+++ b/ext/bg/js/anki.js
@@ -0,0 +1,95 @@
+/*
+ * 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 AnkiConnect {
+    constructor(server) {
+        this.server = server;
+        this.localVersion = 2;
+        this.remoteVersion = 0;
+    }
+
+    async addNote(note) {
+        await this.checkVersion();
+        return await this.ankiInvoke('addNote', {note});
+    }
+
+    async canAddNotes(notes) {
+        await this.checkVersion();
+        return await this.ankiInvoke('canAddNotes', {notes});
+    }
+
+    async getDeckNames() {
+        await this.checkVersion();
+        return await this.ankiInvoke('deckNames');
+    }
+
+    async getModelNames() {
+        await this.checkVersion();
+        return await this.ankiInvoke('modelNames');
+    }
+
+    async getModelFieldNames(modelName) {
+        await this.checkVersion();
+        return await this.ankiInvoke('modelFieldNames', {modelName});
+    }
+
+    async guiBrowse(query) {
+        await this.checkVersion();
+        return await this.ankiInvoke('guiBrowse', {query});
+    }
+
+    async checkVersion() {
+        if (this.remoteVersion < this.localVersion) {
+            this.remoteVersion = await this.ankiInvoke('version');
+            if (this.remoteVersion < this.localVersion) {
+                throw 'extension and plugin versions incompatible';
+            }
+        }
+    }
+
+    ankiInvoke(action, params) {
+        return requestJson(this.server, 'POST', {action, params, version: this.localVersion});
+    }
+}
+
+class AnkiNull {
+    async addNote(note) {
+        return null;
+    }
+
+    async canAddNotes(notes) {
+        return [];
+    }
+
+    async getDeckNames() {
+        return [];
+    }
+
+    async getModelNames() {
+        return [];
+    }
+
+    async getModelFieldNames(modelName) {
+        return [];
+    }
+
+    async guiBrowse(query) {
+        return [];
+    }
+}
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index f61e9742..e8c9452c 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -17,7 +17,7 @@
  */
 
 
-window.yomichan_backend = new class {
+class Backend {
     constructor() {
         this.translator = new Translator();
         this.anki = new AnkiNull();
@@ -113,6 +113,7 @@ window.yomichan_backend = new class {
 
         return true;
     }
-};
+}
 
+window.yomichan_backend = new Backend();
 window.yomichan_backend.prepare();
diff --git a/ext/fg/js/source-element.js b/ext/fg/js/source-element.js
deleted file mode 100644
index a8101382..00000000
--- a/ext/fg/js/source-element.js
+++ /dev/null
@@ -1,77 +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 TextSourceElement {
-    constructor(element, content='') {
-        this.element = element;
-        this.content = content;
-    }
-
-    clone() {
-        return new TextSourceElement(this.element, this.content);
-    }
-
-    text() {
-        return this.content;
-    }
-
-    setEndOffset(length) {
-        switch (this.element.nodeName) {
-            case 'BUTTON':
-                this.content = this.element.innerHTML;
-                break;
-            case 'IMG':
-                this.content = this.element.getAttribute('alt');
-                break;
-            default:
-                this.content = this.element.value;
-                break;
-        }
-
-        this.content = this.content || '';
-        this.content = this.content.substring(0, length);
-
-        return this.content.length;
-    }
-
-    setStartOffset(length) {
-        return 0;
-    }
-
-    containsPoint(point) {
-        const rect = this.getRect();
-        return point.x >= rect.left && point.x <= rect.right;
-    }
-
-    getRect() {
-        return this.element.getBoundingClientRect();
-    }
-
-    select() {
-        // NOP
-    }
-
-    deselect() {
-        // NOP
-    }
-
-    equals(other) {
-        return other.element === this.element && other.content === this.content;
-    }
-}
diff --git a/ext/fg/js/source-range.js b/ext/fg/js/source-range.js
deleted file mode 100644
index fa73b0a4..00000000
--- a/ext/fg/js/source-range.js
+++ /dev/null
@@ -1,176 +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 TextSourceRange {
-    constructor(range, content='') {
-        this.range = range;
-        this.content = content;
-    }
-
-    clone() {
-        return new TextSourceRange(this.range.cloneRange(), this.content);
-    }
-
-    text() {
-        return this.content;
-    }
-
-    setEndOffset(length) {
-        const state = TextSourceRange.seekForward(this.range.startContainer, this.range.startOffset, length);
-        this.range.setEnd(state.node, state.offset);
-        this.content = state.content;
-        return length - state.remainder;
-    }
-
-    setStartOffset(length) {
-        const state = TextSourceRange.seekBackward(this.range.startContainer, this.range.startOffset, length);
-        this.range.setStart(state.node, state.offset);
-        this.content = state.content;
-        return length - state.remainder;
-    }
-
-    containsPoint(point) {
-        const rect = this.getPaddedRect();
-        return point.x >= rect.left && point.x <= rect.right;
-    }
-
-    getRect() {
-        return this.range.getBoundingClientRect();
-    }
-
-    getPaddedRect() {
-        const range = this.range.cloneRange();
-        const startOffset = range.startOffset;
-        const endOffset = range.endOffset;
-        const node = range.startContainer;
-
-        range.setStart(node, Math.max(0, startOffset - 1));
-        range.setEnd(node, Math.min(node.length, endOffset + 1));
-
-        return range.getBoundingClientRect();
-    }
-
-    select() {
-        const selection = window.getSelection();
-        selection.removeAllRanges();
-        selection.addRange(this.range);
-    }
-
-    deselect() {
-        const selection = window.getSelection();
-        selection.removeAllRanges();
-    }
-
-    equals(other) {
-        return other.range && other.range.compareBoundaryPoints(Range.START_TO_START, this.range) === 0;
-    }
-
-    static shouldEnter(node) {
-        if (node.nodeType !== 1) {
-            return false;
-        }
-
-        const skip = ['RT', 'SCRIPT', 'STYLE'];
-        if (skip.includes(node.nodeName)) {
-            return false;
-        }
-
-        const style = window.getComputedStyle(node);
-        const hidden =
-            style.visibility === 'hidden' ||
-            style.display === 'none' ||
-            parseFloat(style.fontSize) === 0;
-
-        return !hidden;
-    }
-
-    static seekForward(node, offset, length) {
-        const state = {node, offset, remainder: length, content: ''};
-        if (!TextSourceRange.seekForwardHelper(node, state)) {
-            return state;
-        }
-
-        for (let current = node; current !== null; current = current.parentElement) {
-            for (let sibling = current.nextSibling; sibling !== null; sibling = sibling.nextSibling) {
-                if (!TextSourceRange.seekForwardHelper(sibling, state)) {
-                    return state;
-                }
-            }
-        }
-
-        return state;
-    }
-
-    static seekForwardHelper(node, state) {
-        if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) {
-            const offset = state.node === node ? state.offset : 0;
-            const remaining = node.length - offset;
-            const consumed = Math.min(remaining, state.remainder);
-            state.content = state.content + node.nodeValue.substring(offset, offset + consumed);
-            state.node = node;
-            state.offset = offset + consumed;
-            state.remainder -= consumed;
-        } else if (TextSourceRange.shouldEnter(node)) {
-            for (let i = 0; i < node.childNodes.length; ++i) {
-                if (!TextSourceRange.seekForwardHelper(node.childNodes[i], state)) {
-                    break;
-                }
-            }
-        }
-
-        return state.remainder > 0;
-    }
-
-    static seekBackward(node, offset, length) {
-        const state = {node, offset, remainder: length, content: ''};
-        if (!TextSourceRange.seekBackwardHelper(node, state)) {
-            return state;
-        }
-
-        for (let current = node; current !== null; current = current.parentElement) {
-            for (let sibling = current.previousSibling; sibling !== null; sibling = sibling.previousSibling) {
-                if (!TextSourceRange.seekBackwardHelper(sibling, state)) {
-                    return state;
-                }
-            }
-        }
-
-        return state;
-    }
-
-    static seekBackwardHelper(node, state) {
-        if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) {
-            const offset = state.node === node ? state.offset : node.length;
-            const remaining = offset;
-            const consumed = Math.min(remaining, state.remainder);
-            state.content = node.nodeValue.substring(offset - consumed, offset) + state.content;
-            state.node = node;
-            state.offset = offset - consumed;
-            state.remainder -= consumed;
-        } else if (TextSourceRange.shouldEnter(node)) {
-            for (let i = node.childNodes.length - 1; i >= 0; --i) {
-                if (!TextSourceRange.seekBackwardHelper(node.childNodes[i], state)) {
-                    break;
-                }
-            }
-        }
-
-        return state.remainder > 0;
-    }
-}
diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js
new file mode 100644
index 00000000..210dda12
--- /dev/null
+++ b/ext/fg/js/source.js
@@ -0,0 +1,236 @@
+/*
+ * 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 TextSourceRange {
+    constructor(range, content='') {
+        this.range = range;
+        this.content = content;
+    }
+
+    clone() {
+        return new TextSourceRange(this.range.cloneRange(), this.content);
+    }
+
+    text() {
+        return this.content;
+    }
+
+    setEndOffset(length) {
+        const state = TextSourceRange.seekForward(this.range.startContainer, this.range.startOffset, length);
+        this.range.setEnd(state.node, state.offset);
+        this.content = state.content;
+        return length - state.remainder;
+    }
+
+    setStartOffset(length) {
+        const state = TextSourceRange.seekBackward(this.range.startContainer, this.range.startOffset, length);
+        this.range.setStart(state.node, state.offset);
+        this.content = state.content;
+        return length - state.remainder;
+    }
+
+    containsPoint(point) {
+        const rect = this.getPaddedRect();
+        return point.x >= rect.left && point.x <= rect.right;
+    }
+
+    getRect() {
+        return this.range.getBoundingClientRect();
+    }
+
+    getPaddedRect() {
+        const range = this.range.cloneRange();
+        const startOffset = range.startOffset;
+        const endOffset = range.endOffset;
+        const node = range.startContainer;
+
+        range.setStart(node, Math.max(0, startOffset - 1));
+        range.setEnd(node, Math.min(node.length, endOffset + 1));
+
+        return range.getBoundingClientRect();
+    }
+
+    select() {
+        const selection = window.getSelection();
+        selection.removeAllRanges();
+        selection.addRange(this.range);
+    }
+
+    deselect() {
+        const selection = window.getSelection();
+        selection.removeAllRanges();
+    }
+
+    equals(other) {
+        return other.range && other.range.compareBoundaryPoints(Range.START_TO_START, this.range) === 0;
+    }
+
+    static shouldEnter(node) {
+        if (node.nodeType !== 1) {
+            return false;
+        }
+
+        const skip = ['RT', 'SCRIPT', 'STYLE'];
+        if (skip.includes(node.nodeName)) {
+            return false;
+        }
+
+        const style = window.getComputedStyle(node);
+        const hidden =
+            style.visibility === 'hidden' ||
+            style.display === 'none' ||
+            parseFloat(style.fontSize) === 0;
+
+        return !hidden;
+    }
+
+    static seekForward(node, offset, length) {
+        const state = {node, offset, remainder: length, content: ''};
+        if (!TextSourceRange.seekForwardHelper(node, state)) {
+            return state;
+        }
+
+        for (let current = node; current !== null; current = current.parentElement) {
+            for (let sibling = current.nextSibling; sibling !== null; sibling = sibling.nextSibling) {
+                if (!TextSourceRange.seekForwardHelper(sibling, state)) {
+                    return state;
+                }
+            }
+        }
+
+        return state;
+    }
+
+    static seekForwardHelper(node, state) {
+        if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) {
+            const offset = state.node === node ? state.offset : 0;
+            const remaining = node.length - offset;
+            const consumed = Math.min(remaining, state.remainder);
+            state.content = state.content + node.nodeValue.substring(offset, offset + consumed);
+            state.node = node;
+            state.offset = offset + consumed;
+            state.remainder -= consumed;
+        } else if (TextSourceRange.shouldEnter(node)) {
+            for (let i = 0; i < node.childNodes.length; ++i) {
+                if (!TextSourceRange.seekForwardHelper(node.childNodes[i], state)) {
+                    break;
+                }
+            }
+        }
+
+        return state.remainder > 0;
+    }
+
+    static seekBackward(node, offset, length) {
+        const state = {node, offset, remainder: length, content: ''};
+        if (!TextSourceRange.seekBackwardHelper(node, state)) {
+            return state;
+        }
+
+        for (let current = node; current !== null; current = current.parentElement) {
+            for (let sibling = current.previousSibling; sibling !== null; sibling = sibling.previousSibling) {
+                if (!TextSourceRange.seekBackwardHelper(sibling, state)) {
+                    return state;
+                }
+            }
+        }
+
+        return state;
+    }
+
+    static seekBackwardHelper(node, state) {
+        if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) {
+            const offset = state.node === node ? state.offset : node.length;
+            const remaining = offset;
+            const consumed = Math.min(remaining, state.remainder);
+            state.content = node.nodeValue.substring(offset - consumed, offset) + state.content;
+            state.node = node;
+            state.offset = offset - consumed;
+            state.remainder -= consumed;
+        } else if (TextSourceRange.shouldEnter(node)) {
+            for (let i = node.childNodes.length - 1; i >= 0; --i) {
+                if (!TextSourceRange.seekBackwardHelper(node.childNodes[i], state)) {
+                    break;
+                }
+            }
+        }
+
+        return state.remainder > 0;
+    }
+}
+
+
+class TextSourceElement {
+    constructor(element, content='') {
+        this.element = element;
+        this.content = content;
+    }
+
+    clone() {
+        return new TextSourceElement(this.element, this.content);
+    }
+
+    text() {
+        return this.content;
+    }
+
+    setEndOffset(length) {
+        switch (this.element.nodeName) {
+            case 'BUTTON':
+                this.content = this.element.innerHTML;
+                break;
+            case 'IMG':
+                this.content = this.element.getAttribute('alt');
+                break;
+            default:
+                this.content = this.element.value;
+                break;
+        }
+
+        this.content = this.content || '';
+        this.content = this.content.substring(0, length);
+
+        return this.content.length;
+    }
+
+    setStartOffset(length) {
+        return 0;
+    }
+
+    containsPoint(point) {
+        const rect = this.getRect();
+        return point.x >= rect.left && point.x <= rect.right;
+    }
+
+    getRect() {
+        return this.element.getBoundingClientRect();
+    }
+
+    select() {
+        // NOP
+    }
+
+    deselect() {
+        // NOP
+    }
+
+    equals(other) {
+        return other.element === this.element && other.content === this.content;
+    }
+}
diff --git a/ext/manifest.json b/ext/manifest.json
index 48308b17..ee3bd2ac 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -18,8 +18,7 @@
             "fg/js/api.js",
             "fg/js/document.js",
             "fg/js/popup.js",
-            "fg/js/source-element.js",
-            "fg/js/source-range.js",
+            "fg/js/source.js",
             "fg/js/util.js",
 
             "fg/js/frontend.js"
-- 
cgit v1.2.3


From 82863cd86156d48b9f18dc10a560bedb82641862 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Mon, 14 Aug 2017 19:55:04 -0700
Subject: renaming files

---
 ext/bg/context.html         |  43 +++++++++++++
 ext/bg/js/context.js        |  31 ++++++++++
 ext/bg/js/display-window.js |  54 ----------------
 ext/bg/js/popup.js          |  31 ----------
 ext/bg/js/search.js         |  56 +++++++++++++++++
 ext/bg/popup.html           |  43 -------------
 ext/bg/search.html          |   4 +-
 ext/fg/css/client.css       |   2 +-
 ext/fg/float.html           |  43 +++++++++++++
 ext/fg/frame.html           |  43 -------------
 ext/fg/js/display-frame.js  |  86 --------------------------
 ext/fg/js/float.js          |  88 ++++++++++++++++++++++++++
 ext/fg/js/popup.js          |   4 +-
 ext/manifest.json           |   5 +-
 ext/mixed/css/display.css   | 146 ++++++++++++++++++++++++++++++++++++++++++++
 ext/mixed/css/frame.css     | 146 --------------------------------------------
 16 files changed, 414 insertions(+), 411 deletions(-)
 create mode 100644 ext/bg/context.html
 create mode 100644 ext/bg/js/context.js
 delete mode 100644 ext/bg/js/display-window.js
 delete mode 100644 ext/bg/js/popup.js
 create mode 100644 ext/bg/js/search.js
 delete mode 100644 ext/bg/popup.html
 create mode 100644 ext/fg/float.html
 delete mode 100644 ext/fg/frame.html
 delete mode 100644 ext/fg/js/display-frame.js
 create mode 100644 ext/fg/js/float.js
 create mode 100644 ext/mixed/css/display.css
 delete mode 100644 ext/mixed/css/frame.css

(limited to 'ext/fg')

diff --git a/ext/bg/context.html b/ext/bg/context.html
new file mode 100644
index 00000000..3828c9fe
--- /dev/null
+++ b/ext/bg/context.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap.min.css">
+        <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">
+        <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap-toggle/bootstrap-toggle.min.css">
+        <style type="text/css">
+            body {
+                padding: 10px;
+                text-align: center;
+            }
+
+            .btn-group {
+                display: flex;
+            }
+        </style>
+    </head>
+    <body>
+        <p>
+            <input type="checkbox" id="enable-search">
+        </p>
+        <p>
+            <div class="btn-group" style="white-space: nowrap">
+                <button type="button" id="open-search" title="Search (Alt + Insert)" class="btn btn-default btn-xs glyphicon glyphicon-search"></button>
+                <button type="button" id="open-options" title="Options" class="btn btn-default btn-xs glyphicon glyphicon-wrench"></button>
+                <button type="button" id="open-help" title="Help" class="btn btn-default btn-xs glyphicon glyphicon-question-sign"></button>
+            </div>
+        </p>
+
+        <script src="/mixed/lib/jquery.min.js"></script>
+        <script src="/mixed/lib/bootstrap-toggle/bootstrap-toggle.min.js"></script>
+        <script src="/mixed/lib/handlebars.min.js"></script>
+
+        <script src="/bg/js/api.js"></script>
+        <script src="/bg/js/dictionary.js"></script>
+        <script src="/bg/js/options.js"></script>
+        <script src="/mixed/js/japanese.js"></script>
+        <script src="/mixed/js/request.js"></script>
+
+        <script src="/bg/js/context.js"></script>
+    </body>
+</html>
diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js
new file mode 100644
index 00000000..77cb5166
--- /dev/null
+++ b/ext/bg/js/context.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017  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/>.
+ */
+
+
+$(document).ready(() => {
+    $('#open-search').click(() => apiCommandExec('search'));
+    $('#open-options').click(() => apiCommandExec('options'));
+    $('#open-help').click(() => apiCommandExec('help'));
+
+    optionsLoad().then(options => {
+        const toggle = $('#enable-search');
+        toggle.prop('checked', options.general.enable).change();
+        toggle.bootstrapToggle();
+        toggle.change(() => apiCommandExec('toggle'));
+    });
+});
diff --git a/ext/bg/js/display-window.js b/ext/bg/js/display-window.js
deleted file mode 100644
index cbb96681..00000000
--- a/ext/bg/js/display-window.js
+++ /dev/null
@@ -1,54 +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/>.
- */
-
-
-window.yomichan_window = new class extends Display {
-    constructor() {
-        super($('#spinner'), $('#content'));
-
-        this.search = $('#search').click(this.onSearch.bind(this));
-        this.query = $('#query').on('input', this.onSearchInput.bind(this));
-        this.intro = $('#intro');
-
-        window.wanakana.bind(this.query.get(0));
-    }
-
-    onError(error) {
-        window.alert(`Error: ${error}`);
-    }
-
-    onSearchClear() {
-        this.query.focus().select();
-    }
-
-    onSearchInput() {
-        this.search.prop('disabled', this.query.val().length === 0);
-    }
-
-    async onSearch(e) {
-        e.preventDefault();
-
-        try {
-            this.intro.slideUp();
-            const {length, definitions} = await apiTermsFind(this.query.val());
-            super.termsShow(definitions, await apiOptionsGet());
-        } catch (e) {
-            this.onError(e);
-        }
-    }
-};
diff --git a/ext/bg/js/popup.js b/ext/bg/js/popup.js
deleted file mode 100644
index 77cb5166..00000000
--- a/ext/bg/js/popup.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017  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/>.
- */
-
-
-$(document).ready(() => {
-    $('#open-search').click(() => apiCommandExec('search'));
-    $('#open-options').click(() => apiCommandExec('options'));
-    $('#open-help').click(() => apiCommandExec('help'));
-
-    optionsLoad().then(options => {
-        const toggle = $('#enable-search');
-        toggle.prop('checked', options.general.enable).change();
-        toggle.bootstrapToggle();
-        toggle.change(() => apiCommandExec('toggle'));
-    });
-});
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js
new file mode 100644
index 00000000..87f50c32
--- /dev/null
+++ b/ext/bg/js/search.js
@@ -0,0 +1,56 @@
+/*
+ * 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 DisplaySearch extends Display {
+    constructor() {
+        super($('#spinner'), $('#content'));
+
+        this.search = $('#search').click(this.onSearch.bind(this));
+        this.query = $('#query').on('input', this.onSearchInput.bind(this));
+        this.intro = $('#intro');
+
+        window.wanakana.bind(this.query.get(0));
+    }
+
+    onError(error) {
+        window.alert(`Error: ${error}`);
+    }
+
+    onSearchClear() {
+        this.query.focus().select();
+    }
+
+    onSearchInput() {
+        this.search.prop('disabled', this.query.val().length === 0);
+    }
+
+    async onSearch(e) {
+        e.preventDefault();
+
+        try {
+            this.intro.slideUp();
+            const {length, definitions} = await apiTermsFind(this.query.val());
+            super.termsShow(definitions, await apiOptionsGet());
+        } catch (e) {
+            this.onError(e);
+        }
+    }
+}
+
+window.yomichan_search = new DisplaySearch();
diff --git a/ext/bg/popup.html b/ext/bg/popup.html
deleted file mode 100644
index 60253d6f..00000000
--- a/ext/bg/popup.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-    <head>
-        <meta charset="UTF-8">
-        <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap.min.css">
-        <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">
-        <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap-toggle/bootstrap-toggle.min.css">
-        <style type="text/css">
-            body {
-                padding: 10px;
-                text-align: center;
-            }
-
-            .btn-group {
-                display: flex;
-            }
-        </style>
-    </head>
-    <body>
-        <p>
-            <input type="checkbox" id="enable-search">
-        </p>
-        <p>
-            <div class="btn-group" style="white-space: nowrap">
-                <button type="button" id="open-search" title="Search (Alt + Insert)" class="btn btn-default btn-xs glyphicon glyphicon-search"></button>
-                <button type="button" id="open-options" title="Options" class="btn btn-default btn-xs glyphicon glyphicon-wrench"></button>
-                <button type="button" id="open-help" title="Help" class="btn btn-default btn-xs glyphicon glyphicon-question-sign"></button>
-            </div>
-        </p>
-
-        <script src="/mixed/lib/jquery.min.js"></script>
-        <script src="/mixed/lib/bootstrap-toggle/bootstrap-toggle.min.js"></script>
-        <script src="/mixed/lib/handlebars.min.js"></script>
-
-        <script src="/bg/js/api.js"></script>
-        <script src="/bg/js/dictionary.js"></script>
-        <script src="/bg/js/options.js"></script>
-        <script src="/mixed/js/japanese.js"></script>
-        <script src="/mixed/js/request.js"></script>
-
-        <script src="/bg/js/popup.js"></script>
-    </body>
-</html>
diff --git a/ext/bg/search.html b/ext/bg/search.html
index fe44d74e..d10530f9 100644
--- a/ext/bg/search.html
+++ b/ext/bg/search.html
@@ -5,7 +5,7 @@
         <title>Yomichan Search</title>
         <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap.min.css">
         <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">
-        <link rel="stylesheet" type="text/css" href="/mixed/css/frame.css">
+        <link rel="stylesheet" type="text/css" href="/mixed/css/display.css">
     </head>
     <body>
         <div class="container-fluid">
@@ -45,6 +45,6 @@
         <script src="/mixed/js/display.js"></script>
         <script src="/mixed/js/japanese.js"></script>
 
-        <script src="/bg/js/display-window.js"></script>
+        <script src="/bg/js/search.js"></script>
     </body>
 </html>
diff --git a/ext/fg/css/client.css b/ext/fg/css/client.css
index 9f566480..b5b1f6bd 100644
--- a/ext/fg/css/client.css
+++ b/ext/fg/css/client.css
@@ -17,7 +17,7 @@
  */
 
 
-iframe#yomichan-popup {
+iframe#yomichan-float {
     all: initial;
     background-color: #fff;
     border: 1px solid #999;
diff --git a/ext/fg/float.html b/ext/fg/float.html
new file mode 100644
index 00000000..a3b66c92
--- /dev/null
+++ b/ext/fg/float.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <title></title>
+        <link rel="stylesheet" href="/mixed/lib/bootstrap/css/bootstrap.min.css">
+        <link rel="stylesheet" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">
+        <link rel="stylesheet" href="/mixed/css/display.css">
+        <style type="text/css">
+            .entry, .note {
+                padding-left: 10px;
+                padding-right: 10px;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="spinner">
+            <img src="/mixed/img/spinner.gif">
+        </div>
+
+        <div id="definitions"></div>
+
+        <div id="error-orphaned">
+            <div class="container-fluid">
+                <h1>Yomichan Updated!</h1>
+                <p>
+                    The Yomichan extension has been updated to a new version! In order to continue
+                    viewing definitions on this page you must reload this tab or restart your browser.
+                </p>
+            </div>
+        </div>
+
+        <script src="/mixed/lib/jquery.min.js"></script>
+        <script src="/mixed/lib/wanakana.min.js"></script>
+
+        <script src="/fg/js/api.js"></script>
+        <script src="/fg/js/util.js"></script>
+        <script src="/mixed/js/audio.js"></script>
+        <script src="/mixed/js/display.js"></script>
+
+        <script src="/fg/js/float.js"></script>
+    </body>
+</html>
diff --git a/ext/fg/frame.html b/ext/fg/frame.html
deleted file mode 100644
index dda3ef06..00000000
--- a/ext/fg/frame.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-    <head>
-        <meta charset="UTF-8">
-        <title></title>
-        <link rel="stylesheet" href="/mixed/lib/bootstrap/css/bootstrap.min.css">
-        <link rel="stylesheet" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">
-        <link rel="stylesheet" href="/mixed/css/frame.css">
-        <style type="text/css">
-            .entry, .note {
-                padding-left: 10px;
-                padding-right: 10px;
-            }
-        </style>
-    </head>
-    <body>
-        <div id="spinner">
-            <img src="/mixed/img/spinner.gif">
-        </div>
-
-        <div id="definitions"></div>
-
-        <div id="error-orphaned">
-            <div class="container-fluid">
-                <h1>Yomichan Updated!</h1>
-                <p>
-                    The Yomichan extension has been updated to a new version! In order to continue
-                    viewing definitions on this page you must reload this tab or restart your browser.
-                </p>
-            </div>
-        </div>
-
-        <script src="/fg/js/api.js"></script>
-        <script src="/fg/js/dictionary.js"></script>
-        <script src="/fg/js/util.js"></script>
-        <script src="/mixed/js/audio.js"></script>
-        <script src="/mixed/js/display.js"></script>
-        <script src="/mixed/lib/jquery.min.js"></script>
-        <script src="/mixed/lib/wanakana.min.js"></script>
-
-        <script src="/fg/js/display-frame.js"></script>
-    </body>
-</html>
diff --git a/ext/fg/js/display-frame.js b/ext/fg/js/display-frame.js
deleted file mode 100644
index e3f3e692..00000000
--- a/ext/fg/js/display-frame.js
+++ /dev/null
@@ -1,86 +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/>.
- */
-
-
-window.yomichan_frame = new class extends Display {
-    constructor() {
-        super($('#spinner'), $('#definitions'));
-        $(window).on('message', this.onMessage.bind(this));
-    }
-
-    onError(error) {
-        if (window.yomichan_orphaned) {
-            this.onOrphaned();
-        } else {
-            window.alert(`Error: ${error}`);
-        }
-    }
-
-    onOrphaned() {
-        $('#definitions').hide();
-        $('#error-orphaned').show();
-    }
-
-    onSearchClear() {
-        window.parent.postMessage('popupClose', '*');
-    }
-
-    onSelectionCopy() {
-        window.parent.postMessage('selectionCopy', '*');
-    }
-
-    onMessage(e) {
-        const handlers = {
-            termsShow: ({definitions, options, context}) => {
-                this.termsShow(definitions, options, context);
-            },
-
-            kanjiShow: ({definitions, options, context}) => {
-                this.kanjiShow(definitions, options, context);
-            },
-
-            orphaned: () => {
-                this.onOrphaned();
-            }
-        };
-
-        const {action, params} = e.originalEvent.data;
-        const handler = handlers[action];
-        if (handler) {
-            handler(params);
-        }
-    }
-
-    onKeyDown(e) {
-        const handlers = {
-            67: /* c */ () => {
-                if (e.ctrlKey && !window.getSelection().toString()) {
-                    this.onSelectionCopy();
-                    return true;
-                }
-            }
-        };
-
-        const handler = handlers[e.keyCode];
-        if (handler && handler()) {
-            e.preventDefault();
-        } else {
-            super.onKeyDown(e);
-        }
-    }
-};
diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js
new file mode 100644
index 00000000..59293239
--- /dev/null
+++ b/ext/fg/js/float.js
@@ -0,0 +1,88 @@
+/*
+ * 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 DisplayFloat extends Display {
+    constructor() {
+        super($('#spinner'), $('#definitions'));
+        $(window).on('message', e => this.onMessage(e));
+    }
+
+    onError(error) {
+        if (window.yomichan_orphaned) {
+            this.onOrphaned();
+        } else {
+            window.alert(`Error: ${error}`);
+        }
+    }
+
+    onOrphaned() {
+        $('#definitions').hide();
+        $('#error-orphaned').show();
+    }
+
+    onSearchClear() {
+        window.parent.postMessage('popupClose', '*');
+    }
+
+    onSelectionCopy() {
+        window.parent.postMessage('selectionCopy', '*');
+    }
+
+    onMessage(e) {
+        const handlers = {
+            termsShow: ({definitions, options, context}) => {
+                this.termsShow(definitions, options, context);
+            },
+
+            kanjiShow: ({definitions, options, context}) => {
+                this.kanjiShow(definitions, options, context);
+            },
+
+            orphaned: () => {
+                this.onOrphaned();
+            }
+        };
+
+        const {action, params} = e.originalEvent.data;
+        const handler = handlers[action];
+        if (handler) {
+            handler(params);
+        }
+    }
+
+    onKeyDown(e) {
+        const handlers = {
+            67: /* c */ () => {
+                if (e.ctrlKey && !window.getSelection().toString()) {
+                    this.onSelectionCopy();
+                    return true;
+                }
+            }
+        };
+
+        const handler = handlers[e.keyCode];
+        if (handler && handler()) {
+            e.preventDefault();
+        } else {
+            super.onKeyDown(e);
+        }
+    }
+}
+
+window.yomichan_display = new DisplayFloat();
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index ba3289d4..8e61169a 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -20,10 +20,10 @@
 class Popup {
     constructor() {
         this.container = document.createElement('iframe');
-        this.container.id = 'yomichan-popup';
+        this.container.id = 'yomichan-float';
         this.container.addEventListener('mousedown', e => e.stopPropagation());
         this.container.addEventListener('scroll', e => e.stopPropagation());
-        this.container.setAttribute('src', chrome.extension.getURL('/fg/frame.html'));
+        this.container.setAttribute('src', chrome.extension.getURL('/fg/float.html'));
         this.container.style.width = '0px';
         this.container.style.height = '0px';
         this.injected = null;
diff --git a/ext/manifest.json b/ext/manifest.json
index ee3bd2ac..a8b68bfb 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -7,7 +7,7 @@
     "icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"},
     "browser_action": {
         "default_icon": {"19": "mixed/img/icon19.png", "38": "mixed/img/icon38.png"},
-        "default_popup": "bg/popup.html"
+        "default_popup": "bg/context.html"
     },
 
     "author": "Alex Yatskov",
@@ -20,7 +20,6 @@
             "fg/js/popup.js",
             "fg/js/source.js",
             "fg/js/util.js",
-
             "fg/js/frontend.js"
         ],
         "css": ["fg/css/client.css"]
@@ -48,7 +47,7 @@
             "description": "Open search window"
         }
     },
-    "web_accessible_resources": ["fg/frame.html"],
+    "web_accessible_resources": ["fg/float.html"],
     "applications": {
         "gecko": {
             "id": "yomichan-live@foosoft.net",
diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css
new file mode 100644
index 00000000..8b1819bd
--- /dev/null
+++ b/ext/mixed/css/display.css
@@ -0,0 +1,146 @@
+/*
+ * 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 entrys 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/>.
+ */
+
+
+/*
+ * Fonts
+ */
+
+@font-face {
+    font-family: kanji-stroke-orders;
+    src: url('/mixed/ttf/kanji-stroke-orders.ttf');
+}
+
+/*
+ * General
+ */
+
+hr {
+    padding: 0px;
+    margin: 0px;
+}
+
+#spinner {
+    bottom: 5px;
+    display: none;
+    position: fixed;
+    right: 5px;
+}
+
+#error-orphaned {
+    display: none;
+}
+
+
+/*
+ * Entries
+ */
+
+.entry, .note {
+    padding-top: 10px;
+    padding-bottom: 10px;
+}
+
+.tag-default {
+    background-color: #8a8a91;
+}
+
+.tag-name {
+    background-color: #5cb85c;
+}
+
+.tag-expression {
+    background-color: #f0ad4e;
+}
+
+.tag-popular {
+    background-color: #0275d8;
+}
+
+.tag-frequent {
+    background-color: #5bc0de;
+}
+
+.tag-archaism {
+    background-color: #d9534f;
+}
+
+.tag-dictionary {
+    background-color: #aa66cc;
+}
+
+.actions .disabled {
+    pointer-events: none;
+    cursor: default;
+}
+
+.actions .disabled img {
+    -webkit-filter: grayscale(100%);
+    opacity: 0.25;
+}
+
+.actions .pending {
+    visibility: hidden;
+}
+
+.actions {
+    display: inline-block;
+    float: right;
+}
+
+.actions:after {
+    clear: both;
+    content: '';
+    display: block;
+}
+
+.expression {
+    display: inline-block;
+    font-size: 24px;
+}
+
+.expression a {
+    border-bottom: 1px #777 dashed;
+    color: #333;
+    text-decoration: none;
+}
+
+.reasons {
+    color: #777;
+    display: inline-block;
+}
+
+.glossary ol, .glossary ul {
+    padding-left: 1.4em;
+}
+
+.glossary li {
+    color: #777;
+}
+
+.glossary-item {
+    color: #000;
+}
+
+.glyph {
+    font-family: kanji-stroke-orders;
+    font-size: 120px;
+    line-height: 120px;
+    padding: 0.01em;
+    vertical-align: top;
+}
diff --git a/ext/mixed/css/frame.css b/ext/mixed/css/frame.css
deleted file mode 100644
index 8b1819bd..00000000
--- a/ext/mixed/css/frame.css
+++ /dev/null
@@ -1,146 +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 entrys 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/>.
- */
-
-
-/*
- * Fonts
- */
-
-@font-face {
-    font-family: kanji-stroke-orders;
-    src: url('/mixed/ttf/kanji-stroke-orders.ttf');
-}
-
-/*
- * General
- */
-
-hr {
-    padding: 0px;
-    margin: 0px;
-}
-
-#spinner {
-    bottom: 5px;
-    display: none;
-    position: fixed;
-    right: 5px;
-}
-
-#error-orphaned {
-    display: none;
-}
-
-
-/*
- * Entries
- */
-
-.entry, .note {
-    padding-top: 10px;
-    padding-bottom: 10px;
-}
-
-.tag-default {
-    background-color: #8a8a91;
-}
-
-.tag-name {
-    background-color: #5cb85c;
-}
-
-.tag-expression {
-    background-color: #f0ad4e;
-}
-
-.tag-popular {
-    background-color: #0275d8;
-}
-
-.tag-frequent {
-    background-color: #5bc0de;
-}
-
-.tag-archaism {
-    background-color: #d9534f;
-}
-
-.tag-dictionary {
-    background-color: #aa66cc;
-}
-
-.actions .disabled {
-    pointer-events: none;
-    cursor: default;
-}
-
-.actions .disabled img {
-    -webkit-filter: grayscale(100%);
-    opacity: 0.25;
-}
-
-.actions .pending {
-    visibility: hidden;
-}
-
-.actions {
-    display: inline-block;
-    float: right;
-}
-
-.actions:after {
-    clear: both;
-    content: '';
-    display: block;
-}
-
-.expression {
-    display: inline-block;
-    font-size: 24px;
-}
-
-.expression a {
-    border-bottom: 1px #777 dashed;
-    color: #333;
-    text-decoration: none;
-}
-
-.reasons {
-    color: #777;
-    display: inline-block;
-}
-
-.glossary ol, .glossary ul {
-    padding-left: 1.4em;
-}
-
-.glossary li {
-    color: #777;
-}
-
-.glossary-item {
-    color: #000;
-}
-
-.glyph {
-    font-family: kanji-stroke-orders;
-    font-size: 120px;
-    line-height: 120px;
-    padding: 0.01em;
-    vertical-align: top;
-}
-- 
cgit v1.2.3


From bdf231082f4b4ca7c4c90d8b0cd40b6c4201723d Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Mon, 14 Aug 2017 21:43:09 -0700
Subject: lots of fixes to backend

---
 ext/bg/context.html      |  5 +----
 ext/bg/js/anki.js        |  9 +++++++++
 ext/bg/js/api.js         |  2 +-
 ext/bg/js/backend.js     | 20 +++++++++++++------
 ext/bg/js/context.js     |  4 ++--
 ext/bg/js/database.js    |  2 +-
 ext/bg/js/deinflector.js |  2 +-
 ext/bg/js/dictionary.js  |  2 +-
 ext/bg/js/handlebars.js  |  2 +-
 ext/bg/js/search.js      |  5 ++---
 ext/bg/js/settings.js    | 50 ++++++++++++++++++++++++------------------------
 ext/bg/js/util.js        |  7 ++++++-
 ext/bg/settings.html     |  3 +--
 ext/fg/js/api.js         | 22 ++++++++++++++-------
 ext/fg/js/frontend.js    |  2 +-
 ext/fg/js/popup.js       |  2 +-
 ext/mixed/js/display.js  |  9 +++++----
 17 files changed, 87 insertions(+), 61 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/bg/context.html b/ext/bg/context.html
index 3828c9fe..8a72acc7 100644
--- a/ext/bg/context.html
+++ b/ext/bg/context.html
@@ -30,13 +30,10 @@
 
         <script src="/mixed/lib/jquery.min.js"></script>
         <script src="/mixed/lib/bootstrap-toggle/bootstrap-toggle.min.js"></script>
-        <script src="/mixed/lib/handlebars.min.js"></script>
 
         <script src="/bg/js/api.js"></script>
-        <script src="/bg/js/dictionary.js"></script>
         <script src="/bg/js/options.js"></script>
-        <script src="/mixed/js/japanese.js"></script>
-        <script src="/mixed/js/request.js"></script>
+        <script src="/bg/js/util.js"></script>
 
         <script src="/bg/js/context.js"></script>
     </body>
diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js
index f0ec4571..c327969f 100644
--- a/ext/bg/js/anki.js
+++ b/ext/bg/js/anki.js
@@ -17,6 +17,10 @@
  */
 
 
+/*
+ * AnkiConnect
+ */
+
 class AnkiConnect {
     constructor(server) {
         this.server = server;
@@ -68,6 +72,11 @@ class AnkiConnect {
     }
 }
 
+
+/*
+ * AnkiNull
+ */
+
 class AnkiNull {
     async addNote(note) {
         return null;
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
index 024ba75e..b55e306f 100644
--- a/ext/bg/js/api.js
+++ b/ext/bg/js/api.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index e8c9452c..97e5602a 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -77,7 +77,11 @@ class Backend {
 
         const handlers = {
             optionsGet: ({callback}) => {
-                forward(optionsLoad(), callback);
+                forward(apiOptionsGet(), callback);
+            },
+
+            optionsSet: ({options, callback}) => {
+                forward(apiOptionsSet(options), callback);
             },
 
             kanjiFind: ({text, callback}) => {
@@ -88,10 +92,6 @@ class Backend {
                 forward(apiTermsFind(text), callback);
             },
 
-            templateRender: ({template, data, callback}) => {
-                forward(apiTemplateRender(template, data), callback);
-            },
-
             definitionAdd: ({definition, mode, callback}) => {
                 forward(apiDefinitionAdd(definition, mode), callback);
             },
@@ -102,6 +102,14 @@ class Backend {
 
             noteView: ({noteId}) => {
                 forward(apiNoteView(noteId), callback);
+            },
+
+            templateRender: ({template, data, callback}) => {
+                forward(apiTemplateRender(template, data), callback);
+            },
+
+            commandExec: ({command, callback}) => {
+                forward(apiCommandExec(command), callback);
             }
         };
 
diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js
index 77cb5166..689d6863 100644
--- a/ext/bg/js/context.js
+++ b/ext/bg/js/context.js
@@ -17,7 +17,7 @@
  */
 
 
-$(document).ready(() => {
+$(document).ready(utilAsync(() => {
     $('#open-search').click(() => apiCommandExec('search'));
     $('#open-options').click(() => apiCommandExec('options'));
     $('#open-help').click(() => apiCommandExec('help'));
@@ -28,4 +28,4 @@ $(document).ready(() => {
         toggle.bootstrapToggle();
         toggle.change(() => apiCommandExec('toggle'));
     });
-});
+}));
diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js
index b38e00db..e00cb7a3 100644
--- a/ext/bg/js/database.js
+++ b/ext/bg/js/database.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
diff --git a/ext/bg/js/deinflector.js b/ext/bg/js/deinflector.js
index 8b67761b..0abde99d 100644
--- a/ext/bg/js/deinflector.js
+++ b/ext/bg/js/deinflector.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js
index 6f9b30e4..c8d431b9 100644
--- a/ext/bg/js/dictionary.js
+++ b/ext/bg/js/dictionary.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js
index df98bef1..debb0690 100644
--- a/ext/bg/js/handlebars.js
+++ b/ext/bg/js/handlebars.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js
index 87f50c32..54cda8ec 100644
--- a/ext/bg/js/search.js
+++ b/ext/bg/js/search.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -41,9 +41,8 @@ class DisplaySearch extends Display {
     }
 
     async onSearch(e) {
-        e.preventDefault();
-
         try {
+            e.preventDefault();
             this.intro.slideUp();
             const {length, definitions} = await apiTermsFind(this.query.val());
             super.termsShow(definitions, await apiOptionsGet());
diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js
index ce9e14a2..89b1581f 100644
--- a/ext/bg/js/settings.js
+++ b/ext/bg/js/settings.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -90,12 +90,12 @@ function formUpdateVisibility(options) {
     }
 }
 
-async function onFormOptionsChanged(e) {(async () => {
-    if (!e.originalEvent && !e.isTrigger) {
-        return;
-    }
-
+async function onFormOptionsChanged(e) {
     try {
+        if (!e.originalEvent && !e.isTrigger) {
+            return;
+        }
+
         ankiErrorShow();
         ankiSpinnerShow(true);
 
@@ -116,9 +116,9 @@ async function onFormOptionsChanged(e) {(async () => {
     } finally {
         ankiSpinnerShow(false);
     }
-})();}
+}
 
-function onReady() {(async () => {
+async function onReady() {
     const options = await optionsLoad();
 
     $('#show-usage-guide').prop('checked', options.general.showGuide);
@@ -139,16 +139,16 @@ function onReady() {(async () => {
     $('#scan-length').val(options.scanning.length);
     $('#scan-modifier-key').val(options.scanning.modifier);
 
-    $('#dict-purge').click(onDictionaryPurge);
-    $('#dict-file').change(onDictionaryImport);
+    $('#dict-purge').click(utilAsync(onDictionaryPurge));
+    $('#dict-file').change(utilAsync(onDictionaryImport));
 
     $('#anki-enable').prop('checked', options.anki.enable);
     $('#card-tags').val(options.anki.tags.join(' '));
     $('#generate-html-cards').prop('checked', options.anki.htmlCards);
     $('#sentence-detection-extent').val(options.anki.sentenceExt);
     $('#interface-server').val(options.anki.server);
-    $('input, select').not('.anki-model').change(onFormOptionsChanged);
-    $('.anki-model').change(onAnkiModelChanged);
+    $('input, select').not('.anki-model').change(utilAsync(onFormOptionsChanged));
+    $('.anki-model').change(utilAsync(onAnkiModelChanged));
 
     try {
         await dictionaryGroupsPopulate(options);
@@ -163,9 +163,9 @@ function onReady() {(async () => {
     }
 
     formUpdateVisibility(options);
-})();}
+}
 
-$(document).ready(onReady);
+$(document).ready(utilAsync(onReady));
 
 
 /*
@@ -237,7 +237,7 @@ async function dictionaryGroupsPopulate(options) {
     });
 }
 
-async function onDictionaryPurge(e) {(async () => {
+async function onDictionaryPurge(e) {
     e.preventDefault();
 
     const dictControls = $('#dict-importer, #dict-groups').hide();
@@ -261,9 +261,9 @@ async function onDictionaryPurge(e) {(async () => {
         dictControls.show();
         dictProgress.hide();
     }
-})();}
+}
 
-function onDictionaryImport(e) {(async () => {
+async function onDictionaryImport(e) {
     const dictFile = $('#dict-file');
     const dictControls = $('#dict-importer').hide();
     const dictProgress = $('#dict-import-progress').show();
@@ -291,7 +291,7 @@ function onDictionaryImport(e) {(async () => {
         dictControls.show();
         dictProgress.hide();
     }
-})();}
+}
 
 
 /*
@@ -398,7 +398,7 @@ async function ankiFieldsPopulate(element, options) {
         container.append($(html));
     }
 
-    tab.find('.anki-field-value').change(onFormOptionsChanged);
+    tab.find('.anki-field-value').change(utilAsync(onFormOptionsChanged));
     tab.find('.marker-link').click(onAnkiMarkerClicked);
 }
 
@@ -408,12 +408,12 @@ function onAnkiMarkerClicked(e) {
     $(link).closest('.input-group').find('.anki-field-value').val(`{${link.text}}`).trigger('change');
 }
 
-function onAnkiModelChanged(e) {(async () => {
-    if (!e.originalEvent) {
-        return;
-    }
-
+async function onAnkiModelChanged(e) {
     try {
+        if (!e.originalEvent) {
+            return;
+        }
+
         ankiErrorShow();
         ankiSpinnerShow(true);
 
@@ -431,4 +431,4 @@ function onAnkiModelChanged(e) {(async () => {
     } finally {
         ankiSpinnerShow(false);
     }
-})();}
+}
diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js
index 9dc57950..11ec23eb 100644
--- a/ext/bg/js/util.js
+++ b/ext/bg/js/util.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -16,6 +16,11 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+function utilAsync(func) {
+    return function(...args) {
+        func.apply(this, args);
+    };
+}
 
 function utilBackend() {
     return chrome.extension.getBackgroundPage().yomichan_backend;
diff --git a/ext/bg/settings.html b/ext/bg/settings.html
index 719c67a2..237750b3 100644
--- a/ext/bg/settings.html
+++ b/ext/bg/settings.html
@@ -276,8 +276,7 @@
         <script src="/mixed/lib/bootstrap/js/bootstrap.min.js"></script>
         <script src="/mixed/lib/handlebars.min.js"></script>
 
-        <script src="/bg/js/anki-connect.js"></script>
-        <script src="/bg/js/anki-null.js"></script>
+        <script src="/bg/js/anki.js"></script>
         <script src="/bg/js/api.js"></script>
         <script src="/bg/js/dictionary.js"></script>
         <script src="/bg/js/handlebars.js"></script>
diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js
index b4d75c3c..174531ba 100644
--- a/ext/fg/js/api.js
+++ b/ext/fg/js/api.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -17,6 +17,10 @@
  */
 
 
+function apiOptionsSet(options) {
+    return utilInvoke('optionsSet', {options});
+}
+
 function apiOptionsGet() {
     return utilInvoke('optionsGet');
 }
@@ -29,18 +33,22 @@ function apiKanjiFind(text) {
     return utilInvoke('kanjiFind', {text});
 }
 
-function apiTemplateRender(template, data) {
-    return utilInvoke('templateRender', {data, template});
+function apiDefinitionAdd(definition, mode) {
+    return utilInvoke('definitionAdd', {definition, mode});
 }
 
 function apiDefinitionsAddable(definitions, modes) {
     return utilInvoke('definitionsAddable', {definitions, modes}).catch(() => null);
 }
 
-function apiDefinitionAdd(definition, mode) {
-    return utilInvoke('definitionAdd', {definition, mode});
-}
-
 function apiNoteView(noteId) {
     return utilInvoke('noteView', {noteId});
 }
+
+function apiTemplateRender(template, data) {
+    return utilInvoke('templateRender', {data, template});
+}
+
+function apiCommandExec(command) {
+    return utilInvoke('commandExec', {command});
+}
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 005139e6..cc4d99c8 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -230,7 +230,7 @@ class Frontend {
 
         const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
         const url = window.location.href;
-        this.popup.showKanji(
+        this.popup.kanjiShow(
             textSource.getRect(),
             definitions,
             this.options,
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index 8e61169a..8cb16b5a 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -102,7 +102,7 @@ class Popup {
 
     async kanjiShow(elementRect, definitions, options, context) {
         await this.show(elementRect, options);
-        this.invokeApi('termsShow', {definitions, options, context});
+        this.invokeApi('kanjiShow', {definitions, options, context});
     }
 
     invokeApi(action, params={}) {
diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js
index 97dd7d5c..21748f5d 100644
--- a/ext/mixed/js/display.js
+++ b/ext/mixed/js/display.js
@@ -42,7 +42,7 @@ class Display {
 
     onSourceTermView(e) {
         e.preventDefault();
-        this.sourceBack();
+        this.sourceTermView();
     }
 
     async onKanjiLookup(e) {
@@ -154,7 +154,7 @@ class Display {
 
             66: /* b */ () => {
                 if (e.altKey) {
-                    this.sourceBack();
+                    this.sourceTermView();
                     return true;
                 }
             },
@@ -276,6 +276,7 @@ class Display {
             this.entryScrollIntoView(context && context.index || 0);
 
             $('.action-add-note').click(this.onNoteAdd.bind(this));
+            $('.action-view-note').click(this.onNoteView.bind(this));
             $('.source-term').click(this.onSourceTermView.bind(this));
 
             await this.adderButtonUpdate(['kanji'], sequence);
@@ -288,7 +289,7 @@ class Display {
         try {
             this.spinner.show();
 
-            const states = apiDefinitionsAddable(this.definitions, modes);
+            const states = await apiDefinitionsAddable(this.definitions, modes);
             if (!states || sequence !== this.sequence) {
                 return;
             }
@@ -332,7 +333,7 @@ class Display {
         this.index = index;
     }
 
-    sourceBack() {
+    sourceTermView() {
         if (this.context && this.context.source) {
             const context = {
                 url: this.context.source.url,
-- 
cgit v1.2.3


From 211e5d1155e82ccbbc188dc889600459a62229fb Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Mon, 14 Aug 2017 23:22:37 -0700
Subject: cleanup

---
 ext/fg/js/document.js |  2 +-
 ext/fg/js/float.js    |  4 ++--
 ext/fg/js/frontend.js | 18 +++++++++---------
 ext/fg/js/util.js     |  6 ++++++
 4 files changed, 18 insertions(+), 12 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js
index 582b6770..17cca613 100644
--- a/ext/fg/js/document.js
+++ b/ext/fg/js/document.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js
index 59293239..22374f8b 100644
--- a/ext/fg/js/float.js
+++ b/ext/fg/js/float.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -20,7 +20,7 @@
 class DisplayFloat extends Display {
     constructor() {
         super($('#spinner'), $('#definitions'));
-        $(window).on('message', e => this.onMessage(e));
+        $(window).on('message', utilAsync(this.onMessage.bind(this)));
     }
 
     onError(error) {
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index cc4d99c8..7d26f946 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -33,14 +33,14 @@ class Frontend {
         try {
             this.options = await apiOptionsGet();
 
-            window.addEventListener('message', e => this.onFrameMessage(e));
-            window.addEventListener('mousedown', e => this.onMouseDown(e));
-            window.addEventListener('mousemove', e => this.onMouseMove(e));
-            window.addEventListener('mouseover', e => this.onMouseOver(e));
-            window.addEventListener('mouseup', e => this.onMouseUp(e));
-            window.addEventListener('resize', e => this.onResize(e));
-
-            chrome.runtime.onMessage.addListener(({action, params}, sender, callback) => this.onBgMessage(action, params, sender, callback));
+            window.addEventListener('message', this.onFrameMessage.bind(this));
+            window.addEventListener('mousedown', this.onMouseDown.bind(this));
+            window.addEventListener('mousemove', this.onMouseMove.bind(this));
+            window.addEventListener('mouseover', this.onMouseOver.bind(this));
+            window.addEventListener('mouseup', this.onMouseUp.bind(this));
+            window.addEventListener('resize', this.onResize.bind(this));
+
+            chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
         } catch (e) {
             this.onError(e);
         }
@@ -124,7 +124,7 @@ class Frontend {
         this.searchClear();
     }
 
-    onBgMessage(action, params, sender, callback) {
+    onBgMessage({action, params}, sender, callback) {
         const handlers = {
             optionsSet: options => {
                 this.options = options;
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
index afa895ba..3faf3b47 100644
--- a/ext/fg/js/util.js
+++ b/ext/fg/js/util.js
@@ -17,6 +17,12 @@
  */
 
 
+function utilAsync(func) {
+    return function(...args) {
+        func.apply(this, args);
+    };
+}
+
 function utilInvoke(action, params={}) {
     return new Promise((resolve, reject) => {
         try {
-- 
cgit v1.2.3


From 3362a68e06a16efa87a2ad7cc75f18f8f4b2ea25 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Tue, 15 Aug 2017 20:04:15 -0700
Subject: frontend cleanup

---
 ext/fg/js/frontend.js | 44 +++++++++++++++++++++++---------------------
 ext/fg/js/popup.js    | 12 ++++++------
 ext/fg/js/source.js   | 10 +++++++++-
 ext/fg/js/util.js     |  2 +-
 4 files changed, 39 insertions(+), 29 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 7d26f946..58a721bf 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -21,10 +21,10 @@ class Frontend {
     constructor() {
         this.popup = new Popup();
         this.popupTimer = null;
-        this.lastMousePos = null;
+        this.mousePosLast = null;
         this.mouseDownLeft = false;
         this.mouseDownMiddle = false;
-        this.lastTextSource = null;
+        this.textSourceLast = null;
         this.pendingLookup = false;
         this.options = null;
     }
@@ -53,7 +53,7 @@ class Frontend {
     }
 
     onMouseMove(e) {
-        this.lastMousePos = {x: e.clientX, y: e.clientY};
+        this.mousePosLast = {x: e.clientX, y: e.clientY};
         this.popupTimerClear();
 
         if (!this.options.general.enable) {
@@ -75,7 +75,7 @@ class Frontend {
             return;
         }
 
-        const searchFunc = () => this.searchAt(this.lastMousePos);
+        const searchFunc = () => this.searchAt(this.mousePosLast);
         if (this.options.scanning.modifier === 'none') {
             this.popupTimerSet(searchFunc);
         } else {
@@ -84,7 +84,7 @@ class Frontend {
     }
 
     onMouseDown(e) {
-        this.lastMousePos = {x: e.clientX, y: e.clientY};
+        this.mousePosLast = {x: e.clientX, y: e.clientY};
         this.popupTimerClear();
         this.searchClear();
 
@@ -143,13 +143,7 @@ class Frontend {
     }
 
     onError(error) {
-        if (window.yomichan_orphaned) {
-            if (this.lastTextSource && this.options.scanning.modifier !== 'none') {
-                this.popup.showOrphaned(this.lastTextSource.getRect(), this.options);
-            }
-        } else {
-            window.alert(`Error: ${error}`);
-        }
+        window.alert(`Error: ${error}`);
     }
 
     popupTimerSet(callback) {
@@ -165,18 +159,20 @@ class Frontend {
     }
 
     async searchAt(point) {
+        let textSource = null;
+
         try {
             if (this.pendingLookup) {
                 return;
             }
 
-            const textSource = docRangeFromPoint(point);
+            textSource = docRangeFromPoint(point);
             if (!textSource || !textSource.containsPoint(point)) {
                 docImposterDestroy();
                 return;
             }
 
-            if (this.lastTextSource && this.lastTextSource.equals(textSource)) {
+            if (this.textSourceLast && this.textSourceLast.equals(textSource)) {
                 return;
             }
 
@@ -186,7 +182,13 @@ class Frontend {
                 await this.searchKanji(textSource);
             }
         } catch (e) {
-            this.onError(e);
+            if (window.yomichan_orphaned) {
+                if (textSource && this.options.scanning.modifier !== 'none') {
+                    this.popup.showOrphaned(textSource.getRect(), this.options);
+                }
+            } else {
+                this.onError(e);
+            }
         } finally {
             docImposterDestroy();
             this.pendingLookup = false;
@@ -212,7 +214,7 @@ class Frontend {
             {sentence, url}
         );
 
-        this.lastTextSource = textSource;
+        this.textSourceLast = textSource;
         if (this.options.scanning.selectText) {
             textSource.select();
         }
@@ -237,7 +239,7 @@ class Frontend {
             {sentence, url}
         );
 
-        this.lastTextSource = textSource;
+        this.textSourceLast = textSource;
         if (this.options.scanning.selectText) {
             textSource.select();
         }
@@ -249,11 +251,11 @@ class Frontend {
         docImposterDestroy();
         this.popup.hide();
 
-        if (this.options.scanning.selectText && this.lastTextSource) {
-            this.lastTextSource.deselect();
+        if (this.options.scanning.selectText && this.textSourceLast) {
+            this.textSourceLast.deselect();
         }
 
-        this.lastTextSource = null;
+        this.textSourceLast = null;
     }
 }
 
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index 8cb16b5a..03958832 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -87,6 +87,11 @@ class Popup {
         this.container.style.visibility = 'visible';
     }
 
+    async showOrphaned(elementRect, options) {
+        await this.show(elementRect, options);
+        this.invokeApi('orphaned');
+    }
+
     hide() {
         this.container.style.visibility = 'hidden';
     }
@@ -108,9 +113,4 @@ class Popup {
     invokeApi(action, params={}) {
         this.container.contentWindow.postMessage({action, params}, '*');
     }
-
-    async onOrphaned(elementRect, options) {
-        await this.show(elementRect, options);
-        this.invokeApi('orphaned');
-    }
 }
diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js
index 210dda12..3b6ecb2a 100644
--- a/ext/fg/js/source.js
+++ b/ext/fg/js/source.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -17,6 +17,10 @@
  */
 
 
+/*
+ * TextSourceRange
+ */
+
 class TextSourceRange {
     constructor(range, content='') {
         this.range = range;
@@ -176,6 +180,10 @@ class TextSourceRange {
 }
 
 
+/*
+ * TextSourceElement
+ */
+
 class TextSourceElement {
     constructor(element, content='') {
         this.element = element;
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
index 3faf3b47..5eff4018 100644
--- a/ext/fg/js/util.js
+++ b/ext/fg/js/util.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017  Alex Yatskov <alex@foosoft.net>
  * Author: Alex Yatskov <alex@foosoft.net>
  *
  * This program is free software: you can redistribute it and/or modify
-- 
cgit v1.2.3


From e19933f9804abf4e64d96143bbb58f8059de5b38 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Tue, 15 Aug 2017 21:36:30 -0700
Subject: jisho.org audio support

---
 ext/bg/js/api.js        |  3 +++
 ext/bg/js/backend.js    |  4 ++++
 ext/bg/settings.html    |  1 +
 ext/fg/js/api.js        |  4 ++++
 ext/mixed/js/audio.js   | 30 +++++++++++++++++++++++++++++-
 ext/mixed/js/display.js |  3 +--
 6 files changed, 42 insertions(+), 3 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
index b55e306f..8b4c3896 100644
--- a/ext/bg/js/api.js
+++ b/ext/bg/js/api.js
@@ -127,3 +127,6 @@ async function apiCommandExec(command) {
     }
 }
 
+async function apiAudioGetUrl(definition, source) {
+    return audioBuildUrl(definition, source);
+}
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index 6c77ba7e..9602d385 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -110,6 +110,10 @@ class Backend {
 
             commandExec: ({command, callback}) => {
                 forward(apiCommandExec(command), callback);
+            },
+
+            audioGetUrl: ({definition, source, callback}) => {
+                forward(apiAudioGetUrl(definition, source), callback);
             }
         };
 
diff --git a/ext/bg/settings.html b/ext/bg/settings.html
index 218f9f7d..ba155d4a 100644
--- a/ext/bg/settings.html
+++ b/ext/bg/settings.html
@@ -48,6 +48,7 @@
                         <option value="disabled">Disabled</option>
                         <option value="jpod101">JapanesePod101</option>
                         <option value="jpod101-alternate">JapanesePod101 (alternate)</option>
+                        <option value="jisho">Jisho</option>
                     </select>
                 </div>
 
diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js
index 174531ba..151882eb 100644
--- a/ext/fg/js/api.js
+++ b/ext/fg/js/api.js
@@ -52,3 +52,7 @@ function apiTemplateRender(template, data) {
 function apiCommandExec(command) {
     return utilInvoke('commandExec', {command});
 }
+
+function apiAudioGetUrl(definition, source) {
+    return utilInvoke('audioGetUrl', {definition, source});
+}
diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js
index eb8fde94..0952887e 100644
--- a/ext/mixed/js/audio.js
+++ b/ext/mixed/js/audio.js
@@ -79,7 +79,35 @@ async function audioBuildUrl(definition, mode, cache={}) {
                 }
             }
         });
-    } else {
+    } else if (mode === 'jisho') {
+        return new Promise((resolve, reject) => {
+            const response = cache[definition.expression];
+            if (response) {
+                resolve(response);
+            } else {
+                const xhr = new XMLHttpRequest();
+                xhr.open('GET', `http://jisho.org/search/${definition.expression}`);
+                xhr.addEventListener('error', () => reject('failed to scrape audio data'));
+                xhr.addEventListener('load', () => {
+                    cache[definition.expression] = xhr.responseText;
+                    resolve(xhr.responseText);
+                });
+
+                xhr.send();
+            }
+        }).then(response => {
+            try {
+                const dom = new DOMParser().parseFromString(response, 'text/html');
+                const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`);
+                if (audio) {
+                    return audio.getElementsByTagName('source').item(0).getAttribute('src');
+                }
+            } catch (e) {
+                // NOP
+            }
+        });
+    }
+    else {
         return Promise.resolve();
     }
 }
diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js
index 21748f5d..12950dfd 100644
--- a/ext/mixed/js/display.js
+++ b/ext/mixed/js/display.js
@@ -27,7 +27,6 @@ class Display {
         this.sequence = 0;
         this.index = 0;
         this.audioCache = {};
-        this.responseCache = {};
 
         $(document).keydown(this.onKeyDown.bind(this));
     }
@@ -368,7 +367,7 @@ class Display {
         try {
             this.spinner.show();
 
-            let url = await audioBuildUrl(definition, this.options.general.audioSource, this.responseCache);
+            let url = await apiAudioGetUrl(definition, this.options.general.audioSource);
             if (!url) {
                 url = '/mixed/mp3/button.mp3';
             }
-- 
cgit v1.2.3


From 8ed3ca6fd44d24aff9ea41c65821c6e094024d4e Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Tue, 15 Aug 2017 21:40:41 -0700
Subject: cleanup

---
 ext/bg/background.html  |   4 +-
 ext/bg/js/audio.js      | 154 ++++++++++++++++++++++++++++++++++++++++++++++++
 ext/bg/js/request.js    |  40 +++++++++++++
 ext/bg/search.html      |   2 +-
 ext/bg/settings.html    |   2 +-
 ext/fg/float.html       |   1 -
 ext/mixed/js/audio.js   | 154 ------------------------------------------------
 ext/mixed/js/request.js |  40 -------------
 8 files changed, 198 insertions(+), 199 deletions(-)
 create mode 100644 ext/bg/js/audio.js
 create mode 100644 ext/bg/js/request.js
 delete mode 100644 ext/mixed/js/audio.js
 delete mode 100644 ext/mixed/js/request.js

(limited to 'ext/fg')

diff --git a/ext/bg/background.html b/ext/bg/background.html
index 90ad9709..97b20f46 100644
--- a/ext/bg/background.html
+++ b/ext/bg/background.html
@@ -11,17 +11,17 @@
 
         <script src="/bg/js/anki.js"></script>
         <script src="/bg/js/api.js"></script>
+        <script src="/bg/js/audio.js"></script>
         <script src="/bg/js/database.js"></script>
         <script src="/bg/js/deinflector.js"></script>
         <script src="/bg/js/dictionary.js"></script>
         <script src="/bg/js/handlebars.js"></script>
         <script src="/bg/js/options.js"></script>
+        <script src="/bg/js/request.js"></script>
         <script src="/bg/js/templates.js"></script>
         <script src="/bg/js/translator.js"></script>
         <script src="/bg/js/util.js"></script>
-        <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/japanese.js"></script>
-        <script src="/mixed/js/request.js"></script>
 
         <script src="/bg/js/backend.js"></script>
     </body>
diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js
new file mode 100644
index 00000000..0952887e
--- /dev/null
+++ b/ext/bg/js/audio.js
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017  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/>.
+ */
+
+
+async function audioBuildUrl(definition, mode, cache={}) {
+    if (mode === 'jpod101') {
+        let kana = definition.reading;
+        let kanji = definition.expression;
+
+        if (!kana && wanakana.isHiragana(kanji)) {
+            kana = kanji;
+            kanji = null;
+        }
+
+        const params = [];
+        if (kanji) {
+            params.push(`kanji=${encodeURIComponent(kanji)}`);
+        }
+        if (kana) {
+            params.push(`kana=${encodeURIComponent(kana)}`);
+        }
+
+        const url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`;
+        return Promise.resolve(url);
+    } else if (mode === 'jpod101-alternate') {
+        return new Promise((resolve, reject) => {
+            const response = cache[definition.expression];
+            if (response) {
+                resolve(response);
+            } else {
+                const data = {
+                    post: 'dictionary_reference',
+                    match_type: 'exact',
+                    search_query: definition.expression
+                };
+
+                const params = [];
+                for (const key in data) {
+                    params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
+                }
+
+                const xhr = new XMLHttpRequest();
+                xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
+                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+                xhr.addEventListener('error', () => reject('failed to scrape audio data'));
+                xhr.addEventListener('load', () => {
+                    cache[definition.expression] = xhr.responseText;
+                    resolve(xhr.responseText);
+                });
+
+                xhr.send(params.join('&'));
+            }
+        }).then(response => {
+            const dom = new DOMParser().parseFromString(response, 'text/html');
+            for (const row of dom.getElementsByClassName('dc-result-row')) {
+                try {
+                    const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url');
+                    const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText;
+                    if (url && reading && (!definition.reading || definition.reading === reading)) {
+                        return url;
+                    }
+                } catch (e) {
+                    // NOP
+                }
+            }
+        });
+    } else if (mode === 'jisho') {
+        return new Promise((resolve, reject) => {
+            const response = cache[definition.expression];
+            if (response) {
+                resolve(response);
+            } else {
+                const xhr = new XMLHttpRequest();
+                xhr.open('GET', `http://jisho.org/search/${definition.expression}`);
+                xhr.addEventListener('error', () => reject('failed to scrape audio data'));
+                xhr.addEventListener('load', () => {
+                    cache[definition.expression] = xhr.responseText;
+                    resolve(xhr.responseText);
+                });
+
+                xhr.send();
+            }
+        }).then(response => {
+            try {
+                const dom = new DOMParser().parseFromString(response, 'text/html');
+                const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`);
+                if (audio) {
+                    return audio.getElementsByTagName('source').item(0).getAttribute('src');
+                }
+            } catch (e) {
+                // NOP
+            }
+        });
+    }
+    else {
+        return Promise.resolve();
+    }
+}
+
+function audioBuildFilename(definition) {
+    if (definition.reading || definition.expression) {
+        let filename = 'yomichan';
+        if (definition.reading) {
+            filename += `_${definition.reading}`;
+        }
+        if (definition.expression) {
+            filename += `_${definition.expression}`;
+        }
+
+        return filename += '.mp3';
+    }
+}
+
+async function audioInject(definition, fields, mode) {
+    let usesAudio = false;
+    for (const name in fields) {
+        if (fields[name].includes('{audio}')) {
+            usesAudio = true;
+            break;
+        }
+    }
+
+    if (!usesAudio) {
+        return true;
+    }
+
+    try {
+        const url = await audioBuildUrl(definition, mode);
+        const filename = audioBuildFilename(definition);
+
+        if (url && filename) {
+            definition.audio = {url, filename};
+        }
+
+        return true;
+    } catch (e) {
+        return false;
+    }
+}
diff --git a/ext/bg/js/request.js b/ext/bg/js/request.js
new file mode 100644
index 00000000..94fd135a
--- /dev/null
+++ b/ext/bg/js/request.js
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017  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 requestJson(url, action, params) {
+    return new Promise((resolve, reject) => {
+        const xhr = new XMLHttpRequest();
+        xhr.overrideMimeType('application/json');
+        xhr.addEventListener('load', () => resolve(xhr.responseText));
+        xhr.addEventListener('error', () => reject('failed to execute network request'));
+        xhr.open(action, url);
+        if (params) {
+            xhr.send(JSON.stringify(params));
+        } else {
+            xhr.send();
+        }
+    }).then(responseText => {
+        try {
+            return JSON.parse(responseText);
+        }
+        catch (e) {
+            return Promise.reject('invalid JSON response');
+        }
+    });
+}
diff --git a/ext/bg/search.html b/ext/bg/search.html
index d10530f9..5fbd467a 100644
--- a/ext/bg/search.html
+++ b/ext/bg/search.html
@@ -37,11 +37,11 @@
         <script src="/mixed/lib/wanakana.min.js"></script>
 
         <script src="/bg/js/api.js"></script>
+        <script src="/bg/js/audio.js"></script>
         <script src="/bg/js/dictionary.js"></script>
         <script src="/bg/js/handlebars.js"></script>
         <script src="/bg/js/templates.js"></script>
         <script src="/bg/js/util.js"></script>
-        <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
         <script src="/mixed/js/japanese.js"></script>
 
diff --git a/ext/bg/settings.html b/ext/bg/settings.html
index ba155d4a..8798aeb1 100644
--- a/ext/bg/settings.html
+++ b/ext/bg/settings.html
@@ -48,7 +48,7 @@
                         <option value="disabled">Disabled</option>
                         <option value="jpod101">JapanesePod101</option>
                         <option value="jpod101-alternate">JapanesePod101 (alternate)</option>
-                        <option value="jisho">Jisho</option>
+                        <option value="jisho">Jisho.org</option>
                     </select>
                 </div>
 
diff --git a/ext/fg/float.html b/ext/fg/float.html
index a3b66c92..89872cce 100644
--- a/ext/fg/float.html
+++ b/ext/fg/float.html
@@ -35,7 +35,6 @@
 
         <script src="/fg/js/api.js"></script>
         <script src="/fg/js/util.js"></script>
-        <script src="/mixed/js/audio.js"></script>
         <script src="/mixed/js/display.js"></script>
 
         <script src="/fg/js/float.js"></script>
diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js
deleted file mode 100644
index 0952887e..00000000
--- a/ext/mixed/js/audio.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2017  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/>.
- */
-
-
-async function audioBuildUrl(definition, mode, cache={}) {
-    if (mode === 'jpod101') {
-        let kana = definition.reading;
-        let kanji = definition.expression;
-
-        if (!kana && wanakana.isHiragana(kanji)) {
-            kana = kanji;
-            kanji = null;
-        }
-
-        const params = [];
-        if (kanji) {
-            params.push(`kanji=${encodeURIComponent(kanji)}`);
-        }
-        if (kana) {
-            params.push(`kana=${encodeURIComponent(kana)}`);
-        }
-
-        const url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`;
-        return Promise.resolve(url);
-    } else if (mode === 'jpod101-alternate') {
-        return new Promise((resolve, reject) => {
-            const response = cache[definition.expression];
-            if (response) {
-                resolve(response);
-            } else {
-                const data = {
-                    post: 'dictionary_reference',
-                    match_type: 'exact',
-                    search_query: definition.expression
-                };
-
-                const params = [];
-                for (const key in data) {
-                    params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
-                }
-
-                const xhr = new XMLHttpRequest();
-                xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
-                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
-                xhr.addEventListener('error', () => reject('failed to scrape audio data'));
-                xhr.addEventListener('load', () => {
-                    cache[definition.expression] = xhr.responseText;
-                    resolve(xhr.responseText);
-                });
-
-                xhr.send(params.join('&'));
-            }
-        }).then(response => {
-            const dom = new DOMParser().parseFromString(response, 'text/html');
-            for (const row of dom.getElementsByClassName('dc-result-row')) {
-                try {
-                    const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url');
-                    const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText;
-                    if (url && reading && (!definition.reading || definition.reading === reading)) {
-                        return url;
-                    }
-                } catch (e) {
-                    // NOP
-                }
-            }
-        });
-    } else if (mode === 'jisho') {
-        return new Promise((resolve, reject) => {
-            const response = cache[definition.expression];
-            if (response) {
-                resolve(response);
-            } else {
-                const xhr = new XMLHttpRequest();
-                xhr.open('GET', `http://jisho.org/search/${definition.expression}`);
-                xhr.addEventListener('error', () => reject('failed to scrape audio data'));
-                xhr.addEventListener('load', () => {
-                    cache[definition.expression] = xhr.responseText;
-                    resolve(xhr.responseText);
-                });
-
-                xhr.send();
-            }
-        }).then(response => {
-            try {
-                const dom = new DOMParser().parseFromString(response, 'text/html');
-                const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`);
-                if (audio) {
-                    return audio.getElementsByTagName('source').item(0).getAttribute('src');
-                }
-            } catch (e) {
-                // NOP
-            }
-        });
-    }
-    else {
-        return Promise.resolve();
-    }
-}
-
-function audioBuildFilename(definition) {
-    if (definition.reading || definition.expression) {
-        let filename = 'yomichan';
-        if (definition.reading) {
-            filename += `_${definition.reading}`;
-        }
-        if (definition.expression) {
-            filename += `_${definition.expression}`;
-        }
-
-        return filename += '.mp3';
-    }
-}
-
-async function audioInject(definition, fields, mode) {
-    let usesAudio = false;
-    for (const name in fields) {
-        if (fields[name].includes('{audio}')) {
-            usesAudio = true;
-            break;
-        }
-    }
-
-    if (!usesAudio) {
-        return true;
-    }
-
-    try {
-        const url = await audioBuildUrl(definition, mode);
-        const filename = audioBuildFilename(definition);
-
-        if (url && filename) {
-            definition.audio = {url, filename};
-        }
-
-        return true;
-    } catch (e) {
-        return false;
-    }
-}
diff --git a/ext/mixed/js/request.js b/ext/mixed/js/request.js
deleted file mode 100644
index 94fd135a..00000000
--- a/ext/mixed/js/request.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017  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 requestJson(url, action, params) {
-    return new Promise((resolve, reject) => {
-        const xhr = new XMLHttpRequest();
-        xhr.overrideMimeType('application/json');
-        xhr.addEventListener('load', () => resolve(xhr.responseText));
-        xhr.addEventListener('error', () => reject('failed to execute network request'));
-        xhr.open(action, url);
-        if (params) {
-            xhr.send(JSON.stringify(params));
-        } else {
-            xhr.send();
-        }
-    }).then(responseText => {
-        try {
-            return JSON.parse(responseText);
-        }
-        catch (e) {
-            return Promise.reject('invalid JSON response');
-        }
-    });
-}
-- 
cgit v1.2.3


From 0c650dac828b7ab9641396268a66d3a7410d4000 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Thu, 17 Aug 2017 19:05:31 -0700
Subject: don't show busy spinner while waiting for card info smoother cursor
 movement in firefox

---
 ext/fg/js/frontend.js   | 20 +++++++++++++++-----
 ext/mixed/js/display.js |  4 ----
 2 files changed, 15 insertions(+), 9 deletions(-)

(limited to 'ext/fg')

diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 58a721bf..41c93f00 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -21,7 +21,6 @@ class Frontend {
     constructor() {
         this.popup = new Popup();
         this.popupTimer = null;
-        this.mousePosLast = null;
         this.mouseDownLeft = false;
         this.mouseDownMiddle = false;
         this.textSourceLast = null;
@@ -53,7 +52,6 @@ class Frontend {
     }
 
     onMouseMove(e) {
-        this.mousePosLast = {x: e.clientX, y: e.clientY};
         this.popupTimerClear();
 
         if (!this.options.general.enable) {
@@ -64,6 +62,10 @@ class Frontend {
             return;
         }
 
+        if (this.pendingLookup) {
+            return;
+        }
+
         const mouseScan = this.mouseDownMiddle && this.options.scanning.middleMouse;
         const keyScan =
             this.options.scanning.modifier === 'alt' && e.altKey ||
@@ -75,11 +77,19 @@ class Frontend {
             return;
         }
 
-        const searchFunc = () => this.searchAt(this.mousePosLast);
+        const search = async () => {
+            try {
+                await this.searchAt({x: e.clientX, y: e.clientY});
+                this.pendingLookup = false;
+            } catch (e) {
+                this.onError(e);
+            }
+        };
+
         if (this.options.scanning.modifier === 'none') {
-            this.popupTimerSet(searchFunc);
+            this.popupTimerSet(search);
         } else {
-            searchFunc();
+            search();
         }
     }
 
diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js
index 12950dfd..47efd195 100644
--- a/ext/mixed/js/display.js
+++ b/ext/mixed/js/display.js
@@ -286,8 +286,6 @@ class Display {
 
     async adderButtonUpdate(modes, sequence) {
         try {
-            this.spinner.show();
-
             const states = await apiDefinitionsAddable(this.definitions, modes);
             if (!states || sequence !== this.sequence) {
                 return;
@@ -308,8 +306,6 @@ class Display {
             }
         } catch (e) {
             this.onError(e);
-        } finally {
-            this.spinner.hide();
         }
     }
 
-- 
cgit v1.2.3


From 191336522c220b0a3cfe41515ed23946b3462217 Mon Sep 17 00:00:00 2001
From: Alex Yatskov <alex@foosoft.net>
Date: Thu, 17 Aug 2017 19:14:06 -0700
Subject: fix flicker on form elements on mouseover (fixes #56)

---
 ext/fg/js/document.js | 1 +
 1 file changed, 1 insertion(+)

(limited to 'ext/fg')

diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js
index 17cca613..26c85b40 100644
--- a/ext/fg/js/document.js
+++ b/ext/fg/js/document.js
@@ -46,6 +46,7 @@ function docImposterCreate(element) {
     imposter.style.position = 'absolute';
     imposter.style.top = `${offset.top}px`;
     imposter.style.left = `${offset.left}px`;
+    imposter.style.opacity = 0;
     imposter.style.zIndex = 2147483646;
     if (element.nodeName === 'TEXTAREA' && styleProps.overflow === 'visible') {
         imposter.style.overflow = 'auto';
-- 
cgit v1.2.3