From 37ffcbf3ee2d9722751e899bdf4bac91ca7c92c1 Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Mon, 4 Apr 2016 22:02:26 -0700 Subject: File reorg --- ext/api.js | 38 ---------- ext/bg/background.html | 10 +-- ext/bg/deinflector.js | 126 -------------------------------- ext/bg/dictionary.js | 67 ----------------- ext/bg/js/deinflector.js | 126 ++++++++++++++++++++++++++++++++ ext/bg/js/dictionary.js | 67 +++++++++++++++++ ext/bg/js/options-form.js | 42 +++++++++++ ext/bg/js/options.js | 38 ++++++++++ ext/bg/js/templates.js | 35 +++++++++ ext/bg/js/translator.js | 178 ++++++++++++++++++++++++++++++++++++++++++++++ ext/bg/js/yomichan.js | 83 +++++++++++++++++++++ ext/bg/options-form.js | 42 ----------- ext/bg/options.html | 4 +- ext/bg/options.js | 38 ---------- ext/bg/templates.js | 35 --------- ext/bg/translator.js | 178 ---------------------------------------------- ext/bg/yomichan.js | 83 --------------------- ext/client.css | 51 ------------- ext/client.js | 122 ------------------------------- ext/css/client.css | 51 +++++++++++++ ext/js/api.js | 38 ++++++++++ ext/js/client.js | 122 +++++++++++++++++++++++++++++++ ext/js/util.js | 70 ++++++++++++++++++ ext/manifest.json | 4 +- ext/util.js | 70 ------------------ 25 files changed, 859 insertions(+), 859 deletions(-) delete mode 100644 ext/api.js delete mode 100644 ext/bg/deinflector.js delete mode 100644 ext/bg/dictionary.js create mode 100644 ext/bg/js/deinflector.js create mode 100644 ext/bg/js/dictionary.js create mode 100644 ext/bg/js/options-form.js create mode 100644 ext/bg/js/options.js create mode 100644 ext/bg/js/templates.js create mode 100644 ext/bg/js/translator.js create mode 100644 ext/bg/js/yomichan.js delete mode 100644 ext/bg/options-form.js delete mode 100644 ext/bg/options.js delete mode 100644 ext/bg/templates.js delete mode 100644 ext/bg/translator.js delete mode 100644 ext/bg/yomichan.js delete mode 100644 ext/client.css delete mode 100644 ext/client.js create mode 100644 ext/css/client.css create mode 100644 ext/js/api.js create mode 100644 ext/js/client.js create mode 100644 ext/js/util.js delete mode 100644 ext/util.js (limited to 'ext') diff --git a/ext/api.js b/ext/api.js deleted file mode 100644 index 7f552a1e..00000000 --- a/ext/api.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -function sendMessage(action, data, callback) { - chrome.runtime.sendMessage({action: action, data: data}, callback); -} - -function findTerm(text, callback) { - sendMessage('findTerm', {text: text}, callback); -} - -function findKanji(text, callback) { - sendMessage('findKanji', {text: text}, callback); -} - -function renderTemplate(data, template, callback) { - sendMessage('renderTemplate', {data: data, template: template}, callback); -} - -function getState(callback) { - sendMessage('getState', null, callback); -} diff --git a/ext/bg/background.html b/ext/bg/background.html index 0a0018e3..6e917561 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -3,10 +3,10 @@ - - - - - + + + + + diff --git a/ext/bg/deinflector.js b/ext/bg/deinflector.js deleted file mode 100644 index 03f9d40a..00000000 --- a/ext/bg/deinflector.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -class Deinflection { - constructor(term, tags=[], rule='') { - this.children = []; - this.term = term; - this.tags = tags; - this.rule = rule; - } - - validate(validator) { - for (const tags of validator(this.term)) { - if (this.tags.length === 0) { - return true; - } - - for (const tag of this.tags) { - if (this.searchTags(tag, tags)) { - return true; - } - } - } - - return false; - } - - deinflect(validator, rules) { - if (this.validate(validator)) { - const child = new Deinflection(this.term); - this.children.push(child); - } - - for (const rule in rules) { - const variants = rules[rule]; - for (const v of variants) { - let allowed = this.tags.length === 0; - for (const tag of this.tags) { - if (this.searchTags(tag, v.tagsIn)) { - allowed = true; - break; - } - } - - if (!allowed || !this.term.endsWith(v.kanaIn)) { - continue; - } - - const term = this.term.slice(0, -v.kanaIn.length) + v.kanaOut; - const child = new Deinflection(term, v.tagsOut, rule); - if (child.deinflect(validator, rules)) { - this.children.push(child); - } - } - } - - return this.children.length > 0; - } - - searchTags(tag, tags) { - for (const t of tags) { - const re = new RegExp(tag); - if (re.test(t)) { - return true; - } - } - - return false; - } - - gather() { - if (this.children.length === 0) { - return [{root: this.term, rules: []}]; - } - - const paths = []; - for (const child of this.children) { - for (const path of child.gather()) { - if (this.rule.length > 0) { - path.rules.push(this.rule); - } - - path.source = this.term; - paths.push(path); - } - } - - return paths; - } -} - - -class Deinflector { - constructor() { - this.rules = {}; - } - - setRules(rules) { - this.rules = rules; - } - - deinflect(term, validator) { - const node = new Deinflection(term); - if (node.deinflect(validator, this.rules)) { - return node.gather(); - } - - return null; - } -} diff --git a/ext/bg/dictionary.js b/ext/bg/dictionary.js deleted file mode 100644 index a68c2daf..00000000 --- a/ext/bg/dictionary.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -class Dictionary { - constructor() { - this.terms = []; - this.termIndices = {}; - - this.kanji = []; - this.kanjiIndices = {}; - } - - addTermData(terms) { - let index = this.terms.length; - for (const [e, r, g, t] of terms) { - this.storeIndex(this.termIndices, e, index); - this.storeIndex(this.termIndices, r, index++); - this.terms.push([e, r, g, t]); - } - } - - addKanjiData(kanji) { - let index = this.kanji.length; - for (const [c, k, o, g] of kanji) { - this.storeIndex(this.kanjiIndices, c, index++); - this.kanji.push([c, k, o, g]); - } - } - - findTerm(term) { - return (this.termIndices[term] || []).map(index => { - const [e, r, g, t] = this.terms[index]; - return {id: index, expression: e, reading: r, glossary: g, tags: t.split(' ')}; - }); - } - - findKanji(kanji) { - return (this.kanjiIndices[kanji] || []).map(index => { - const [c, k, o, g] = def; - return {id: index, character: c, kunyomi: k, onyomi: o, glossary: g}; - }); - } - - storeIndex(indices, term, index) { - if (term.length > 0) { - const indices = this.termIndices[term] || []; - indices.push(index); - this.termIndices[term] = indices; - } - } -} diff --git a/ext/bg/js/deinflector.js b/ext/bg/js/deinflector.js new file mode 100644 index 00000000..03f9d40a --- /dev/null +++ b/ext/bg/js/deinflector.js @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +class Deinflection { + constructor(term, tags=[], rule='') { + this.children = []; + this.term = term; + this.tags = tags; + this.rule = rule; + } + + validate(validator) { + for (const tags of validator(this.term)) { + if (this.tags.length === 0) { + return true; + } + + for (const tag of this.tags) { + if (this.searchTags(tag, tags)) { + return true; + } + } + } + + return false; + } + + deinflect(validator, rules) { + if (this.validate(validator)) { + const child = new Deinflection(this.term); + this.children.push(child); + } + + for (const rule in rules) { + const variants = rules[rule]; + for (const v of variants) { + let allowed = this.tags.length === 0; + for (const tag of this.tags) { + if (this.searchTags(tag, v.tagsIn)) { + allowed = true; + break; + } + } + + if (!allowed || !this.term.endsWith(v.kanaIn)) { + continue; + } + + const term = this.term.slice(0, -v.kanaIn.length) + v.kanaOut; + const child = new Deinflection(term, v.tagsOut, rule); + if (child.deinflect(validator, rules)) { + this.children.push(child); + } + } + } + + return this.children.length > 0; + } + + searchTags(tag, tags) { + for (const t of tags) { + const re = new RegExp(tag); + if (re.test(t)) { + return true; + } + } + + return false; + } + + gather() { + if (this.children.length === 0) { + return [{root: this.term, rules: []}]; + } + + const paths = []; + for (const child of this.children) { + for (const path of child.gather()) { + if (this.rule.length > 0) { + path.rules.push(this.rule); + } + + path.source = this.term; + paths.push(path); + } + } + + return paths; + } +} + + +class Deinflector { + constructor() { + this.rules = {}; + } + + setRules(rules) { + this.rules = rules; + } + + deinflect(term, validator) { + const node = new Deinflection(term); + if (node.deinflect(validator, this.rules)) { + return node.gather(); + } + + return null; + } +} diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js new file mode 100644 index 00000000..a68c2daf --- /dev/null +++ b/ext/bg/js/dictionary.js @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +class Dictionary { + constructor() { + this.terms = []; + this.termIndices = {}; + + this.kanji = []; + this.kanjiIndices = {}; + } + + addTermData(terms) { + let index = this.terms.length; + for (const [e, r, g, t] of terms) { + this.storeIndex(this.termIndices, e, index); + this.storeIndex(this.termIndices, r, index++); + this.terms.push([e, r, g, t]); + } + } + + addKanjiData(kanji) { + let index = this.kanji.length; + for (const [c, k, o, g] of kanji) { + this.storeIndex(this.kanjiIndices, c, index++); + this.kanji.push([c, k, o, g]); + } + } + + findTerm(term) { + return (this.termIndices[term] || []).map(index => { + const [e, r, g, t] = this.terms[index]; + return {id: index, expression: e, reading: r, glossary: g, tags: t.split(' ')}; + }); + } + + findKanji(kanji) { + return (this.kanjiIndices[kanji] || []).map(index => { + const [c, k, o, g] = def; + return {id: index, character: c, kunyomi: k, onyomi: o, glossary: g}; + }); + } + + storeIndex(indices, term, index) { + if (term.length > 0) { + const indices = this.termIndices[term] || []; + indices.push(index); + this.termIndices[term] = indices; + } + } +} diff --git a/ext/bg/js/options-form.js b/ext/bg/js/options-form.js new file mode 100644 index 00000000..cde0ea62 --- /dev/null +++ b/ext/bg/js/options-form.js @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +$('#saveOptions').click(() => { + saveOptions(sanitizeOptions(formToOptions())) +}); + +$('#resetOptions').click(() => { + if (confirm('Reset options to defaults?')) { + optionsToForm(sanitizeOptions({})); + } +}); + +function optionsToForm(opts) { + $('#scanLength').val(opts.scanLength); +} + +function formToOptions() { + return { + scanLength: $('#scanLength').val() + }; +} + +$(document).ready(() => { + loadOptions((opts) => optionsToForm(sanitizeOptions(opts))); +}); diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js new file mode 100644 index 00000000..1db3be5e --- /dev/null +++ b/ext/bg/js/options.js @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +function sanitizeOptions(options) { + const defaults = { + scanLength: 20 + }; + + for (const key in defaults) { + options[key] = options[key] || defaults[key]; + } + + return options; +} + +function loadOptions(callback) { + chrome.storage.sync.get(null, (items) => callback(sanitizeOptions(items))); +} + +function saveOptions(opts, callback) { + chrome.storage.sync.set(sanitizeOptions(opts), callback); +} diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js new file mode 100644 index 00000000..8ac5f8e5 --- /dev/null +++ b/ext/bg/js/templates.js @@ -0,0 +1,35 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['defs.html'] = template({"1":function(container,depth0,helpers,partials,data) { + var stack1; + + return "
\n" + + ((stack1 = container.invokePartial(partials["term.html"],depth0,{"name":"term.html","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") + + " " + + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.last),{"name":"unless","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\n
\n"; +},"2":function(container,depth0,helpers,partials,data) { + return "
"; +},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1; + + return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.defs : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); +},"usePartial":true,"useData":true}); +templates['term.html'] = template({"1":function(container,depth0,helpers,partials,data) { + var helper; + + return "
" + + container.escapeExpression(((helper = (helper = helpers.reading || (depth0 != null ? depth0.reading : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"reading","hash":{},"data":data}) : helper))) + + "
"; +},"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; + + return "
" + + alias4(((helper = (helper = helpers.expression || (depth0 != null ? depth0.expression : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expression","hash":{},"data":data}) : helper))) + + "
\n" + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reading : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\n
" + + alias4(((helper = (helper = helpers.glossary || (depth0 != null ? depth0.glossary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"glossary","hash":{},"data":data}) : helper))) + + "
\n"; +},"useData":true}); +})(); \ No newline at end of file diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js new file mode 100644 index 00000000..e8224320 --- /dev/null +++ b/ext/bg/js/translator.js @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +class Translator { + constructor() { + this.loaded = false; + this.paths = { + rules: 'bg/data/rules.json', + edict: 'bg/data/edict.csv', + enamdict: 'bg/data/enamdict.csv', + kanjidic: 'bg/data/kanjidic.csv' + }; + + this.dictionary = new Dictionary(); + this.deinflector = new Deinflector(); + } + + loadData(callback) { + if (this.loaded) { + callback(); + return; + } + + const pendingLoads = []; + for (const key of ['rules', 'edict', 'enamdict', 'kanjidic']) { + pendingLoads.push(key); + Translator.loadData(this.paths[key], (response) => { + switch (key) { + case 'rules': + this.deinflector.setRules(JSON.parse(response)); + break; + case 'kanjidic': + this.dictionary.addKanjiData(Translator.parseCsv(response)); + break; + case 'edict': + case 'enamdict': + this.dictionary.addTermData(Translator.parseCsv(response)); + break; + } + + pendingLoads.splice(pendingLoads.indexOf(key), 1); + if (pendingLoads.length === 0) { + this.loaded = true; + callback(); + } + }); + } + } + + findTerm(text) { + const groups = {}; + for (let i = text.length; i > 0; --i) { + const term = text.slice(0, i); + + const dfs = this.deinflector.deinflect(term, t => { + const tags = []; + for (const d of this.dictionary.findTerm(t)) { + tags.push(d.tags); + } + + return tags; + }); + + if (dfs === null) { + this.processTerm(groups, term); + } else { + for (const df of dfs) { + this.processTerm(groups, df.source, df.rules, df.root); + } + } + } + + let results = []; + for (const key in groups) { + results.push(groups[key]); + } + + results = results.sort((v1, v2) => { + const sl1 = v1.source.length; + const sl2 = v2.source.length; + if (sl1 > sl2) { + return -1; + } else if (sl1 < sl2) { + return 1; + } + + const p1 = v1.tags.indexOf('P') >= 0; + const p2 = v2.tags.indexOf('P') >= 0; + if (p1 && !p2) { + return -1; + } else if (!p1 && p2) { + return 1; + } + + const rl1 = v1.rules.length; + const rl2 = v2.rules.length; + if (rl1 < rl2) { + return -1; + } else if (rl2 > rl1) { + return 1; + } + + return 0; + }); + + let length = 0; + for (const result of results) { + length = Math.max(length, result.source.length); + } + + return {results: results, length: length}; + } + + findKanji(text) { + let results = []; + + const processed = {}; + for (const c of text) { + if (!processed.has(c)) { + results = results.concat(this.dictionary.findKanji(c)); + processed[c] = true; + } + } + + return results; + } + + processTerm(groups, source, rules=[], root='') { + for (const entry of this.dictionary.findTerm(root || source)) { + if (entry.id in groups) { + continue; + } + + groups[entry.id] = { + expression: entry.expression, + reading: entry.reading, + glossary: entry.glossary, + tags: entry.tags, + source: source, + rules: rules + }; + } + } + + static loadData(url, callback) { + const xhr = new XMLHttpRequest(); + xhr.addEventListener('load', () => callback(xhr.responseText)); + xhr.open('GET', chrome.extension.getURL(url), true); + xhr.send(); + } + + static parseCsv(data) { + const result = []; + for (const row of data.split('\n')) { + if (row.length > 0) { + result.push(row.split('\t')); + } + } + + return result; + } +} diff --git a/ext/bg/js/yomichan.js b/ext/bg/js/yomichan.js new file mode 100644 index 00000000..2e4552a1 --- /dev/null +++ b/ext/bg/js/yomichan.js @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +class Yomichan { + constructor() { + Handlebars.partials = Handlebars.templates; + + this.translator = new Translator(); + this.updateState('disabled'); + + chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); + chrome.browserAction.onClicked.addListener(this.onBrowserAction.bind(this)); + } + + onMessage(request, sender, callback) { + const {action, data} = request; + const handler = { + findKanji: ({text}) => this.translator.onFindKanji(text), + findTerm: ({text}) => this.translator.findTerm(text), + getState: () => this.state, + renderTemplate: ({data, template}) => Handlebars.templates[template](data) + }[action]; + + if (handler !== null) { + const result = handler.call(this, data); + if (callback !== null) { + callback(result); + } + } + } + + onBrowserAction(tab) { + switch (this.state) { + case 'disabled': + this.updateState('loading'); + break; + case 'enabled': + this.updateState('disabled'); + break; + } + } + + updateState(state) { + this.state = state; + + switch (state) { + case 'disabled': + chrome.browserAction.setBadgeText({text: ''}); + break; + case 'enabled': + chrome.browserAction.setBadgeText({text: 'on'}); + break; + case 'loading': + chrome.browserAction.setBadgeText({text: '...'}); + this.translator.loadData(() => this.updateState('enabled')); + break; + } + + chrome.tabs.query({}, (tabs) => { + for (const tab of tabs) { + chrome.tabs.sendMessage(tab.id, this.state, () => null); + } + }); + } +} + +window.yomichan = new Yomichan(); diff --git a/ext/bg/options-form.js b/ext/bg/options-form.js deleted file mode 100644 index cde0ea62..00000000 --- a/ext/bg/options-form.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -$('#saveOptions').click(() => { - saveOptions(sanitizeOptions(formToOptions())) -}); - -$('#resetOptions').click(() => { - if (confirm('Reset options to defaults?')) { - optionsToForm(sanitizeOptions({})); - } -}); - -function optionsToForm(opts) { - $('#scanLength').val(opts.scanLength); -} - -function formToOptions() { - return { - scanLength: $('#scanLength').val() - }; -} - -$(document).ready(() => { - loadOptions((opts) => optionsToForm(sanitizeOptions(opts))); -}); diff --git a/ext/bg/options.html b/ext/bg/options.html index 7854fd86..f73162ec 100644 --- a/ext/bg/options.html +++ b/ext/bg/options.html @@ -26,8 +26,8 @@ - - + + diff --git a/ext/bg/options.js b/ext/bg/options.js deleted file mode 100644 index 1db3be5e..00000000 --- a/ext/bg/options.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -function sanitizeOptions(options) { - const defaults = { - scanLength: 20 - }; - - for (const key in defaults) { - options[key] = options[key] || defaults[key]; - } - - return options; -} - -function loadOptions(callback) { - chrome.storage.sync.get(null, (items) => callback(sanitizeOptions(items))); -} - -function saveOptions(opts, callback) { - chrome.storage.sync.set(sanitizeOptions(opts), callback); -} diff --git a/ext/bg/templates.js b/ext/bg/templates.js deleted file mode 100644 index 8ac5f8e5..00000000 --- a/ext/bg/templates.js +++ /dev/null @@ -1,35 +0,0 @@ -(function() { - var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; -templates['defs.html'] = template({"1":function(container,depth0,helpers,partials,data) { - var stack1; - - return "
\n" - + ((stack1 = container.invokePartial(partials["term.html"],depth0,{"name":"term.html","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") - + " " - + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.last),{"name":"unless","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "\n
\n"; -},"2":function(container,depth0,helpers,partials,data) { - return "
"; -},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.defs : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); -},"usePartial":true,"useData":true}); -templates['term.html'] = template({"1":function(container,depth0,helpers,partials,data) { - var helper; - - return "
" - + container.escapeExpression(((helper = (helper = helpers.reading || (depth0 != null ? depth0.reading : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"reading","hash":{},"data":data}) : helper))) - + "
"; -},"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; - - return "
" - + alias4(((helper = (helper = helpers.expression || (depth0 != null ? depth0.expression : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expression","hash":{},"data":data}) : helper))) - + "
\n" - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reading : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "\n
" - + alias4(((helper = (helper = helpers.glossary || (depth0 != null ? depth0.glossary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"glossary","hash":{},"data":data}) : helper))) - + "
\n"; -},"useData":true}); -})(); \ No newline at end of file diff --git a/ext/bg/translator.js b/ext/bg/translator.js deleted file mode 100644 index e8224320..00000000 --- a/ext/bg/translator.js +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -class Translator { - constructor() { - this.loaded = false; - this.paths = { - rules: 'bg/data/rules.json', - edict: 'bg/data/edict.csv', - enamdict: 'bg/data/enamdict.csv', - kanjidic: 'bg/data/kanjidic.csv' - }; - - this.dictionary = new Dictionary(); - this.deinflector = new Deinflector(); - } - - loadData(callback) { - if (this.loaded) { - callback(); - return; - } - - const pendingLoads = []; - for (const key of ['rules', 'edict', 'enamdict', 'kanjidic']) { - pendingLoads.push(key); - Translator.loadData(this.paths[key], (response) => { - switch (key) { - case 'rules': - this.deinflector.setRules(JSON.parse(response)); - break; - case 'kanjidic': - this.dictionary.addKanjiData(Translator.parseCsv(response)); - break; - case 'edict': - case 'enamdict': - this.dictionary.addTermData(Translator.parseCsv(response)); - break; - } - - pendingLoads.splice(pendingLoads.indexOf(key), 1); - if (pendingLoads.length === 0) { - this.loaded = true; - callback(); - } - }); - } - } - - findTerm(text) { - const groups = {}; - for (let i = text.length; i > 0; --i) { - const term = text.slice(0, i); - - const dfs = this.deinflector.deinflect(term, t => { - const tags = []; - for (const d of this.dictionary.findTerm(t)) { - tags.push(d.tags); - } - - return tags; - }); - - if (dfs === null) { - this.processTerm(groups, term); - } else { - for (const df of dfs) { - this.processTerm(groups, df.source, df.rules, df.root); - } - } - } - - let results = []; - for (const key in groups) { - results.push(groups[key]); - } - - results = results.sort((v1, v2) => { - const sl1 = v1.source.length; - const sl2 = v2.source.length; - if (sl1 > sl2) { - return -1; - } else if (sl1 < sl2) { - return 1; - } - - const p1 = v1.tags.indexOf('P') >= 0; - const p2 = v2.tags.indexOf('P') >= 0; - if (p1 && !p2) { - return -1; - } else if (!p1 && p2) { - return 1; - } - - const rl1 = v1.rules.length; - const rl2 = v2.rules.length; - if (rl1 < rl2) { - return -1; - } else if (rl2 > rl1) { - return 1; - } - - return 0; - }); - - let length = 0; - for (const result of results) { - length = Math.max(length, result.source.length); - } - - return {results: results, length: length}; - } - - findKanji(text) { - let results = []; - - const processed = {}; - for (const c of text) { - if (!processed.has(c)) { - results = results.concat(this.dictionary.findKanji(c)); - processed[c] = true; - } - } - - return results; - } - - processTerm(groups, source, rules=[], root='') { - for (const entry of this.dictionary.findTerm(root || source)) { - if (entry.id in groups) { - continue; - } - - groups[entry.id] = { - expression: entry.expression, - reading: entry.reading, - glossary: entry.glossary, - tags: entry.tags, - source: source, - rules: rules - }; - } - } - - static loadData(url, callback) { - const xhr = new XMLHttpRequest(); - xhr.addEventListener('load', () => callback(xhr.responseText)); - xhr.open('GET', chrome.extension.getURL(url), true); - xhr.send(); - } - - static parseCsv(data) { - const result = []; - for (const row of data.split('\n')) { - if (row.length > 0) { - result.push(row.split('\t')); - } - } - - return result; - } -} diff --git a/ext/bg/yomichan.js b/ext/bg/yomichan.js deleted file mode 100644 index 2e4552a1..00000000 --- a/ext/bg/yomichan.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -class Yomichan { - constructor() { - Handlebars.partials = Handlebars.templates; - - this.translator = new Translator(); - this.updateState('disabled'); - - chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); - chrome.browserAction.onClicked.addListener(this.onBrowserAction.bind(this)); - } - - onMessage(request, sender, callback) { - const {action, data} = request; - const handler = { - findKanji: ({text}) => this.translator.onFindKanji(text), - findTerm: ({text}) => this.translator.findTerm(text), - getState: () => this.state, - renderTemplate: ({data, template}) => Handlebars.templates[template](data) - }[action]; - - if (handler !== null) { - const result = handler.call(this, data); - if (callback !== null) { - callback(result); - } - } - } - - onBrowserAction(tab) { - switch (this.state) { - case 'disabled': - this.updateState('loading'); - break; - case 'enabled': - this.updateState('disabled'); - break; - } - } - - updateState(state) { - this.state = state; - - switch (state) { - case 'disabled': - chrome.browserAction.setBadgeText({text: ''}); - break; - case 'enabled': - chrome.browserAction.setBadgeText({text: 'on'}); - break; - case 'loading': - chrome.browserAction.setBadgeText({text: '...'}); - this.translator.loadData(() => this.updateState('enabled')); - break; - } - - chrome.tabs.query({}, (tabs) => { - for (const tab of tabs) { - chrome.tabs.sendMessage(tab.id, this.state, () => null); - } - }); - } -} - -window.yomichan = new Yomichan(); diff --git a/ext/client.css b/ext/client.css deleted file mode 100644 index ce1ae9a9..00000000 --- a/ext/client.css +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - -.yomichan-popup { - all: initial; - background-color: #fff; - border: 1px solid #999; - box-shadow: 0 0 10px rgba(0, 0, 0, .5); - color: #000; - font-family: sans-serif; - font-size: 12pt; - max-height: 300px; - max-width: 500px; - min-width: 200px; - overflow-y: auto; - padding: 10px; - position: fixed; - visibility: hidden; - z-index: 256; -} - -.yomichan-def-expression { - float: left; - font-family: serif; - font-size: 16pt; -} - -.yomichan-def-reading { - float: right; - font-family: serif; - font-size: 16pt; -} - -.yomichan-def-glossary { - clear: both; -} diff --git a/ext/client.js b/ext/client.js deleted file mode 100644 index 2d9a470f..00000000 --- a/ext/client.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -class Client { - constructor() { - this.popupOffset = 10; - this.lastMosePos = null; - this.enabled = false; - - this.popup = document.createElement('div'); - this.popup.classList.add('yomichan-popup'); - this.popup.addEventListener('mousedown', (e) => e.stopPropagation()); - this.popup.addEventListener('scroll', (e) => e.stopPropagation()); - document.body.appendChild(this.popup); - - chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); - window.addEventListener('mousedown', this.onMouseDown.bind(this)); - window.addEventListener('mousemove', this.onMouseMove.bind(this)); - window.addEventListener('keydown', this.onKeyDown.bind(this)); - window.addEventListener('scroll', (e) => this.hidePopup()); - window.addEventListener('resize', (e) => this.hidePopup()); - - getState((state) => this.setEnabled(state === 'enabled')); - } - - onKeyDown(e) { - if (this.enabled && this.lastMousePos !== null && (e.keyCode === 16 || e.charCode === 16)) { - this.searchAtPoint(this.lastMousePos); - } - } - - onMouseMove(e) { - this.lastMousePos = {x: e.clientX, y: e.clientY}; - if (this.enabled && (e.shiftKey || e.which === 2)) { - this.searchAtPoint(this.lastMousePos); - } - } - - onMouseDown(e) { - this.lastMousePos = {x: e.clientX, y: e.clientY}; - if (this.enabled && (e.shiftKey || e.which === 2)) { - this.searchAtPoint(this.lastMousePos); - } else { - this.hidePopup(); - } - } - - onMessage(request, sender, callback) { - this.setEnabled(request === 'enabled'); - callback(); - } - - searchAtPoint(point) { - const range = getRangeAtPoint(point, 10); - if (range === null) { - this.hidePopup(); - return; - } - - const rect = getRangePaddedRect(range); - if (point.x < rect.left || point.x > rect.right) { - this.hidePopup(); - return; - } - - findTerm(range.toString(), ({results, length}) => { - if (length === 0) { - this.hidePopup(); - } else { - range.setEnd(range.endContainer, range.startOffset + length); - renderTemplate({defs: results}, 'defs.html', (html) => { - this.popup.innerHTML = html; - this.showPopup(range); - }); - } - }); - } - - showPopup(range) { - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - - const pos = getPopupPositionForRange(this.popup, range, this.popupOffset); - - this.popup.style.left = pos.x + 'px'; - this.popup.style.top = pos.y + 'px'; - this.popup.style.visibility = 'visible'; - } - - hidePopup() { - if (this.popup.style.visibility !== 'hidden') { - const selection = window.getSelection(); - selection.removeAllRanges(); - this.popup.style.visibility = 'hidden'; - } - } - - setEnabled(enabled) { - if (!(this.enabled = enabled)) { - this.hidePopup(); - } - } -} - -window.yomiClient = new Client(); diff --git a/ext/css/client.css b/ext/css/client.css new file mode 100644 index 00000000..ce1ae9a9 --- /dev/null +++ b/ext/css/client.css @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + +.yomichan-popup { + all: initial; + background-color: #fff; + border: 1px solid #999; + box-shadow: 0 0 10px rgba(0, 0, 0, .5); + color: #000; + font-family: sans-serif; + font-size: 12pt; + max-height: 300px; + max-width: 500px; + min-width: 200px; + overflow-y: auto; + padding: 10px; + position: fixed; + visibility: hidden; + z-index: 256; +} + +.yomichan-def-expression { + float: left; + font-family: serif; + font-size: 16pt; +} + +.yomichan-def-reading { + float: right; + font-family: serif; + font-size: 16pt; +} + +.yomichan-def-glossary { + clear: both; +} diff --git a/ext/js/api.js b/ext/js/api.js new file mode 100644 index 00000000..7f552a1e --- /dev/null +++ b/ext/js/api.js @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +function sendMessage(action, data, callback) { + chrome.runtime.sendMessage({action: action, data: data}, callback); +} + +function findTerm(text, callback) { + sendMessage('findTerm', {text: text}, callback); +} + +function findKanji(text, callback) { + sendMessage('findKanji', {text: text}, callback); +} + +function renderTemplate(data, template, callback) { + sendMessage('renderTemplate', {data: data, template: template}, callback); +} + +function getState(callback) { + sendMessage('getState', null, callback); +} diff --git a/ext/js/client.js b/ext/js/client.js new file mode 100644 index 00000000..2d9a470f --- /dev/null +++ b/ext/js/client.js @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +class Client { + constructor() { + this.popupOffset = 10; + this.lastMosePos = null; + this.enabled = false; + + this.popup = document.createElement('div'); + this.popup.classList.add('yomichan-popup'); + this.popup.addEventListener('mousedown', (e) => e.stopPropagation()); + this.popup.addEventListener('scroll', (e) => e.stopPropagation()); + document.body.appendChild(this.popup); + + chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); + window.addEventListener('mousedown', this.onMouseDown.bind(this)); + window.addEventListener('mousemove', this.onMouseMove.bind(this)); + window.addEventListener('keydown', this.onKeyDown.bind(this)); + window.addEventListener('scroll', (e) => this.hidePopup()); + window.addEventListener('resize', (e) => this.hidePopup()); + + getState((state) => this.setEnabled(state === 'enabled')); + } + + onKeyDown(e) { + if (this.enabled && this.lastMousePos !== null && (e.keyCode === 16 || e.charCode === 16)) { + this.searchAtPoint(this.lastMousePos); + } + } + + onMouseMove(e) { + this.lastMousePos = {x: e.clientX, y: e.clientY}; + if (this.enabled && (e.shiftKey || e.which === 2)) { + this.searchAtPoint(this.lastMousePos); + } + } + + onMouseDown(e) { + this.lastMousePos = {x: e.clientX, y: e.clientY}; + if (this.enabled && (e.shiftKey || e.which === 2)) { + this.searchAtPoint(this.lastMousePos); + } else { + this.hidePopup(); + } + } + + onMessage(request, sender, callback) { + this.setEnabled(request === 'enabled'); + callback(); + } + + searchAtPoint(point) { + const range = getRangeAtPoint(point, 10); + if (range === null) { + this.hidePopup(); + return; + } + + const rect = getRangePaddedRect(range); + if (point.x < rect.left || point.x > rect.right) { + this.hidePopup(); + return; + } + + findTerm(range.toString(), ({results, length}) => { + if (length === 0) { + this.hidePopup(); + } else { + range.setEnd(range.endContainer, range.startOffset + length); + renderTemplate({defs: results}, 'defs.html', (html) => { + this.popup.innerHTML = html; + this.showPopup(range); + }); + } + }); + } + + showPopup(range) { + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + + const pos = getPopupPositionForRange(this.popup, range, this.popupOffset); + + this.popup.style.left = pos.x + 'px'; + this.popup.style.top = pos.y + 'px'; + this.popup.style.visibility = 'visible'; + } + + hidePopup() { + if (this.popup.style.visibility !== 'hidden') { + const selection = window.getSelection(); + selection.removeAllRanges(); + this.popup.style.visibility = 'hidden'; + } + } + + setEnabled(enabled) { + if (!(this.enabled = enabled)) { + this.hidePopup(); + } + } +} + +window.yomiClient = new Client(); diff --git a/ext/js/util.js b/ext/js/util.js new file mode 100644 index 00000000..eb153e9b --- /dev/null +++ b/ext/js/util.js @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +function getRangeAtPoint(point, lookAhead) { + const range = document.caretRangeFromPoint(point.x, point.y); + if (range === null) { + return null; + } + + const node = range.startContainer; + if (node.nodeType !== 3 /* TEXT_NODE */) { + return null; + } + + const offset = range.startOffset; + const length = Math.min(node.length - offset, lookAhead); + if (length === 0) { + return null; + } + + range.setEnd(node, offset + length); + return range; +} + +function getRangePaddedRect(range) { + const node = range.startContainer; + const startOffset = range.startOffset; + const endOffset = range.endOffset; + + range.setStart(node, Math.max(0, startOffset - 1)); + range.setEnd(node, Math.min(node.length, endOffset + 1)); + const rect = range.getBoundingClientRect(); + range.setStart(node, startOffset); + range.setEnd(node, endOffset); + + return rect; +} + +function getPopupPositionForRange(popup, range, offset) { + const rangeRect = range.getBoundingClientRect(); + const popupRect = popup.getBoundingClientRect(); + + let posX = rangeRect.left; + if (posX + popupRect.width >= window.innerWidth) { + posX = window.innerWidth - popupRect.width; + } + + let posY = rangeRect.bottom + offset; + if (posY + popupRect.height >= window.innerHeight) { + posY = rangeRect.top - popupRect.height - offset; + } + + return {x: posX, y: posY}; +} diff --git a/ext/manifest.json b/ext/manifest.json index 538d1d37..2f1a2b58 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -11,7 +11,7 @@ "content_scripts": [{ "matches": ["*://*/*"], - "js": ["api.js", "util.js", "client.js"], - "css": ["client.css"] + "js": ["js/api.js", "js/util.js", "js/client.js"], + "css": ["css/client.css"] }] } diff --git a/ext/util.js b/ext/util.js deleted file mode 100644 index eb153e9b..00000000 --- a/ext/util.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -function getRangeAtPoint(point, lookAhead) { - const range = document.caretRangeFromPoint(point.x, point.y); - if (range === null) { - return null; - } - - const node = range.startContainer; - if (node.nodeType !== 3 /* TEXT_NODE */) { - return null; - } - - const offset = range.startOffset; - const length = Math.min(node.length - offset, lookAhead); - if (length === 0) { - return null; - } - - range.setEnd(node, offset + length); - return range; -} - -function getRangePaddedRect(range) { - const node = range.startContainer; - const startOffset = range.startOffset; - const endOffset = range.endOffset; - - range.setStart(node, Math.max(0, startOffset - 1)); - range.setEnd(node, Math.min(node.length, endOffset + 1)); - const rect = range.getBoundingClientRect(); - range.setStart(node, startOffset); - range.setEnd(node, endOffset); - - return rect; -} - -function getPopupPositionForRange(popup, range, offset) { - const rangeRect = range.getBoundingClientRect(); - const popupRect = popup.getBoundingClientRect(); - - let posX = rangeRect.left; - if (posX + popupRect.width >= window.innerWidth) { - posX = window.innerWidth - popupRect.width; - } - - let posY = rangeRect.bottom + offset; - if (posY + popupRect.height >= window.innerHeight) { - posY = rangeRect.top - popupRect.height - offset; - } - - return {x: posX, y: posY}; -} -- cgit v1.2.3