diff options
author | Alex Yatskov <alex@foosoft.net> | 2019-10-05 10:14:07 -0700 |
---|---|---|
committer | Alex Yatskov <alex@foosoft.net> | 2019-10-05 10:14:07 -0700 |
commit | 7f5f3951f057b84864f7f37825804f9049ddab10 (patch) | |
tree | 1a724bbd4d8cf2b053f3e2b99b46ba41008508b8 /ext/bg | |
parent | f4b6527ed6ed1f0f4f5a63b94766b20f3b90e6ec (diff) | |
parent | 46ab36180f486a19332c538401b4db12ffe1bda1 (diff) |
Merge branch 'master' into testing
Diffstat (limited to 'ext/bg')
-rw-r--r-- | ext/bg/context.html | 18 | ||||
-rw-r--r-- | ext/bg/css/settings.css | 154 | ||||
-rw-r--r-- | ext/bg/js/api.js | 52 | ||||
-rw-r--r-- | ext/bg/js/backend.js | 84 | ||||
-rw-r--r-- | ext/bg/js/conditions-ui.js | 4 | ||||
-rw-r--r-- | ext/bg/js/database.js | 180 | ||||
-rw-r--r-- | ext/bg/js/deinflector.js | 122 | ||||
-rw-r--r-- | ext/bg/js/options.js | 1 | ||||
-rw-r--r-- | ext/bg/js/search.js | 52 | ||||
-rw-r--r-- | ext/bg/js/settings.js | 27 | ||||
-rw-r--r-- | ext/bg/js/translator.js | 179 | ||||
-rw-r--r-- | ext/bg/lang/deinflect.json | 205 | ||||
-rw-r--r-- | ext/bg/legal.html | 20 | ||||
-rw-r--r-- | ext/bg/search.html | 22 | ||||
-rw-r--r-- | ext/bg/settings.html | 167 |
15 files changed, 812 insertions, 475 deletions
diff --git a/ext/bg/context.html b/ext/bg/context.html index 198ccd42..51346838 100644 --- a/ext/bg/context.html +++ b/ext/bg/context.html @@ -14,20 +14,20 @@ .btn-group { display: flex; + margin-top: 10px; + white-space: nowrap; } </style> </head> <body> - <p> + <div> <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> + </div> + <div class="btn-group"> + <button type="button" id="open-search" title="Search (Alt + Insert)" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-search"></span></button> + <button type="button" id="open-options" title="Options" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-wrench"></span></button> + <button type="button" id="open-help" title="Help" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-question-sign"></span></button> + </div> <script src="/mixed/lib/jquery.min.js"></script> <script src="/mixed/lib/bootstrap-toggle/bootstrap-toggle.min.js"></script> diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css new file mode 100644 index 00000000..12bbe8a8 --- /dev/null +++ b/ext/bg/css/settings.css @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2019 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/>. + */ + + +#anki-spinner, #anki-general, #anki-error, +#dict-spinner, #dict-error, #dict-warning, #dict-purge, #dict-import-progress, +#debug, .options-advanced, .storage-hidden, #storage-spinner { + display: none; +} + +.tab-content { + border-bottom: 1px #ddd solid; + border-left: 1px #ddd solid; + border-right: 1px #ddd solid; + padding: 10px; +} + +#field-templates { + font-family: monospace; + overflow-x: hidden; + white-space: pre; +} + +.bottom-links { + padding-bottom: 1em; +} + +.label-light { + font-weight: normal; +} + +.form-control.is-invalid { + border-color: #f00000; +} + +.condition { + display: flex; + -flex-wrap: wrap; +} +.condition-input { + flex: 1 1 auto; +} +.condition-line-break { + flex: 1 0 100%; + display: none; +} +.condition>.input-group-btn { + width: auto; +} +.condition>.condition-prefix:after { + content: "IF"; +} +.condition:nth-child(n+2)>.condition-prefix:after { + content: "AND"; +} + +.input-group .condition-prefix { + flex: 0 0 auto; +} +.input-group .condition-prefix, +.input-group .condition-group-separator-label { + width: 60px; + text-align: center; +} +.input-group .condition-group-separator-label { + padding: 6px 12px; + font-weight: bold; + display: inline-block; +} +.input-group .condition-type, +.input-group .condition-operator { + width: auto; + text-align: center; + text-align-last: center; +} + +.condition-group>.condition>div:first-child { + border-bottom-left-radius: 0; +} +.condition-group>.condition:nth-child(n+2)>div:first-child { + border-top-left-radius: 0; +} +.condition-group>.condition:nth-child(n+2)>div:last-child>button { + border-top-right-radius: 0; +} +.condition-group>.condition:nth-last-child(n+2)>div:last-child>button { + border-bottom-right-radius: 0; +} +.condition-group-options>.condition-add { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.condition-groups>*:last-of-type { + display: none; +} + +#custom-popup-css { + width: 100%; + min-height: 34px; + height: 96px; + resize: vertical; + font-family: 'Courier New', Courier, monospace; + white-space: pre; +} + +[data-show-for-browser] { + display: none; +} + +[data-browser=edge] [data-show-for-browser~=edge], +[data-browser=chrome] [data-show-for-browser~=chrome], +[data-browser=firefox] [data-show-for-browser~=firefox], +[data-browser=firefox-mobile] [data-show-for-browser~=firefox-mobile] { + display: initial; +} + +@media screen and (max-width: 740px) { + .col-xs-6 { + float: none; + width: 100%; + } + .col-xs-6+.col-xs-6 { + margin-top: 15px; + } +} + +@media screen and (max-width: 600px) { + .condition { + flex-wrap: wrap; + } + .condition-input { + order: 2; + } + .condition-line-break { + display: block; + order: 1; + } +} diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 474fe604..222e7ffe 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -126,35 +126,35 @@ async function apiTemplateRender(template, data, dynamic) { } async function apiCommandExec(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: async () => { - const optionsContext = { - depth: 0, - url: window.location.href - }; - const options = await apiOptionsGet(optionsContext); - options.general.enable = !options.general.enable; - await apiOptionsSave('popup'); - } - }; - - const handler = handlers[command]; - if (handler) { + const handlers = apiCommandExec.handlers; + if (handlers.hasOwnProperty(command)) { + const handler = handlers[command]; handler(); } } +apiCommandExec.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: async () => { + const optionsContext = { + depth: 0, + url: window.location.href + }; + const options = await apiOptionsGet(optionsContext); + options.general.enable = !options.general.enable; + await apiOptionsSave('popup'); + } +}; async function apiAudioGetUrl(definition, source) { return audioBuildUrl(definition, source); diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 4068b760..3c5ad885 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -69,68 +69,13 @@ class Backend { } onMessage({action, params}, sender, callback) { - const forward = (promise, callback) => { - return promise.then(result => { - callback({result}); - }).catch(error => { - callback({error: error.toString ? error.toString() : error}); - }); - }; - - const handlers = { - optionsGet: ({optionsContext, callback}) => { - forward(apiOptionsGet(optionsContext), callback); - }, - - kanjiFind: ({text, optionsContext, callback}) => { - forward(apiKanjiFind(text, optionsContext), callback); - }, - - termsFind: ({text, optionsContext, callback}) => { - forward(apiTermsFind(text, optionsContext), callback); - }, - - definitionAdd: ({definition, mode, context, optionsContext, callback}) => { - forward(apiDefinitionAdd(definition, mode, context, optionsContext), callback); - }, - - definitionsAddable: ({definitions, modes, optionsContext, callback}) => { - forward(apiDefinitionsAddable(definitions, modes, optionsContext), callback); - }, - - noteView: ({noteId}) => { - forward(apiNoteView(noteId), callback); - }, - - templateRender: ({template, data, dynamic, callback}) => { - forward(apiTemplateRender(template, data, dynamic), callback); - }, - - commandExec: ({command, callback}) => { - forward(apiCommandExec(command), callback); - }, - - audioGetUrl: ({definition, source, callback}) => { - forward(apiAudioGetUrl(definition, source), callback); - }, - - screenshotGet: ({options}) => { - forward(apiScreenshotGet(options, sender), callback); - }, - - forward: ({action, params}) => { - forward(apiForward(action, params, sender), callback); - }, - - frameInformationGet: () => { - forward(apiFrameInformationGet(sender), callback); - } - }; - - const handler = handlers[action]; - if (handler) { - params.callback = callback; - handler(params); + const handlers = Backend.messageHandlers; + if (handlers.hasOwnProperty(action)) { + const handler = handlers[action]; + const promise = handler(params, sender); + promise + .then(result => callback({result})) + .catch(error => callback({error: typeof error.toString === 'function' ? error.toString() : error})); } return true; @@ -227,5 +172,20 @@ class Backend { } } +Backend.messageHandlers = { + optionsGet: ({optionsContext}) => apiOptionsGet(optionsContext), + kanjiFind: ({text, optionsContext}) => apiKanjiFind(text, optionsContext), + termsFind: ({text, optionsContext}) => apiTermsFind(text, optionsContext), + definitionAdd: ({definition, mode, context, optionsContext}) => apiDefinitionAdd(definition, mode, context, optionsContext), + definitionsAddable: ({definitions, modes, optionsContext}) => apiDefinitionsAddable(definitions, modes, optionsContext), + noteView: ({noteId}) => apiNoteView(noteId), + templateRender: ({template, data, dynamic}) => apiTemplateRender(template, data, dynamic), + commandExec: ({command}) => apiCommandExec(command), + audioGetUrl: ({definition, source}) => apiAudioGetUrl(definition, source), + screenshotGet: ({options}, sender) => apiScreenshotGet(options, sender), + forward: ({action, params}, sender) => apiForward(action, params, sender), + frameInformationGet: (params, sender) => apiFrameInformationGet(sender), +}; + window.yomichan_backend = new Backend(); window.yomichan_backend.prepare(); diff --git a/ext/bg/js/conditions-ui.js b/ext/bg/js/conditions-ui.js index a6f54a1c..43c6dc08 100644 --- a/ext/bg/js/conditions-ui.js +++ b/ext/bg/js/conditions-ui.js @@ -36,7 +36,7 @@ ConditionsUI.Container = class Container { this.container.empty(); - for (const conditionGroup of conditionGroups) { + for (const conditionGroup of toIterable(conditionGroups)) { this.children.push(new ConditionsUI.ConditionGroup(this, conditionGroup)); } @@ -122,7 +122,7 @@ ConditionsUI.ConditionGroup = class ConditionGroup { this.separator = ConditionsUI.instantiateTemplate('#condition-group-separator-template').appendTo(parent.container); this.addButton = this.options.find('.condition-add'); - for (const condition of conditionGroup.conditions) { + for (const condition of toIterable(conditionGroup.conditions)) { this.children.push(new ConditionsUI.Condition(this, condition)); } diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 093ec102..e8214c3c 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -68,24 +68,39 @@ class Database { const results = []; await this.db.terms.where('expression').equals(term).or('reading').equals(term).each(row => { if (titles.includes(row.dictionary)) { - results.push({ - expression: row.expression, - reading: row.reading, - definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''), - termTags: dictFieldSplit(row.termTags || ''), - rules: dictFieldSplit(row.rules), - glossary: row.glossary, - score: row.score, - dictionary: row.dictionary, - id: row.id, - sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence - }); + results.push(Database.createTerm(row)); } }); return results; } + async findTermsBulk(terms, titles) { + const promises = []; + const visited = {}; + const results = []; + const createResult = Database.createTerm; + const filter = (row) => titles.includes(row.dictionary); + + const db = this.db.backendDB(); + const dbTransaction = db.transaction(['terms'], 'readonly'); + const dbTerms = dbTransaction.objectStore('terms'); + const dbIndex1 = dbTerms.index('expression'); + const dbIndex2 = dbTerms.index('reading'); + + for (let i = 0; i < terms.length; ++i) { + const only = IDBKeyRange.only(terms[i]); + promises.push( + Database.getAll(dbIndex1, only, i, visited, filter, createResult, results), + Database.getAll(dbIndex2, only, i, visited, filter, createResult, results) + ); + } + + await Promise.all(promises); + + return results; + } + async findTermsExact(term, reading, titles) { if (!this.db) { throw 'Database not initialized'; @@ -94,18 +109,7 @@ class Database { const results = []; await this.db.terms.where('expression').equals(term).each(row => { if (row.reading === reading && titles.includes(row.dictionary)) { - results.push({ - expression: row.expression, - reading: row.reading, - definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''), - termTags: dictFieldSplit(row.termTags || ''), - rules: dictFieldSplit(row.rules), - glossary: row.glossary, - score: row.score, - dictionary: row.dictionary, - id: row.id, - sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence - }); + results.push(Database.createTerm(row)); } }); @@ -120,18 +124,7 @@ class Database { const results = []; await this.db.terms.where('sequence').equals(sequence).each(row => { if (row.dictionary === mainDictionary) { - results.push({ - expression: row.expression, - reading: row.reading, - definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''), - termTags: dictFieldSplit(row.termTags || ''), - rules: dictFieldSplit(row.rules), - glossary: row.glossary, - score: row.score, - dictionary: row.dictionary, - id: row.id, - sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence - }); + results.push(Database.createTerm(row)); } }); @@ -157,6 +150,28 @@ class Database { return results; } + async findTermMetaBulk(terms, titles) { + const promises = []; + const visited = {}; + const results = []; + const createResult = Database.createTermMeta; + const filter = (row) => titles.includes(row.dictionary); + + const db = this.db.backendDB(); + const dbTransaction = db.transaction(['termMeta'], 'readonly'); + const dbTerms = dbTransaction.objectStore('termMeta'); + const dbIndex = dbTerms.index('expression'); + + for (let i = 0; i < terms.length; ++i) { + const only = IDBKeyRange.only(terms[i]); + promises.push(Database.getAll(dbIndex, only, i, visited, filter, createResult, results)); + } + + await Promise.all(promises); + + return results; + } + async findKanji(kanji, titles) { if (!this.db) { throw 'Database not initialized'; @@ -199,23 +214,30 @@ class Database { return results; } + findTagForTitleCached(name, title) { + if (this.tagCache.hasOwnProperty(title)) { + const cache = this.tagCache[title]; + if (cache.hasOwnProperty(name)) { + return cache[name]; + } + } + } + async findTagForTitle(name, title) { if (!this.db) { throw 'Database not initialized'; } - this.tagCache[title] = this.tagCache[title] || {}; + const cache = (this.tagCache.hasOwnProperty(title) ? this.tagCache[title] : (this.tagCache[title] = {})); - let result = this.tagCache[title][name]; - if (!result) { - await this.db.tagMeta.where('name').equals(name).each(row => { - if (title === row.dictionary) { - result = row; - } - }); + let result = null; + await this.db.tagMeta.where('name').equals(name).each(row => { + if (title === row.dictionary) { + result = row; + } + }); - this.tagCache[title][name] = result; - } + cache[name] = result; return result; } @@ -489,4 +511,70 @@ class Database { return summary; } + + static createTerm(row, index) { + return { + index, + expression: row.expression, + reading: row.reading, + definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''), + termTags: dictFieldSplit(row.termTags || ''), + rules: dictFieldSplit(row.rules), + glossary: row.glossary, + score: row.score, + dictionary: row.dictionary, + id: row.id, + sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence + }; + } + + static createTermMeta(row, index) { + return { + index, + mode: row.mode, + data: row.data, + dictionary: row.dictionary + }; + } + + static getAll(dbIndex, query, index, visited, filter, createResult, results) { + const fn = typeof dbIndex.getAll === 'function' ? Database.getAllFast : Database.getAllUsingCursor; + return fn(dbIndex, query, index, visited, filter, createResult, results); + } + + static getAllFast(dbIndex, query, index, visited, filter, createResult, results) { + return new Promise((resolve, reject) => { + const request = dbIndex.getAll(query); + request.onerror = (e) => reject(e); + request.onsuccess = (e) => { + for (const row of e.target.result) { + if (filter(row, index) && !visited.hasOwnProperty(row.id)) { + visited[row.id] = true; + results.push(createResult(row, index)); + } + } + resolve(); + }; + }); + } + + static getAllUsingCursor(dbIndex, query, index, visited, filter, createResult, results) { + return new Promise((resolve, reject) => { + const request = dbIndex.openCursor(query, 'next'); + request.onerror = (e) => reject(e); + request.onsuccess = (e) => { + const cursor = e.target.result; + if (cursor) { + const row = cursor.value; + if (filter(row, index) && !visited.hasOwnProperty(row.id)) { + visited[row.id] = true; + results.push(createResult(row, index)); + } + cursor.continue(); + } else { + resolve(); + } + }; + }); + } } diff --git a/ext/bg/js/deinflector.js b/ext/bg/js/deinflector.js index 0abde99d..ad77895c 100644 --- a/ext/bg/js/deinflector.js +++ b/ext/bg/js/deinflector.js @@ -17,103 +17,53 @@ */ -class Deinflection { - constructor(term, {rules=[], definitions=[], reason=''} = {}) { - this.term = term; - this.rules = rules; - this.definitions = definitions; - this.reason = reason; - this.children = []; +class Deinflector { + constructor(reasons) { + this.reasons = reasons; } - async deinflect(definer, reasons) { - for (const reason in reasons) { - for (const variant of reasons[reason]) { - let accept = this.rules.length === 0; - if (!accept) { - for (const rule of this.rules) { - if (variant.rulesIn.includes(rule)) { - accept = true; - break; + deinflect(source) { + const results = [{ + source, + term: source, + rules: [], + definitions: [], + reasons: [] + }]; + for (let i = 0; i < results.length; ++i) { + const entry = results[i]; + + for (const reason in this.reasons) { + for (const variant of this.reasons[reason]) { + let accept = entry.rules.length === 0; + if (!accept) { + for (const rule of entry.rules) { + if (variant.rulesIn.includes(rule)) { + accept = true; + break; + } } } - } - if (!accept || !this.term.endsWith(variant.kanaIn)) { - continue; - } - - const term = this.term.slice(0, -variant.kanaIn.length) + variant.kanaOut; - if (term.length === 0) { - continue; - } - - const child = new Deinflection(term, {reason, rules: variant.rulesOut}); - if (await child.deinflect(definer, reasons)) { - this.children.push(child); - } - } - } - - const definitions = await definer(this.term); - if (this.rules.length === 0) { - this.definitions = definitions; - } else { - for (const rule of this.rules) { - for (const definition of definitions) { - if (definition.rules.includes(rule)) { - this.definitions.push(definition); + if (!accept || !entry.term.endsWith(variant.kanaIn)) { + continue; } - } - } - } - - if (this.definitions.length > 0 && this.children.length > 0) { - const child = new Deinflection(this.term, {rules: this.rules, definitions: this.definitions}); - this.children.push(child); - } - return this.definitions.length > 0 || this.children.length > 0; - } - - gather() { - if (this.children.length === 0) { - return [{ - source: this.term, - rules: this.rules, - definitions: this.definitions, - reasons: this.reason.length > 0 ? [this.reason] : [] - }]; - } + const term = entry.term.slice(0, -variant.kanaIn.length) + variant.kanaOut; + if (term.length === 0) { + continue; + } - const results = []; - for (const child of this.children) { - for (const result of child.gather()) { - if (this.reason.length > 0) { - result.reasons.push(this.reason); + results.push({ + source, + term, + rules: variant.rulesOut, + definitions: [], + reasons: [reason, ...entry.reasons] + }); } - - result.source = this.term; - results.push(result); } } - return results; } } - - -class Deinflector { - constructor(reasons) { - this.reasons = reasons; - } - - async deinflect(term, definer) { - const node = new Deinflection(term); - if (await node.deinflect(definer, this.reasons)) { - return node.gather(); - } else { - return []; - } - } -} diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index e9e321df..2c9de1ec 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -281,6 +281,7 @@ function profileOptionsCreateDefaults() { modifier: 'shift', deepDomScan: false, popupNestingMaxDepth: 0, + enablePopupSearch: false, enableOnPopupExpressions: false, enableOnSearchPage: true }, diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 6ff710f0..13ed1e08 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -19,20 +19,27 @@ class DisplaySearch extends Display { constructor() { - super($('#spinner'), $('#content')); + super(document.querySelector('#spinner'), document.querySelector('#content')); this.optionsContext = { depth: 0, url: window.location.href }; - this.search = $('#search').click(this.onSearch.bind(this)); - this.query = $('#query').on('input', this.onSearchInput.bind(this)); - this.intro = $('#intro'); + this.search = document.querySelector('#search'); + this.query = document.querySelector('#query'); + this.intro = document.querySelector('#intro'); + this.introHidden = false; this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); - window.wanakana.bind(this.query.get(0)); + if (this.search !== null) { + this.search.addEventListener('click', (e) => this.onSearch(e), false); + } + if (this.query !== null) { + this.query.addEventListener('input', () => this.onSearchInput(), false); + window.wanakana.bind(this.query); + } } onError(error) { @@ -40,23 +47,50 @@ class DisplaySearch extends Display { } onSearchClear() { - this.query.focus().select(); + if (this.query === null) { + return; + } + + this.query.focus(); + this.query.select(); } onSearchInput() { - this.search.prop('disabled', this.query.val().length === 0); + this.search.disabled = (this.query === null || this.query.value.length === 0); } async onSearch(e) { + if (this.query === null) { + return; + } + try { e.preventDefault(); - this.intro.slideUp(); - const {length, definitions} = await apiTermsFind(this.query.val(), this.optionsContext); + this.hideIntro(); + const {length, definitions} = await apiTermsFind(this.query.value, this.optionsContext); super.termsShow(definitions, await apiOptionsGet(this.optionsContext)); } catch (e) { this.onError(e); } } + + hideIntro() { + if (this.introHidden) { + return; + } + + this.introHidden = true; + + if (this.intro === null) { + return; + } + + const size = this.intro.getBoundingClientRect(); + this.intro.style.height = `${size.height}px`; + this.intro.style.transition = 'height 0.4s ease-in-out 0s'; + window.getComputedStyle(this.intro).getPropertyValue('height'); // Commits height so next line can start animation + this.intro.style.height = '0'; + } } window.yomichan_search = new DisplaySearch(); diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index cb3ddd4e..9838ea02 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -50,6 +50,7 @@ async function formRead(options) { options.scanning.alphanumeric = $('#search-alphanumeric').prop('checked'); options.scanning.autoHideResults = $('#auto-hide-results').prop('checked'); options.scanning.deepDomScan = $('#deep-dom-scan').prop('checked'); + options.scanning.enablePopupSearch = $('#enable-search-within-first-popup').prop('checked'); options.scanning.enableOnPopupExpressions = $('#enable-scanning-of-popup-expressions').prop('checked'); options.scanning.enableOnSearchPage = $('#enable-scanning-on-search-page').prop('checked'); options.scanning.delay = parseInt($('#scan-delay').val(), 10); @@ -115,6 +116,7 @@ async function formWrite(options) { $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric); $('#auto-hide-results').prop('checked', options.scanning.autoHideResults); $('#deep-dom-scan').prop('checked', options.scanning.deepDomScan); + $('#enable-search-within-first-popup').prop('checked', options.scanning.enablePopupSearch); $('#enable-scanning-of-popup-expressions').prop('checked', options.scanning.enableOnPopupExpressions); $('#enable-scanning-on-search-page').prop('checked', options.scanning.enableOnSearchPage); $('#scan-delay').val(options.scanning.delay); @@ -149,6 +151,7 @@ async function formWrite(options) { function formSetupEventListeners() { $('#dict-purge-link').click(utilAsync(onDictionaryPurge)); $('#dict-file').change(utilAsync(onDictionaryImport)); + $('#dict-file-button').click(onDictionaryImportButtonClick); $('#field-templates-reset').click(utilAsync(onAnkiFieldTemplatesReset)); $('input, select, textarea').not('.anki-model').not('.profile-form *').change(utilAsync(onFormOptionsChanged)); @@ -238,6 +241,8 @@ async function onFormOptionsChanged(e) { } async function onReady() { + showExtensionInformation(); + formSetupEventListeners(); await profileOptionsSetup(); @@ -420,7 +425,7 @@ async function onDictionaryPurge(e) { dictionarySpinnerShow(true); await utilDatabasePurge(); - for (const options of await getOptionsArray()) { + for (const options of toIterable(await getOptionsArray())) { options.dictionaries = utilBackgroundIsolate({}); options.general.mainDictionary = ''; } @@ -444,6 +449,11 @@ async function onDictionaryPurge(e) { } } +function onDictionaryImportButtonClick() { + const dictFile = document.querySelector('#dict-file'); + dictFile.click(); +} + async function onDictionaryImport(e) { const dictFile = $('#dict-file'); const dictControls = $('#dict-importer').hide(); @@ -464,7 +474,7 @@ async function onDictionaryImport(e) { const exceptions = []; const summary = await utilDatabaseImport(e.target.files[0], updateProgress, exceptions); - for (const options of await getOptionsArray()) { + for (const options of toIterable(await getOptionsArray())) { options.dictionaries[summary.title] = utilBackgroundIsolate({ enabled: true, priority: 0, @@ -739,3 +749,16 @@ function storageSpinnerShow(show) { spinner.hide(); } } + + +/* + * Information + */ + +function showExtensionInformation() { + const node = document.getElementById('extension-info'); + if (node === null) { return; } + + const manifest = chrome.runtime.getManifest(); + node.textContent = `${manifest.name} v${manifest.version}`; +} diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 7b952622..65d746ea 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -41,9 +41,7 @@ class Translator { const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); const definitionsGrouped = dictTermsGroup(definitions, dictionaries); - for (const definition of definitionsGrouped) { - await this.buildTermFrequencies(definition, titles); - } + await this.buildTermFrequencies(definitionsGrouped, titles); if (options.general.compactTags) { for (const definition of definitionsGrouped) { @@ -147,9 +145,7 @@ class Translator { definitionsMerged.push(groupedDefinition); } - for (const definition of definitionsMerged) { - await this.buildTermFrequencies(definition, titles); - } + await this.buildTermFrequencies(definitionsMerged, titles); if (options.general.compactTags) { for (const definition of definitionsMerged) { @@ -164,9 +160,7 @@ class Translator { const titles = Object.keys(dictionaries); const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); - for (const definition of definitions) { - await this.buildTermFrequencies(definition, titles); - } + await this.buildTermFrequencies(definitions, titles); return {length, definitions}; } @@ -179,13 +173,9 @@ class Translator { } } - const cache = {}; - const titles = Object.keys(dictionaries); - let deinflections = await this.findTermDeinflections(text, titles, cache); const textHiragana = jpKatakanaToHiragana(text); - if (text !== textHiragana) { - deinflections.push(...await this.findTermDeinflections(textHiragana, titles, cache)); - } + const titles = Object.keys(dictionaries); + const deinflections = await this.findTermDeinflections(text, textHiragana, titles); let definitions = []; for (const deinflection of deinflections) { @@ -221,19 +211,77 @@ class Translator { return {length, definitions}; } - async findTermDeinflections(text, titles, cache) { - const definer = async term => { - if (cache.hasOwnProperty(term)) { - return cache[term]; + async findTermDeinflections(text, text2, titles) { + const deinflections = (text === text2 ? this.getDeinflections(text) : this.getDeinflections2(text, text2)); + + if (deinflections.length === 0) { + return []; + } + + const uniqueDeinflectionTerms = []; + const uniqueDeinflectionArrays = []; + const uniqueDeinflectionsMap = {}; + for (const deinflection of deinflections) { + const term = deinflection.term; + let deinflectionArray; + if (uniqueDeinflectionsMap.hasOwnProperty(term)) { + deinflectionArray = uniqueDeinflectionsMap[term]; } else { - return cache[term] = await this.database.findTerms(term, titles); + deinflectionArray = []; + uniqueDeinflectionTerms.push(term); + uniqueDeinflectionArrays.push(deinflectionArray); + uniqueDeinflectionsMap[term] = deinflectionArray; } - }; + deinflectionArray.push(deinflection); + } + + const definitions = await this.database.findTermsBulk(uniqueDeinflectionTerms, titles); + + for (const definition of definitions) { + for (const deinflection of uniqueDeinflectionArrays[definition.index]) { + if (Translator.definitionContainsAnyRule(definition, deinflection.rules)) { + deinflection.definitions.push(definition); + } + } + } + + return deinflections.filter(e => e.definitions.length > 0); + } + + static definitionContainsAnyRule(definition, rules) { + if (rules.length === 0) { + return true; + } + const definitionRules = definition.rules; + for (const rule of rules) { + if (definitionRules.includes(rule)) { + return true; + } + } + return false; + } + + getDeinflections(text) { + const deinflections = []; + + for (let i = text.length; i > 0; --i) { + const textSlice = text.slice(0, i); + deinflections.push(...this.deinflector.deinflect(textSlice)); + } + + return deinflections; + } + + getDeinflections2(text, text2) { + const deinflections = []; - let deinflections = []; for (let i = text.length; i > 0; --i) { const textSlice = text.slice(0, i); - deinflections.push(...await this.deinflector.deinflect(textSlice, definer)); + const text2Slice = text2.slice(0, i); + deinflections.push(...this.deinflector.deinflect(textSlice)); + if (textSlice !== text2Slice) { + deinflections.push(...this.deinflector.deinflect(text2Slice)); + } } return deinflections; @@ -272,24 +320,51 @@ class Translator { return definitions; } - async buildTermFrequencies(definition, titles) { - let terms = []; - if (definition.expressions) { - terms.push(...definition.expressions); - } else { - terms.push(definition); + async buildTermFrequencies(definitions, titles) { + const terms = []; + for (const definition of definitions) { + if (definition.expressions) { + terms.push(...definition.expressions); + } else { + terms.push(definition); + } + } + + if (terms.length === 0) { + return; } - for (const term of terms) { + // Create mapping of unique terms + const expressionsUnique = []; + const termsUnique = []; + const termsUniqueMap = {}; + for (let i = 0, ii = terms.length; i < ii; ++i) { + const term = terms[i]; + const expression = term.expression; term.frequencies = []; - for (const meta of await this.database.findTermMeta(term.expression, titles)) { - if (meta.mode === 'freq') { - term.frequencies.push({ - expression: meta.expression, - frequency: meta.data, - dictionary: meta.dictionary - }); - } + + if (termsUniqueMap.hasOwnProperty(expression)) { + termsUniqueMap[expression].push(term); + } else { + const termList = [term]; + expressionsUnique.push(expression); + termsUnique.push(termList); + termsUniqueMap[expression] = termList; + } + } + + const metas = await this.database.findTermMetaBulk(expressionsUnique, titles); + for (const meta of metas) { + if (meta.mode !== 'freq') { + continue; + } + + for (const term of termsUnique[meta.index]) { + term.frequencies.push({ + expression: meta.expression, + frequency: meta.data, + dictionary: meta.dictionary + }); } } } @@ -298,15 +373,13 @@ class Translator { const tags = []; for (const name of names) { const base = Translator.getNameBase(name); - const meta = await this.database.findTagForTitle(base, title); - - const tag = {name}; - for (const prop in meta || {}) { - if (prop !== 'name') { - tag[prop] = meta[prop]; - } + let meta = this.database.findTagForTitleCached(base, title); + if (typeof meta === 'undefined') { + meta = await this.database.findTagForTitle(base, title); } + const tag = Object.assign({}, meta !== null ? meta : {}, {name}); + tags.push(dictTagSanitize(tag)); } @@ -317,16 +390,18 @@ class Translator { const stats = {}; for (const name in items) { const base = Translator.getNameBase(name); - const meta = await this.database.findTagForTitle(base, title); - const group = stats[meta.category] = stats[meta.category] || []; - - const stat = {name, value: items[name]}; - for (const prop in meta || {}) { - if (prop !== 'name') { - stat[prop] = meta[prop]; + let meta = this.database.findTagForTitleCached(base, title); + if (typeof meta === 'undefined') { + meta = await this.database.findTagForTitle(base, title); + if (meta === null) { + continue; } } + const group = stats[meta.category] = stats[meta.category] || []; + + const stat = Object.assign({}, meta, {name, value: items[name]}); + group.push(dictTagSanitize(stat)); } diff --git a/ext/bg/lang/deinflect.json b/ext/bg/lang/deinflect.json index 7a68ea71..c7977c88 100644 --- a/ext/bg/lang/deinflect.json +++ b/ext/bg/lang/deinflect.json @@ -1185,7 +1185,9 @@ { "kanaIn": "て", "kanaOut": "る", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v1", "vk" @@ -1194,7 +1196,9 @@ { "kanaIn": "いて", "kanaOut": "く", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1202,7 +1206,9 @@ { "kanaIn": "いで", "kanaOut": "ぐ", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1210,7 +1216,9 @@ { "kanaIn": "きて", "kanaOut": "くる", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "vk" ] @@ -1218,7 +1226,9 @@ { "kanaIn": "くて", "kanaOut": "い", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "adj-i" ] @@ -1226,7 +1236,9 @@ { "kanaIn": "して", "kanaOut": "す", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1234,7 +1246,9 @@ { "kanaIn": "して", "kanaOut": "する", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "vs" ] @@ -1242,7 +1256,9 @@ { "kanaIn": "って", "kanaOut": "う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1250,7 +1266,9 @@ { "kanaIn": "って", "kanaOut": "つ", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1258,7 +1276,9 @@ { "kanaIn": "って", "kanaOut": "る", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1266,7 +1286,9 @@ { "kanaIn": "んで", "kanaOut": "ぬ", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1274,7 +1296,9 @@ { "kanaIn": "んで", "kanaOut": "ぶ", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1282,7 +1306,9 @@ { "kanaIn": "んで", "kanaOut": "む", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1290,7 +1316,9 @@ { "kanaIn": "のたもうて", "kanaOut": "のたまう", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1298,7 +1326,9 @@ { "kanaIn": "いって", "kanaOut": "いく", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1306,7 +1336,9 @@ { "kanaIn": "おうて", "kanaOut": "おう", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1314,7 +1346,9 @@ { "kanaIn": "こうて", "kanaOut": "こう", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1322,7 +1356,9 @@ { "kanaIn": "そうて", "kanaOut": "そう", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1330,7 +1366,9 @@ { "kanaIn": "とうて", "kanaOut": "とう", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1338,7 +1376,9 @@ { "kanaIn": "行って", "kanaOut": "行く", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1346,7 +1386,9 @@ { "kanaIn": "逝って", "kanaOut": "逝く", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1354,7 +1396,9 @@ { "kanaIn": "往って", "kanaOut": "往く", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1362,7 +1406,9 @@ { "kanaIn": "請うて", "kanaOut": "請う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1370,7 +1416,9 @@ { "kanaIn": "乞うて", "kanaOut": "乞う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1378,7 +1426,9 @@ { "kanaIn": "恋うて", "kanaOut": "恋う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1386,7 +1436,9 @@ { "kanaIn": "問うて", "kanaOut": "問う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1394,7 +1446,9 @@ { "kanaIn": "負うて", "kanaOut": "負う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1402,7 +1456,9 @@ { "kanaIn": "沿うて", "kanaOut": "沿う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1410,7 +1466,9 @@ { "kanaIn": "添うて", "kanaOut": "添う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1418,7 +1476,9 @@ { "kanaIn": "副うて", "kanaOut": "副う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] @@ -1426,10 +1486,22 @@ { "kanaIn": "厭うて", "kanaOut": "厭う", - "rulesIn": [], + "rulesIn": [ + "iru" + ], "rulesOut": [ "v5" ] + }, + { + "kanaIn": "で", + "kanaOut": "", + "rulesIn": [ + "iru" + ], + "rulesOut": [ + "neg-de" + ] } ], "-zu": [ @@ -2161,7 +2233,8 @@ "kanaIn": "ない", "kanaOut": "る", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v1", @@ -2172,7 +2245,8 @@ "kanaIn": "かない", "kanaOut": "く", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v5" @@ -2182,7 +2256,8 @@ "kanaIn": "がない", "kanaOut": "ぐ", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v5" @@ -2192,7 +2267,8 @@ "kanaIn": "くない", "kanaOut": "い", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "adj-i" @@ -2202,7 +2278,8 @@ "kanaIn": "こない", "kanaOut": "くる", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "vk" @@ -2212,7 +2289,8 @@ "kanaIn": "さない", "kanaOut": "す", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v5" @@ -2222,7 +2300,8 @@ "kanaIn": "しない", "kanaOut": "する", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "vs" @@ -2232,7 +2311,8 @@ "kanaIn": "たない", "kanaOut": "つ", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v5" @@ -2242,7 +2322,8 @@ "kanaIn": "なない", "kanaOut": "ぬ", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v5" @@ -2252,7 +2333,8 @@ "kanaIn": "ばない", "kanaOut": "ぶ", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v5" @@ -2262,7 +2344,8 @@ "kanaIn": "まない", "kanaOut": "む", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v5" @@ -2272,7 +2355,8 @@ "kanaIn": "らない", "kanaOut": "る", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v5" @@ -2282,7 +2366,8 @@ "kanaIn": "わない", "kanaOut": "う", "rulesIn": [ - "adj-i" + "adj-i", + "neg-de" ], "rulesOut": [ "v5" @@ -3593,5 +3678,37 @@ "vk" ] } + ], + "progressive or perfect": [ + { + "kanaIn": "いる", + "kanaOut": "", + "rulesIn": [ + "v1" + ], + "rulesOut": [ + "iru" + ] + }, + { + "kanaIn": "る", + "kanaOut": "", + "rulesIn": [ + "v1" + ], + "rulesOut": [ + "iru" + ] + }, + { + "kanaIn": "おる", + "kanaOut": "", + "rulesIn": [ + "v1" + ], + "rulesOut": [ + "iru" + ] + } ] } diff --git a/ext/bg/legal.html b/ext/bg/legal.html index 28c7fb21..26ac033d 100644 --- a/ext/bg/legal.html +++ b/ext/bg/legal.html @@ -8,10 +8,10 @@ <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css"> </head> <body> - <div class="container-fluid"> + <div class="container"> <h3>Yomichan License</h3> <pre> -Copyright (C) 2016-2017 Alex Yatskov +Copyright (C) 2016-2019 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 @@ -28,10 +28,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </pre> <h3>EDRDG License</h3> <pre> -This package uses the <a href="http://www.csse.monash.edu.au/~jwb/edict.html">EDICT</a> and <a href="http://www.csse.monash.edu.au/~jwb/kanjidic.html">KANJIDIC</a> dictionary files. These files are -the property of the <a href="http://www.edrdg.org/"> Electronic Dictionary Research and Development Group</a>, -and are used in conformance with the Group's <a href="http://www.edrdg.org/edrdg/licence.html">licence</a>. +This package uses the <a href="https://www.edrdg.org/jmdict/edict.html">EDICT</a> and <a href="https://www.edrdg.org/wiki/index.php/KANJIDIC_Project">KANJIDIC</a> dictionary files. These files are +the property of the <a href="https://www.edrdg.org/">Electronic Dictionary Research and Development Group</a>, +and are used in conformance with the Group's <a href="https://www.edrdg.org/edrdg/licence.html">licence</a>. </pre> + <h3>Third-Party Software Licenses</h3> + <ul> + <li><a href="https://github.com/twbs/bootstrap/blob/v3.3.7/LICENSE" target="_blank" rel="noopener">Bootstrap v3.3.7</a></li> + <li><a href="https://github.com/minhur/bootstrap-toggle/blob/2.2.0/LICENSE" target="_blank" rel="noopener">Bootstrap Toggle v2.2.0</a></li> + <li><a href="https://github.com/dfahlander/Dexie.js/blob/v2.0.0-beta.10/LICENSE" target="_blank" rel="noopener">Dexie v2.0.0-beta.10</a></li> + <li><a href="https://github.com/wycats/handlebars.js/blob/v4.0.6/LICENSE" target="_blank" rel="noopener">Handlebars v4.0.6</a></li> + <li><a href="https://github.com/jquery/jquery/blob/3.2.1/LICENSE.txt" target="_blank" rel="noopener">jQuery v3.2.1</a></li> + <li><a href="https://github.com/Stuk/jszip/blob/v3.1.3/LICENSE.markdown" target="_blank" rel="noopener">JSZip v3.1.3</a></li> + <li><a href="https://github.com/WaniKani/WanaKana/blob/2.2.3/LICENSE" target="_blank" rel="noopener">WanaKana v2.2.3</a></li> + </ul> </div> </div> </body> diff --git a/ext/bg/search.html b/ext/bg/search.html index 38c5a4e9..668b2436 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -9,22 +9,20 @@ <link rel="stylesheet" type="text/css" href="/mixed/css/display.css"> </head> <body> - <div class="container-fluid"> - <div id="intro"> + <div class="container"> + <div id="intro" style="overflow: hidden;"> <div class="page-header"> <h1>Yomichan Search</h1> </div> - <p>Search your installed dictionaries by entering a Japanese expression into the field below.</p> + <p style="margin-bottom: 0;">Search your installed dictionaries by entering a Japanese expression into the field below.</p> </div> - <p> - <form class="input-group"> - <input type="text" class="form-control" placeholder="Search for..." id="query" autofocus> - <span class="input-group-btn"> - <input type="submit" class="btn btn-default form-control" id="search" value="Search" disabled> - </span> - </form> - </p> + <form class="input-group" style="padding-top: 10px;"> + <input type="text" class="form-control" placeholder="Search for..." id="query" autofocus> + <span class="input-group-btn"> + <input type="submit" class="btn btn-default form-control" id="search" value="Search" disabled> + </span> + </form> <div id="spinner"> <img src="/mixed/img/spinner.gif"> @@ -34,7 +32,6 @@ </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="/mixed/js/extension.js"></script> @@ -49,6 +46,7 @@ <script src="/fg/js/source.js"></script> <script src="/mixed/js/display.js"></script> <script src="/mixed/js/japanese.js"></script> + <script src="/mixed/js/scroll.js"></script> <script src="/bg/js/search.js"></script> <script src="/bg/js/search-frontend.js"></script> diff --git a/ext/bg/settings.html b/ext/bg/settings.html index d38aa090..0bc5e14c 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -6,116 +6,14 @@ <title>Yomichan Options</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"> - <style> - #anki-spinner, #anki-general, #anki-error, - #dict-spinner, #dict-error, #dict-warning, #dict-purge, #dict-import-progress, - #debug, .options-advanced, .storage-hidden, #storage-spinner { - display: none; - } - - .tab-content { - border-bottom: 1px #ddd solid; - border-left: 1px #ddd solid; - border-right: 1px #ddd solid; - padding: 10px; - } - - #field-templates { - font-family: monospace; - overflow-x: hidden; - white-space: pre; - } - - .bottom-links { - padding-bottom: 1em; - } - - .label-light { - font-weight: normal; - } - - .form-control.is-invalid { - border-color: #f00000; - } - - .condition>.condition-prefix:after { - content: "IF"; - } - .condition:nth-child(n+2)>.condition-prefix:after { - content: "AND"; - } - - .input-group .condition-prefix, - .input-group .condition-group-separator-label { - width: 60px; - text-align: center; - } - .input-group .condition-group-separator-label { - padding: 6px 12px; - font-weight: bold; - display: inline-block; - } - .input-group .condition-type, - .input-group .condition-operator { - width: auto; - text-align: center; - text-align-last: center; - } - - .condition-group>.condition>div:first-child { - border-bottom-left-radius: 0; - } - .condition-group>.condition:nth-child(n+2)>div:first-child { - border-top-left-radius: 0; - } - .condition-group>.condition:nth-child(n+2)>div:last-child>button { - border-top-right-radius: 0; - } - .condition-group>.condition:nth-last-child(n+2)>div:last-child>button { - border-bottom-right-radius: 0; - } - .condition-group-options>.condition-add { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - .condition-groups>*:last-of-type { - display: none; - } - - #custom-popup-css { - width: 100%; - min-height: 34px; - height: 96px; - resize: vertical; - font-family: 'Courier New', Courier, monospace; - white-space: pre; - } - - [data-show-for-browser] { - display: none; - } - - [data-browser=edge] [data-show-for-browser~=edge], - [data-browser=chrome] [data-show-for-browser~=chrome], - [data-browser=firefox] [data-show-for-browser~=firefox], - [data-browser=firefox-mobile] [data-show-for-browser~=firefox-mobile] { - display: initial; - } - - @media screen and (max-width: 740px) { - .col-xs-6 { - float: none; - width: 100%; - } - .col-xs-6+.col-xs-6 { - margin-top: 15px; - } - } - </style> + <link rel="stylesheet" type="text/css" href="/bg/css/settings.css"> </head> <body> - <div class="container-fluid"> + <div class="container"> + <div class="page-header"> + <h1>Yomichan Options</h1> + </div> + <div class="profile-form"> <h3>Profiles</h3> @@ -211,7 +109,8 @@ <div class="input-group-addon condition-prefix"></div> <div class="input-group-btn"><select class="form-control btn btn-default condition-type"><optgroup label="Type"></optgroup></select></div> <div class="input-group-btn"><select class="form-control btn btn-default condition-operator"><optgroup label="Operator"></optgroup></select></div> - <input type="text" class="form-control" /> + <div class="condition-line-break"></div> + <div class="condition-input"><input type="text" class="form-control" /></div> <div class="input-group-btn"><button class="btn btn-danger condition-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></div> </div></template> <template id="condition-group-separator-template"><div class="input-group"> @@ -293,14 +192,14 @@ <div class="form-group"> <div class="row"> <div class="col-xs-6"> - <label for="popup-display-mode">Popup position for horizontal text</label> + <label for="popup-horizontal-text-position">Popup position for horizontal text</label> <select class="form-control" id="popup-horizontal-text-position"> <option value="below">Below text</option> <option value="above">Above text</option> </select> </div> <div class="col-xs-6"> - <label for="popup-display-mode">Popup position for vertical text</label> + <label for="popup-vertical-text-position">Popup position for vertical text</label> <select class="form-control" id="popup-vertical-text-position"> <option value="default">Same as for horizontal text</option> <option value="before">Before text reading direction</option> @@ -315,11 +214,11 @@ <div class="form-group options-advanced"> <div class="row"> <div class="col-xs-6"> - <label for="popup-display-mode">Popup width <span class="label-light">(in pixels)</span></label> + <label for="popup-width">Popup width <span class="label-light">(in pixels)</span></label> <input type="number" min="1" id="popup-width" class="form-control"> </div> <div class="col-xs-6"> - <label for="popup-display-mode">Popup height <span class="label-light">(in pixels)</span></label> + <label for="popup-height">Popup height <span class="label-light">(in pixels)</span></label> <input type="number" min="1" id="popup-height" class="form-control"> </div> </div> @@ -328,11 +227,11 @@ <div class="form-group options-advanced"> <div class="row"> <div class="col-xs-6"> - <label for="popup-display-mode">Horizontal popup offset <span class="label-light">(in pixels)</span></label> + <label for="popup-horizontal-offset">Horizontal popup offset <span class="label-light">(in pixels)</span></label> <input type="number" min="0" id="popup-horizontal-offset" class="form-control"> </div> <div class="col-xs-6"> - <label for="popup-display-mode">Vertical popup offset <span class="label-light">(in pixels)</span></label> + <label for="popup-vertical-offset">Vertical popup offset <span class="label-light">(in pixels)</span></label> <input type="number" min="0" id="popup-vertical-offset" class="form-control"> </div> </div> @@ -341,11 +240,11 @@ <div class="form-group options-advanced"> <div class="row"> <div class="col-xs-6"> - <label for="popup-display-mode">Horizontal popup offset for vertical text <span class="label-light">(in pixels)</span></label> + <label for="popup-horizontal-offset2">Horizontal popup offset for vertical text <span class="label-light">(in pixels)</span></label> <input type="number" min="0" id="popup-horizontal-offset2" class="form-control"> </div> <div class="col-xs-6"> - <label for="popup-display-mode">Vertical popup offset for vertical text <span class="label-light">(in pixels)</span></label> + <label for="popup-vertical-offset2">Vertical popup offset for vertical text <span class="label-light">(in pixels)</span></label> <input type="number" min="0" id="popup-vertical-offset2" class="form-control"> </div> </div> @@ -414,6 +313,10 @@ </p> <div class="checkbox"> + <label><input type="checkbox" id="enable-search-within-first-popup"> Enable search when clicking glossary entries</label> + </div> + + <div class="checkbox"> <label><input type="checkbox" id="enable-scanning-on-search-page"> Enable scanning on search page</label> </div> @@ -465,7 +368,8 @@ <a href="https://foosoft.net/projects/yomichan" target="_blank" rel="noopener">download free dictionaries</a> for use with this extension and to learn about importing proprietary EPWING dictionaries. </p> - <input type="file" id="dict-file"> + <button class="btn btn-primary" id="dict-file-button">Import Dictionary</button> + <div hidden><input type="file" id="dict-file"></div> </div> </div> @@ -499,7 +403,7 @@ </div></div> <div> - <input type="button" value="Refresh" id="storage-refresh" /> + <button class="btn btn-default" id="storage-refresh">Refresh</button> </div> </div> @@ -617,6 +521,29 @@ </div> <div> + <h3>Keyboard Shortcuts</h3> + + <p class="help-block"> + Yomichan includes keyboard shortcuts for common actions which can be adjusted + using the web browser's settings. + Instructions on how to access this settings page are listed below for a few browsers. + </p> + + <h4>Chrome</h4> + <ul class="help-block"> + <li>Open the settings page (<a href="chrome://settings/">chrome://settings/</a>)</li> + <li>Click the "Extensions" link in the left sidebar (<a href="chrome://extensions/">chrome://extensions/</a>)</li> + <li>Open the left side panel using the hamburger (<strong>≡</strong>) menu button, then click "Keyboard shortcuts"</li> + </ul> + + <h4>Firefox</h4> + <ul class="help-block"> + <li>Open the extensions page (<a href="about:addons">about:addons</a>)</li> + <li>Click the button on the right with the gear icon, then click "Manage Extension Shortcuts"</li> + </ul> + </div> + + <div> <h3>Support Development</h3> <p class="help-block"> @@ -635,7 +562,7 @@ <pre id="debug"></pre> <div class="pull-right bottom-links"> - <small><a href="search.html">Search</a> • <a href="https://foosoft.net/projects/yomichan/" target="_blank" rel="noopener">Homepage</a> • <a href="legal.html">Legal</a></small> + <small><span id="extension-info"></span> • <a href="search.html">Search</a> • <a href="https://foosoft.net/projects/yomichan/" target="_blank" rel="noopener">Homepage</a> • <a href="legal.html">Legal</a></small> </div> </div> |