From 51e17b35e3a855c0db6c4be94a8cb416b14c8ad7 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 15:21:43 -0400 Subject: Convert some util* functions into api* functions --- ext/bg/js/backend.js | 45 +++++++++++++++++++++++++++++++++++++- ext/bg/js/settings/anki.js | 10 ++++----- ext/bg/js/settings/dictionaries.js | 12 +++++----- ext/bg/js/util.js | 29 ------------------------ 4 files changed, 55 insertions(+), 41 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index be8ea322..bc687a24 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -102,7 +102,13 @@ class Backend { ['getQueryParserTemplatesHtml', {handler: this._onApiGetQueryParserTemplatesHtml.bind(this), async: true}], ['getZoom', {handler: this._onApiGetZoom.bind(this), async: true}], ['getMessageToken', {handler: this._onApiGetMessageToken.bind(this), async: false}], - ['getDefaultAnkiFieldTemplates', {handler: this._onApiGetDefaultAnkiFieldTemplates.bind(this), async: false}] + ['getDefaultAnkiFieldTemplates', {handler: this._onApiGetDefaultAnkiFieldTemplates.bind(this), async: false}], + ['getAnkiDeckNames', {handler: this._onApiGetAnkiDeckNames.bind(this), async: true}], + ['getAnkiModelNames', {handler: this._onApiGetAnkiModelNames.bind(this), async: true}], + ['getAnkiModelFieldNames', {handler: this._onApiGetAnkiModelFieldNames.bind(this), async: true}], + ['getDictionaryInfo', {handler: this._onApiGetDictionaryInfo.bind(this), async: true}], + ['getDictionaryCounts', {handler: this._onApiGetDictionaryCounts.bind(this), async: true}], + ['purgeDatabase', {handler: this._onApiPurgeDatabase.bind(this), async: true}] ]); this._commandHandlers = new Map([ @@ -704,6 +710,36 @@ class Backend { return this.defaultAnkiFieldTemplates; } + async _onApiGetAnkiDeckNames(params, sender) { + this._validatePrivilegedMessageSender(sender); + return await this.anki.getDeckNames(); + } + + async _onApiGetAnkiModelNames(params, sender) { + this._validatePrivilegedMessageSender(sender); + return await this.anki.getModelNames(); + } + + async _onApiGetAnkiModelFieldNames({modelName}, sender) { + this._validatePrivilegedMessageSender(sender); + return await this.anki.getModelFieldNames(modelName); + } + + async _onApiGetDictionaryInfo(params, sender) { + this._validatePrivilegedMessageSender(sender); + return await this.translator.database.getDictionaryInfo(); + } + + async _onApiGetDictionaryCounts({dictionaryNames, getTotal}, sender) { + this._validatePrivilegedMessageSender(sender); + return await this.translator.database.getDictionaryCounts(dictionaryNames, getTotal); + } + + async _onApiPurgeDatabase(params, sender) { + this._validatePrivilegedMessageSender(sender); + return await this.translator.purgeDatabase(); + } + // Command handlers async _onCommandSearch(params) { @@ -800,6 +836,13 @@ class Backend { // Utilities + _validatePrivilegedMessageSender(sender) { + const url = sender.url; + if (!(typeof url === 'string' && yomichan.isExtensionUrl(url))) { + throw new Error('Invalid message sender'); + } + } + async _getAudioUri(definition, source, details) { let optionsContext = (typeof details === 'object' && details !== null ? details.optionsContext : null); if (!(typeof optionsContext === 'object' && optionsContext !== null)) { diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js index b32a9517..ff1277ed 100644 --- a/ext/bg/js/settings/anki.js +++ b/ext/bg/js/settings/anki.js @@ -16,13 +16,13 @@ */ /* global + * apiGetAnkiDeckNames + * apiGetAnkiModelFieldNames + * apiGetAnkiModelNames * getOptionsContext * getOptionsMutable * onFormOptionsChanged * settingsSaveOptions - * utilAnkiGetDeckNames - * utilAnkiGetModelFieldNames - * utilAnkiGetModelNames * utilBackgroundIsolate */ @@ -107,7 +107,7 @@ async function _ankiDeckAndModelPopulate(options) { const kanjiModel = {value: options.anki.kanji.model, selector: '#anki-kanji-model'}; try { _ankiSpinnerShow(true); - const [deckNames, modelNames] = await Promise.all([utilAnkiGetDeckNames(), utilAnkiGetModelNames()]); + const [deckNames, modelNames] = await Promise.all([apiGetAnkiDeckNames(), apiGetAnkiModelNames()]); deckNames.sort(); modelNames.sort(); termsDeck.values = deckNames; @@ -180,7 +180,7 @@ async function _onAnkiModelChanged(e) { let fieldNames; try { const modelName = node.value; - fieldNames = await utilAnkiGetModelFieldNames(modelName); + fieldNames = await apiGetAnkiModelFieldNames(modelName); _ankiSetError(null); } catch (error) { _ankiSetError(error); diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 1a6d452b..7eed4273 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -17,8 +17,11 @@ /* global * PageExitPrevention + * apiGetDictionaryCounts + * apiGetDictionaryInfo * apiOptionsGet * apiOptionsGetFull + * apiPurgeDatabase * getOptionsContext * getOptionsFullMutable * getOptionsMutable @@ -27,10 +30,7 @@ * storageUpdateStats * utilBackgroundIsolate * utilDatabaseDeleteDictionary - * utilDatabaseGetDictionaryCounts - * utilDatabaseGetDictionaryInfo * utilDatabaseImport - * utilDatabasePurge */ let dictionaryUI = null; @@ -431,7 +431,7 @@ async function onDictionaryOptionsChanged() { async function onDatabaseUpdated() { try { - const dictionaries = await utilDatabaseGetDictionaryInfo(); + const dictionaries = await apiGetDictionaryInfo(); dictionaryUI.setDictionaries(dictionaries); document.querySelector('#dict-warning').hidden = (dictionaries.length > 0); @@ -439,7 +439,7 @@ async function onDatabaseUpdated() { updateMainDictionarySelectOptions(dictionaries); await updateMainDictionarySelectValue(); - const {counts, total} = await utilDatabaseGetDictionaryCounts(dictionaries.map((v) => v.title), true); + const {counts, total} = await apiGetDictionaryCounts(dictionaries.map((v) => v.title), true); dictionaryUI.setCounts(counts, total); } catch (e) { dictionaryErrorsShow([e]); @@ -618,7 +618,7 @@ async function onDictionaryPurge(e) { dictionaryErrorsShow(null); dictionarySpinnerShow(true); - await utilDatabasePurge(); + await apiPurgeDatabase(); for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) { options.dictionaries = utilBackgroundIsolate({}); options.general.mainDictionary = ''; diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 69536f02..106365ac 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -79,35 +79,6 @@ function utilBackend() { return backend; } -async function utilAnkiGetModelNames() { - return utilIsolate(await utilBackend().anki.getModelNames()); -} - -async function utilAnkiGetDeckNames() { - return utilIsolate(await utilBackend().anki.getDeckNames()); -} - -async function utilDatabaseGetDictionaryInfo() { - return utilIsolate(await utilBackend().translator.database.getDictionaryInfo()); -} - -async function utilDatabaseGetDictionaryCounts(dictionaryNames, getTotal) { - return utilIsolate(await utilBackend().translator.database.getDictionaryCounts( - utilBackgroundIsolate(dictionaryNames), - utilBackgroundIsolate(getTotal) - )); -} - -async function utilAnkiGetModelFieldNames(modelName) { - return utilIsolate(await utilBackend().anki.getModelFieldNames( - utilBackgroundIsolate(modelName) - )); -} - -async function utilDatabasePurge() { - return utilIsolate(await utilBackend().translator.purgeDatabase()); -} - async function utilDatabaseDeleteDictionary(dictionaryName, onProgress) { return utilIsolate(await utilBackend().translator.database.deleteDictionary( utilBackgroundIsolate(dictionaryName), -- cgit v1.2.3 From cdd817a0e1c573b24114836a389236f49dd9b279 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 15:23:32 -0400 Subject: Move and rename utilStringHashCode options.js is the only place it's used. --- ext/bg/js/options.js | 23 ++++++++++++++++------- ext/bg/js/util.js | 13 ------------- 2 files changed, 16 insertions(+), 20 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 20df2a68..d36b2bab 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -15,14 +15,23 @@ * along with this program. If not, see . */ -/* global - * utilStringHashCode - */ - /* * Generic options functions */ +function optionsGetStringHashCode(string) { + let hashCode = 0; + + if (typeof string !== 'string') { return hashCode; } + + for (let i = 0, charCode = string.charCodeAt(i); i < string.length; charCode = string.charCodeAt(++i)) { + hashCode = ((hashCode << 5) - hashCode) + charCode; + hashCode |= 0; + } + + return hashCode; +} + function optionsGenericApplyUpdates(options, updates) { const targetVersion = updates.length; const currentVersion = options.version; @@ -63,12 +72,12 @@ const profileOptionsVersionUpdates = [ options.anki.fieldTemplates = null; }, (options) => { - if (utilStringHashCode(options.anki.fieldTemplates) === 1285806040) { + if (optionsGetStringHashCode(options.anki.fieldTemplates) === 1285806040) { options.anki.fieldTemplates = null; } }, (options) => { - if (utilStringHashCode(options.anki.fieldTemplates) === -250091611) { + if (optionsGetStringHashCode(options.anki.fieldTemplates) === -250091611) { options.anki.fieldTemplates = null; } }, @@ -87,7 +96,7 @@ const profileOptionsVersionUpdates = [ (options) => { // Version 12 changes: // The preferred default value of options.anki.fieldTemplates has been changed to null. - if (utilStringHashCode(options.anki.fieldTemplates) === 1444379824) { + if (optionsGetStringHashCode(options.anki.fieldTemplates) === 1444379824) { options.anki.fieldTemplates = null; } }, diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 106365ac..5edcc193 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -58,19 +58,6 @@ function utilBackgroundFunctionIsolate(func) { return backgroundPage.utilFunctionIsolate(func); } -function utilStringHashCode(string) { - let hashCode = 0; - - if (typeof string !== 'string') { return hashCode; } - - for (let i = 0, charCode = string.charCodeAt(i); i < string.length; charCode = string.charCodeAt(++i)) { - hashCode = ((hashCode << 5) - hashCode) + charCode; - hashCode |= 0; - } - - return hashCode; -} - function utilBackend() { const backend = chrome.extension.getBackgroundPage().yomichanBackend; if (!backend.isPrepared) { -- cgit v1.2.3 From ceb12ac41551aca11bc195e5fad9984a28a5e291 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 23:20:36 -0400 Subject: Add support for filtering frequency metadata based on readings --- .../data/dictionary-term-meta-bank-v3-schema.json | 26 ++++++++++++++++++++-- ext/bg/js/translator.js | 16 ++++++++++++- .../valid-dictionary1/term_meta_bank_1.json | 6 +++++ test/test-database.js | 16 ++++++------- 4 files changed, 53 insertions(+), 11 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/data/dictionary-term-meta-bank-v3-schema.json b/ext/bg/data/dictionary-term-meta-bank-v3-schema.json index 8475db81..ffffb546 100644 --- a/ext/bg/data/dictionary-term-meta-bank-v3-schema.json +++ b/ext/bg/data/dictionary-term-meta-bank-v3-schema.json @@ -26,8 +26,30 @@ {}, {"enum": ["freq"]}, { - "type": ["string", "number"], - "description": "Frequency information for the term or expression." + "oneOf": [ + { + "type": ["string", "number"], + "description": "Frequency information for the term or expression." + }, + { + "type": ["object"], + "required": [ + "reading", + "frequency" + ], + "additionalProperties": false, + "properties": { + "reading": { + "type": "string", + "description": "Reading for the term or expression." + }, + "frequency": { + "type": ["string", "number"], + "description": "Frequency information for the term or expression." + } + } + } + ] } ] }, diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index e4441384..b6f8b8e5 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -469,7 +469,9 @@ class Translator { switch (mode) { case 'freq': for (const term of termsUnique[index]) { - term.frequencies.push({expression, frequency: data, dictionary}); + const frequencyData = this.getFrequencyData(expression, data, dictionary, term); + if (frequencyData === null) { continue; } + term.frequencies.push(frequencyData); } break; case 'pitch': @@ -562,6 +564,18 @@ class Translator { return tagMetaList; } + getFrequencyData(expression, data, dictionary, term) { + if (data !== null && typeof data === 'object') { + const {frequency, reading} = data; + + const termReading = term.reading || expression; + if (reading !== termReading) { return null; } + + return {expression, frequency, dictionary}; + } + return {expression, frequency: data, dictionary}; + } + async getPitchData(expression, data, dictionary, term) { const reading = data.reading; const termReading = term.reading || expression; diff --git a/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json index 26922394..73d74e68 100644 --- a/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json +++ b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json @@ -2,6 +2,12 @@ ["打", "freq", 1], ["打つ", "freq", 2], ["打ち込む", "freq", 3], + ["打", "freq", {"reading": "だ", "frequency": 4}], + ["打", "freq", {"reading": "ダース", "frequency": 5}], + ["打つ", "freq", {"reading": "うつ", "frequency": 6}], + ["打つ", "freq", {"reading": "ぶつ", "frequency": 7}], + ["打ち込む", "freq", {"reading": "うちこむ", "frequency": 8}], + ["打ち込む", "freq", {"reading": "ぶちこむ", "frequency": 9}], [ "打ち込む", "pitch", diff --git a/test/test-database.js b/test/test-database.js index d27f92e1..8b7a163a 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -235,8 +235,8 @@ async function testDatabase1() { true ); vm.assert.deepStrictEqual(counts, { - counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 6, tagMeta: 14}], - total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 6, tagMeta: 14} + counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 12, tagMeta: 14}], + total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 12, tagMeta: 14} }); // Test find* functions @@ -626,9 +626,9 @@ async function testFindTermMetaBulk1(database, titles) { } ], expectedResults: { - total: 1, + total: 3, modes: [ - ['freq', 1] + ['freq', 3] ] } }, @@ -639,9 +639,9 @@ async function testFindTermMetaBulk1(database, titles) { } ], expectedResults: { - total: 1, + total: 3, modes: [ - ['freq', 1] + ['freq', 3] ] } }, @@ -652,9 +652,9 @@ async function testFindTermMetaBulk1(database, titles) { } ], expectedResults: { - total: 3, + total: 5, modes: [ - ['freq', 1], + ['freq', 3], ['pitch', 2] ] } -- cgit v1.2.3 From ade1b705d2370be9222ba4164f79bbdfae590bc1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 12 Apr 2020 12:20:02 -0400 Subject: Mark internals as private --- ext/bg/js/anki.js | 70 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 34 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index c7f7c0cc..021cb4c4 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -25,82 +25,84 @@ class AnkiConnect { constructor(server) { - this.server = server; - this.localVersion = 2; - this.remoteVersion = 0; + this._server = server; + this._localVersion = 2; + this._remoteVersion = 0; } async addNote(note) { - await this.checkVersion(); - return await this.ankiInvoke('addNote', {note}); + await this._checkVersion(); + return await this._ankiInvoke('addNote', {note}); } async canAddNotes(notes) { - await this.checkVersion(); - return await this.ankiInvoke('canAddNotes', {notes}); + await this._checkVersion(); + return await this._ankiInvoke('canAddNotes', {notes}); } async getDeckNames() { - await this.checkVersion(); - return await this.ankiInvoke('deckNames'); + await this._checkVersion(); + return await this._ankiInvoke('deckNames'); } async getModelNames() { - await this.checkVersion(); - return await this.ankiInvoke('modelNames'); + await this._checkVersion(); + return await this._ankiInvoke('modelNames'); } async getModelFieldNames(modelName) { - await this.checkVersion(); - return await this.ankiInvoke('modelFieldNames', {modelName}); + await this._checkVersion(); + return await this._ankiInvoke('modelFieldNames', {modelName}); } async guiBrowse(query) { - await this.checkVersion(); - return await this.ankiInvoke('guiBrowse', {query}); + await this._checkVersion(); + return await this._ankiInvoke('guiBrowse', {query}); } async storeMediaFile(filename, dataBase64) { - await this.checkVersion(); - return await this.ankiInvoke('storeMediaFile', {filename, data: dataBase64}); - } - - async checkVersion() { - if (this.remoteVersion < this.localVersion) { - this.remoteVersion = await this.ankiInvoke('version'); - if (this.remoteVersion < this.localVersion) { - throw new Error('Extension and plugin versions incompatible'); - } - } + await this._checkVersion(); + return await this._ankiInvoke('storeMediaFile', {filename, data: dataBase64}); } async findNoteIds(notes) { - await this.checkVersion(); + await this._checkVersion(); const actions = notes.map((note) => ({ action: 'findNotes', params: { - query: `deck:"${AnkiConnect.escapeQuery(note.deckName)}" ${AnkiConnect.fieldsToQuery(note.fields)}` + query: `deck:"${this._escapeQuery(note.deckName)}" ${this._fieldsToQuery(note.fields)}` } })); - return await this.ankiInvoke('multi', {actions}); + return await this._ankiInvoke('multi', {actions}); + } + + // Private + + async _checkVersion() { + if (this._remoteVersion < this._localVersion) { + this._remoteVersion = await this._ankiInvoke('version'); + if (this._remoteVersion < this._localVersion) { + throw new Error('Extension and plugin versions incompatible'); + } + } } - ankiInvoke(action, params) { - return requestJson(this.server, 'POST', {action, params, version: this.localVersion}); + _ankiInvoke(action, params) { + return requestJson(this._server, 'POST', {action, params, version: this._localVersion}); } - static escapeQuery(text) { + _escapeQuery(text) { return text.replace(/"/g, ''); } - static fieldsToQuery(fields) { + _fieldsToQuery(fields) { const fieldNames = Object.keys(fields); if (fieldNames.length === 0) { return ''; } const key = fieldNames[0]; - return `${key.toLowerCase()}:"${AnkiConnect.escapeQuery(fields[key])}"`; + return `${key.toLowerCase()}:"${this._escapeQuery(fields[key])}"`; } } -- cgit v1.2.3 From c41c7252aeb5f10fca7403a19740d869743a38a5 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 12 Apr 2020 12:37:13 -0400 Subject: Add enabled checks --- ext/bg/js/anki.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index 021cb4c4..f7a24291 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -25,47 +25,74 @@ class AnkiConnect { constructor(server) { + this._enabled = true; this._server = server; this._localVersion = 2; this._remoteVersion = 0; } + setServer(server) { + this._server = server; + } + + getServer() { + return this._server; + } + + setEnabled(enabled) { + this._enabled = enabled; + } + + isEnabled() { + return this._enabled; + } + async addNote(note) { + if (!this._enabled) { return null; } await this._checkVersion(); return await this._ankiInvoke('addNote', {note}); } async canAddNotes(notes) { + if (!this._enabled) { return []; } await this._checkVersion(); return await this._ankiInvoke('canAddNotes', {notes}); } async getDeckNames() { + if (!this._enabled) { return []; } await this._checkVersion(); return await this._ankiInvoke('deckNames'); } async getModelNames() { + if (!this._enabled) { return []; } await this._checkVersion(); return await this._ankiInvoke('modelNames'); } async getModelFieldNames(modelName) { + if (!this._enabled) { return []; } await this._checkVersion(); return await this._ankiInvoke('modelFieldNames', {modelName}); } async guiBrowse(query) { + if (!this._enabled) { return []; } await this._checkVersion(); return await this._ankiInvoke('guiBrowse', {query}); } async storeMediaFile(filename, dataBase64) { + if (!this._enabled) { + return {result: null, error: 'AnkiConnect not enabled'}; + } await this._checkVersion(); return await this._ankiInvoke('storeMediaFile', {filename, data: dataBase64}); } async findNoteIds(notes) { + if (!this._enabled) { return []; } await this._checkVersion(); const actions = notes.map((note) => ({ action: 'findNotes', -- cgit v1.2.3 From 4c2ca82a2937fa4e1d0f3f6744f4e1a7b88692a1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 12 Apr 2020 12:38:33 -0400 Subject: Use single instance of AnkiConnect --- ext/bg/js/anki.js | 2 +- ext/bg/js/backend.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index f7a24291..dd802424 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -25,7 +25,7 @@ class AnkiConnect { constructor(server) { - this._enabled = true; + this._enabled = false; this._server = server; this._localVersion = 2; this._remoteVersion = 0; diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index be8ea322..24a16199 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -47,7 +47,7 @@ class Backend { this.database = new Database(); this.dictionaryImporter = new DictionaryImporter(); this.translator = new Translator(this.database); - this.anki = new AnkiNull(); + this.anki = new AnkiConnect(); this.mecab = new Mecab(); this.clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)}); this.options = null; @@ -210,7 +210,8 @@ class Backend { this.setExtensionBadgeText(''); } - this.anki = options.anki.enable ? new AnkiConnect(options.anki.server) : new AnkiNull(); + this.anki.setServer(options.anki.server); + this.anki.setEnabled(options.anki.enable); if (options.parsing.enableMecabParser) { this.mecab.startListener(); -- cgit v1.2.3 From cc5e4294223f9d0106ddec1d561b29ac449b1115 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 12 Apr 2020 12:38:52 -0400 Subject: Remove AnkiNull and redundant comment --- ext/bg/js/anki.js | 39 --------------------------------------- 1 file changed, 39 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index dd802424..27590311 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -19,10 +19,6 @@ * requestJson */ -/* - * AnkiConnect - */ - class AnkiConnect { constructor(server) { this._enabled = false; @@ -132,38 +128,3 @@ class AnkiConnect { return `${key.toLowerCase()}:"${this._escapeQuery(fields[key])}"`; } } - - -/* - * AnkiNull - */ - -class AnkiNull { - async addNote() { - return null; - } - - async canAddNotes() { - return []; - } - - async getDeckNames() { - return []; - } - - async getModelNames() { - return []; - } - - async getModelFieldNames() { - return []; - } - - async guiBrowse() { - return []; - } - - async findNoteIds() { - return []; - } -} -- cgit v1.2.3 From 3c335e68cdd41860d791ffe85dd07abf8932d3ce Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 12 Apr 2020 12:43:24 -0400 Subject: Throw errors in returned by invocation --- ext/bg/js/anki.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index 27590311..c07af462 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -81,7 +81,7 @@ class AnkiConnect { async storeMediaFile(filename, dataBase64) { if (!this._enabled) { - return {result: null, error: 'AnkiConnect not enabled'}; + throw new Error('AnkiConnect not enabled'); } await this._checkVersion(); return await this._ankiInvoke('storeMediaFile', {filename, data: dataBase64}); @@ -110,8 +110,19 @@ class AnkiConnect { } } - _ankiInvoke(action, params) { - return requestJson(this._server, 'POST', {action, params, version: this._localVersion}); + async _ankiInvoke(action, params) { + const result = await requestJson(this._server, 'POST', {action, params, version: this._localVersion}); + if ( + result !== null && + typeof result === 'object' && + !Array.isArray(result) + ) { + const error = result.error; + if (typeof error !== 'undefined') { + throw new Error(`AnkiConnect error: ${error}`); + } + } + return result; } _escapeQuery(text) { -- cgit v1.2.3 From 37c374fb633a5b2f224348a8e5490f0275d348e7 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 12 Apr 2020 12:44:33 -0400 Subject: Rename _ankiInvoke to _invoke to remove redundancy --- ext/bg/js/anki.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index c07af462..928b5159 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -46,37 +46,37 @@ class AnkiConnect { async addNote(note) { if (!this._enabled) { return null; } await this._checkVersion(); - return await this._ankiInvoke('addNote', {note}); + return await this._invoke('addNote', {note}); } async canAddNotes(notes) { if (!this._enabled) { return []; } await this._checkVersion(); - return await this._ankiInvoke('canAddNotes', {notes}); + return await this._invoke('canAddNotes', {notes}); } async getDeckNames() { if (!this._enabled) { return []; } await this._checkVersion(); - return await this._ankiInvoke('deckNames'); + return await this._invoke('deckNames'); } async getModelNames() { if (!this._enabled) { return []; } await this._checkVersion(); - return await this._ankiInvoke('modelNames'); + return await this._invoke('modelNames'); } async getModelFieldNames(modelName) { if (!this._enabled) { return []; } await this._checkVersion(); - return await this._ankiInvoke('modelFieldNames', {modelName}); + return await this._invoke('modelFieldNames', {modelName}); } async guiBrowse(query) { if (!this._enabled) { return []; } await this._checkVersion(); - return await this._ankiInvoke('guiBrowse', {query}); + return await this._invoke('guiBrowse', {query}); } async storeMediaFile(filename, dataBase64) { @@ -84,7 +84,7 @@ class AnkiConnect { throw new Error('AnkiConnect not enabled'); } await this._checkVersion(); - return await this._ankiInvoke('storeMediaFile', {filename, data: dataBase64}); + return await this._invoke('storeMediaFile', {filename, data: dataBase64}); } async findNoteIds(notes) { @@ -96,21 +96,21 @@ class AnkiConnect { query: `deck:"${this._escapeQuery(note.deckName)}" ${this._fieldsToQuery(note.fields)}` } })); - return await this._ankiInvoke('multi', {actions}); + return await this._invoke('multi', {actions}); } // Private async _checkVersion() { if (this._remoteVersion < this._localVersion) { - this._remoteVersion = await this._ankiInvoke('version'); + this._remoteVersion = await this._invoke('version'); if (this._remoteVersion < this._localVersion) { throw new Error('Extension and plugin versions incompatible'); } } } - async _ankiInvoke(action, params) { + async _invoke(action, params) { const result = await requestJson(this._server, 'POST', {action, params, version: this._localVersion}); if ( result !== null && -- cgit v1.2.3 From ebfc7ca945109c6700b3dbf6d45542ddbba94f3d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 12 Apr 2020 12:46:32 -0400 Subject: Pass anki reference to AnkiNoteBuilder constructor --- ext/bg/js/anki-note-builder.js | 7 ++++--- ext/bg/js/backend.js | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index 8a707006..700d8237 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -16,7 +16,8 @@ */ class AnkiNoteBuilder { - constructor({audioSystem, renderTemplate}) { + constructor({anki, audioSystem, renderTemplate}) { + this._anki = anki; this._audioSystem = audioSystem; this._renderTemplate = renderTemplate; } @@ -101,7 +102,7 @@ class AnkiNoteBuilder { } } - async injectScreenshot(definition, fields, screenshot, anki) { + async injectScreenshot(definition, fields, screenshot) { if (!this._containsMarker(fields, 'screenshot')) { return; } const now = new Date(Date.now()); @@ -109,7 +110,7 @@ class AnkiNoteBuilder { const data = screenshot.dataUrl.replace(/^data:[\w\W]*?,/, ''); try { - await anki.storeMediaFile(filename, data); + await this._anki.storeMediaFile(filename, data); } catch (e) { return; } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 24a16199..6a6819e9 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -56,6 +56,7 @@ class Backend { this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); this.audioUriBuilder = new AudioUriBuilder(); this.ankiNoteBuilder = new AnkiNoteBuilder({ + anki: this.anki, audioSystem: this.audioSystem, renderTemplate: this._renderTemplate.bind(this) }); @@ -482,8 +483,7 @@ class Backend { await this.ankiNoteBuilder.injectScreenshot( definition, options.anki.terms.fields, - details.screenshot, - this.anki + details.screenshot ); } -- cgit v1.2.3 From 018913d03fff627fb7d34f594340c47a607e8839 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 17 Apr 2020 19:25:07 -0400 Subject: Use isObject --- ext/bg/js/anki.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index 928b5159..38823431 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -112,11 +112,7 @@ class AnkiConnect { async _invoke(action, params) { const result = await requestJson(this._server, 'POST', {action, params, version: this._localVersion}); - if ( - result !== null && - typeof result === 'object' && - !Array.isArray(result) - ) { + if (isObject(result)) { const error = result.error; if (typeof error !== 'undefined') { throw new Error(`AnkiConnect error: ${error}`); -- cgit v1.2.3 From 06e95b8747e7222d3aa513cda28b0878a11921d9 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 17 Apr 2020 22:16:08 -0400 Subject: Remove unused global --- ext/bg/js/backend.js | 1 - 1 file changed, 1 deletion(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 6a6819e9..1b922730 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -18,7 +18,6 @@ /* global * AnkiConnect * AnkiNoteBuilder - * AnkiNull * AudioSystem * AudioUriBuilder * BackendApiForwarder -- cgit v1.2.3 From 7fc3882607f48bb9371649ceacddf2fe278282d2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 10 Apr 2020 13:44:31 -0400 Subject: Update the parameters passed to various audio-related functions --- ext/bg/js/anki-note-builder.js | 4 ++-- ext/bg/js/audio-uri-builder.js | 26 ++++++++++++-------------- ext/bg/js/backend.js | 22 +++++++--------------- ext/bg/js/settings/audio.js | 7 +------ ext/mixed/js/api.js | 4 ++-- ext/mixed/js/audio-system.js | 21 ++++++++++++--------- ext/mixed/js/display.js | 17 +++++++++-------- 7 files changed, 45 insertions(+), 56 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index 700d8237..9bab095d 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -85,14 +85,14 @@ class AnkiNoteBuilder { }); } - async injectAudio(definition, fields, sources, optionsContext) { + async injectAudio(definition, fields, sources, details) { if (!this._containsMarker(fields, 'audio')) { return; } try { const expressions = definition.expressions; const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; - const {uri} = await this._audioSystem.getDefinitionAudio(audioSourceDefinition, sources, {tts: false, optionsContext}); + const {uri} = await this._audioSystem.getDefinitionAudio(audioSourceDefinition, sources, details); const filename = this._createInjectedAudioFileName(audioSourceDefinition); if (filename !== null) { definition.audio = {url: uri, filename}; diff --git a/ext/bg/js/audio-uri-builder.js b/ext/bg/js/audio-uri-builder.js index dfd195d8..27e97680 100644 --- a/ext/bg/js/audio-uri-builder.js +++ b/ext/bg/js/audio-uri-builder.js @@ -49,11 +49,11 @@ class AudioUriBuilder { return url; } - async getUri(definition, source, options) { + async getUri(definition, source, details) { const handler = this._getUrlHandlers.get(source); if (typeof handler === 'function') { try { - return await handler(definition, options); + return await handler(definition, details); } catch (e) { // NOP } @@ -132,26 +132,24 @@ class AudioUriBuilder { throw new Error('Failed to find audio URL'); } - async _getUriTextToSpeech(definition, options) { - const voiceURI = options.audio.textToSpeechVoice; - if (!voiceURI) { + async _getUriTextToSpeech(definition, {textToSpeechVoice}) { + if (!textToSpeechVoice) { throw new Error('No voice'); } - - return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; + return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(textToSpeechVoice)}`; } - async _getUriTextToSpeechReading(definition, options) { - const voiceURI = options.audio.textToSpeechVoice; - if (!voiceURI) { + async _getUriTextToSpeechReading(definition, {textToSpeechVoice}) { + if (!textToSpeechVoice) { throw new Error('No voice'); } - - return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; + return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(textToSpeechVoice)}`; } - async _getUriCustom(definition, options) { - const customSourceUrl = options.audio.customSourceUrl; + async _getUriCustom(definition, {customSourceUrl}) { + if (typeof customSourceUrl !== 'string') { + throw new Error('No custom URL defined'); + } return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0)); } } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index a1b788df..79402e67 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -51,8 +51,10 @@ class Backend { this.options = null; this.optionsSchema = null; this.defaultAnkiFieldTemplates = null; - this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); this.audioUriBuilder = new AudioUriBuilder(); + this.audioSystem = new AudioSystem({ + audioUriBuilder: this.audioUriBuilder + }); this.ankiNoteBuilder = new AnkiNoteBuilder({ anki: this.anki, audioSystem: this.audioSystem, @@ -494,11 +496,12 @@ class Backend { const templates = this.defaultAnkiFieldTemplates; if (mode !== 'kanji') { + const {customSourceUrl} = options.audio; await this.ankiNoteBuilder.injectAudio( definition, options.anki.terms.fields, options.audio.sources, - optionsContext + {textToSpeechVoice: null, customSourceUrl} ); } @@ -573,9 +576,8 @@ class Backend { return this._runCommand(command, params); } - async _onApiAudioGetUri({definition, source, optionsContext}) { - const options = this.getOptions(optionsContext); - return await this.audioUriBuilder.getUri(definition, source, options); + async _onApiAudioGetUri({definition, source, details}) { + return await this.audioUriBuilder.getUri(definition, source, details); } _onApiScreenshotGet({options}, sender) { @@ -861,16 +863,6 @@ class Backend { } } - async _getAudioUri(definition, source, details) { - let optionsContext = (typeof details === 'object' && details !== null ? details.optionsContext : null); - if (!(typeof optionsContext === 'object' && optionsContext !== null)) { - optionsContext = this.optionsContext; - } - - const options = this.getOptions(optionsContext); - return await this.audioUriBuilder.getUri(definition, source, options); - } - async _renderTemplate(template, data) { return handlebarsRenderDynamic(template, data); } diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 3c6e126c..e9aa72e1 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -28,12 +28,7 @@ let audioSourceUI = null; let audioSystem = null; async function audioSettingsInitialize() { - audioSystem = new AudioSystem({ - getAudioUri: async (definition, source) => { - const optionsContext = getOptionsContext(); - return await apiAudioGetUri(definition, source, optionsContext); - } - }); + audioSystem = new AudioSystem({audioUriBuilder: null}); const optionsContext = getOptionsContext(); const options = await getOptionsMutable(optionsContext); diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 7080d93a..c97dc687 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -64,8 +64,8 @@ function apiTemplateRender(template, data) { return _apiInvoke('templateRender', {data, template}); } -function apiAudioGetUri(definition, source, optionsContext) { - return _apiInvoke('audioGetUri', {definition, source, optionsContext}); +function apiAudioGetUri(definition, source, details) { + return _apiInvoke('audioGetUri', {definition, source, details}); } function apiCommandExec(command, params) { diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 45b733fc..574ad3dc 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -66,10 +66,10 @@ class TextToSpeechAudio { } class AudioSystem { - constructor({getAudioUri}) { + constructor({audioUriBuilder}) { this._cache = new Map(); this._cacheSizeMaximum = 32; - this._getAudioUri = getAudioUri; + this._audioUriBuilder = audioUriBuilder; if (typeof speechSynthesis !== 'undefined') { // speechSynthesis.getVoices() will not be populated unless some API call is made. @@ -90,7 +90,7 @@ class AudioSystem { if (uri === null) { continue; } try { - const audio = await this._createAudio(uri, details); + const audio = await this._createAudio(uri); this._cacheCheck(); this._cache.set(key, {audio, uri, source}); return {audio, uri, source}; @@ -114,20 +114,23 @@ class AudioSystem { // NOP } - async _createAudio(uri, details) { + async _createAudio(uri) { const ttsParameters = this._getTextToSpeechParameters(uri); if (ttsParameters !== null) { - if (typeof details === 'object' && details !== null) { - if (details.tts === false) { - throw new Error('Text-to-speech not permitted'); - } - } return this.createTextToSpeechAudio(ttsParameters); } return await this._createAudioFromUrl(uri); } + _getAudioUri(definition, source, details) { + return ( + this._audioUriBuilder !== null ? + this._audioUriBuilder.getUri(definition, source, details) : + null + ); + } + _createAudioFromUrl(url) { return new Promise((resolve, reject) => { const audio = new Audio(url); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 63687dc2..7f3ba859 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -45,7 +45,13 @@ class Display { this.index = 0; this.audioPlaying = null; this.audioFallback = null; - this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); + this.audioSystem = new AudioSystem({ + audioUriBuilder: { + async getUri(definition, source, details) { + return await apiAudioGetUri(definition, source, details); + } + } + }); this.styleNode = null; this.eventListeners = new EventListenerCollection(); @@ -789,10 +795,10 @@ class Display { this.audioPlaying = null; } - const sources = this.options.audio.sources; let audio, source, info; try { - ({audio, source} = await this.audioSystem.getDefinitionAudio(expression, sources)); + const {sources, textToSpeechVoice, customSourceUrl} = this.options.audio; + ({audio, source} = await this.audioSystem.getDefinitionAudio(expression, sources, {textToSpeechVoice, customSourceUrl})); info = `From source ${1 + sources.indexOf(source)}: ${source}`; } catch (e) { if (this.audioFallback === null) { @@ -947,9 +953,4 @@ class Display { } }; } - - async _getAudioUri(definition, source) { - const optionsContext = this.getOptionsContext(); - return await apiAudioGetUri(definition, source, optionsContext); - } } -- cgit v1.2.3 From 823c026533dcd758c2a93038fa526978a5fa9cc3 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 10 Apr 2020 13:51:47 -0400 Subject: Remove de/structuring from public API --- ext/bg/js/settings/audio.js | 2 +- ext/mixed/js/audio-system.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index e9aa72e1..68dfe71e 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -110,7 +110,7 @@ function textToSpeechTest() { const text = document.querySelector('#text-to-speech-voice-test').dataset.speechText || ''; const voiceUri = document.querySelector('#text-to-speech-voice').value; - const audio = audioSystem.createTextToSpeechAudio({text, voiceUri}); + const audio = audioSystem.createTextToSpeechAudio(text, voiceUri); audio.volume = 1.0; audio.play(); } catch (e) { diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 574ad3dc..5366e3e0 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -102,7 +102,7 @@ class AudioSystem { throw new Error('Could not create audio'); } - createTextToSpeechAudio({text, voiceUri}) { + createTextToSpeechAudio(text, voiceUri) { const voice = this._getTextToSpeechVoiceFromVoiceUri(voiceUri); if (voice === null) { throw new Error('Invalid text-to-speech voice'); @@ -117,7 +117,8 @@ class AudioSystem { async _createAudio(uri) { const ttsParameters = this._getTextToSpeechParameters(uri); if (ttsParameters !== null) { - return this.createTextToSpeechAudio(ttsParameters); + const {text, voiceUri} = ttsParameters; + return this.createTextToSpeechAudio(text, voiceUri); } return await this._createAudioFromUrl(uri); -- cgit v1.2.3 From e1ebfb02f724518432b2e1c5ec2a80ff03b38fd8 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 10 Apr 2020 16:12:55 -0400 Subject: Disable cache on the backend and fix a bug with the cache key --- ext/bg/js/backend.js | 3 ++- ext/bg/js/settings/audio.js | 5 ++++- ext/mixed/js/audio-system.js | 22 ++++++++++++++-------- ext/mixed/js/display.js | 3 ++- 4 files changed, 22 insertions(+), 11 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 79402e67..9d1fa6c1 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -53,7 +53,8 @@ class Backend { this.defaultAnkiFieldTemplates = null; this.audioUriBuilder = new AudioUriBuilder(); this.audioSystem = new AudioSystem({ - audioUriBuilder: this.audioUriBuilder + audioUriBuilder: this.audioUriBuilder, + useCache: false }); this.ankiNoteBuilder = new AnkiNoteBuilder({ anki: this.anki, diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 68dfe71e..98ed9b8b 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -28,7 +28,10 @@ let audioSourceUI = null; let audioSystem = null; async function audioSettingsInitialize() { - audioSystem = new AudioSystem({audioUriBuilder: null}); + audioSystem = new AudioSystem({ + audioUriBuilder: null, + useCache: true + }); const optionsContext = getOptionsContext(); const options = await getOptionsMutable(optionsContext); diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 5366e3e0..255a96de 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -66,8 +66,8 @@ class TextToSpeechAudio { } class AudioSystem { - constructor({audioUriBuilder}) { - this._cache = new Map(); + constructor({audioUriBuilder, useCache}) { + this._cache = useCache ? new Map() : null; this._cacheSizeMaximum = 32; this._audioUriBuilder = audioUriBuilder; @@ -79,10 +79,14 @@ class AudioSystem { async getDefinitionAudio(definition, sources, details) { const key = `${definition.expression}:${definition.reading}`; - const cacheValue = this._cache.get(definition); - if (typeof cacheValue !== 'undefined') { - const {audio, uri, source} = cacheValue; - return {audio, uri, source}; + const hasCache = (this._cache !== null); + + if (hasCache) { + const cacheValue = this._cache.get(key); + if (typeof cacheValue !== 'undefined') { + const {audio, uri, source} = cacheValue; + return {audio, uri, source}; + } } for (const source of sources) { @@ -91,8 +95,10 @@ class AudioSystem { try { const audio = await this._createAudio(uri); - this._cacheCheck(); - this._cache.set(key, {audio, uri, source}); + if (hasCache) { + this._cacheCheck(); + this._cache.set(key, {audio, uri, source}); + } return {audio, uri, source}; } catch (e) { // NOP diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 7f3ba859..8edae7c9 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -50,7 +50,8 @@ class Display { async getUri(definition, source, details) { return await apiAudioGetUri(definition, source, details); } - } + }, + useCache: true }); this.styleNode = null; -- cgit v1.2.3 From 9fe7b9ad29958b148162bfc2d065a7e32a986291 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 14 Apr 2020 18:26:24 -0400 Subject: Remove unused global --- ext/bg/js/settings/audio.js | 1 - 1 file changed, 1 deletion(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 98ed9b8b..ac2d82f3 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -18,7 +18,6 @@ /* global * AudioSourceUI * AudioSystem - * apiAudioGetUri * getOptionsContext * getOptionsMutable * settingsSaveOptions -- cgit v1.2.3 From 6498556ec7ddd3e0896ef47cce297bcbf938defb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 20:45:23 -0400 Subject: Update isPrepared to be consistent with DisplaySearch's isPrepared --- ext/bg/js/backend.js | 12 ++++++++---- ext/bg/js/util.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 9d1fa6c1..f5bd36f5 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -67,8 +67,6 @@ class Backend { url: window.location.href }; - this.isPrepared = false; - this.clipboardPasteTarget = document.querySelector('#clipboard-paste-target'); this.popupWindow = null; @@ -77,6 +75,8 @@ class Backend { this.messageToken = yomichan.generateId(16); + this._isPrepared = false; + this._messageHandlers = new Map([ ['yomichanCoreReady', {handler: this._onApiYomichanCoreReady.bind(this), async: false}], ['optionsSchemaGet', {handler: this._onApiOptionsSchemaGet.bind(this), async: false}], @@ -144,8 +144,6 @@ class Backend { } chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); - this.isPrepared = true; - const options = this.getOptions(this.optionsContext); if (options.general.showGuide) { chrome.tabs.create({url: chrome.runtime.getURL('/bg/guide.html')}); @@ -156,6 +154,12 @@ class Backend { this._sendMessageAllTabs('backendPrepared'); const callback = () => this.checkLastError(chrome.runtime.lastError); chrome.runtime.sendMessage({action: 'backendPrepared'}, callback); + + this._isPrepared = true; + } + + isPrepared() { + return this._isPrepared; } _sendMessageAllTabs(action, params={}) { diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 5edcc193..d2fb0e49 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -60,7 +60,7 @@ function utilBackgroundFunctionIsolate(func) { function utilBackend() { const backend = chrome.extension.getBackgroundPage().yomichanBackend; - if (!backend.isPrepared) { + if (!backend.isPrepared()) { throw new Error('Backend not ready yet'); } return backend; -- cgit v1.2.3 From c9704b5c5e3b8f78888adaa2dcd4fa54f28c9db1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 20:46:11 -0400 Subject: Update when/how badge state is changed --- ext/bg/js/backend.js | 96 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 21 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index f5bd36f5..1589524b 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -75,6 +75,7 @@ class Backend { this.messageToken = yomichan.generateId(16); + this._defaultBrowserActionTitle = null; this._isPrepared = false; this._messageHandlers = new Map([ @@ -121,6 +122,8 @@ class Backend { } async prepare() { + this._defaultBrowserActionTitle = await this._getBrowserIconTitle(); + this._updateBadge(); await this.database.prepare(); await this.translator.prepare(); @@ -156,6 +159,7 @@ class Backend { chrome.runtime.sendMessage({action: 'backendPrepared'}, callback); this._isPrepared = true; + this._updateBadge(); } isPrepared() { @@ -211,15 +215,7 @@ class Backend { applyOptions() { const options = this.getOptions(this.optionsContext); - if (!options.general.enable) { - this.setExtensionBadgeBackgroundColor('#555555'); - this.setExtensionBadgeText('off'); - } else if (!dictConfigured(options)) { - this.setExtensionBadgeBackgroundColor('#f0ad4e'); - this.setExtensionBadgeText('!'); - } else { - this.setExtensionBadgeText(''); - } + this._updateBadge(); this.anki.setServer(options.anki.server); this.anki.setEnabled(options.anki.enable); @@ -299,18 +295,6 @@ class Backend { return true; } - setExtensionBadgeBackgroundColor(color) { - if (typeof chrome.browserAction.setBadgeBackgroundColor === 'function') { - chrome.browserAction.setBadgeBackgroundColor({color}); - } - } - - setExtensionBadgeText(text) { - if (typeof chrome.browserAction.setBadgeText === 'function') { - chrome.browserAction.setBadgeText({text}); - } - } - checkLastError() { // NOP } @@ -868,6 +852,76 @@ class Backend { } } + _getBrowserIconTitle() { + return ( + chrome.browserAction !== null && + typeof chrome.browserAction === 'object' && + typeof chrome.browserAction.getTitle === 'function' ? + new Promise((resolve) => chrome.browserAction.getTitle({}, resolve)) : + Promise.resolve('') + ); + } + + _updateBadge() { + let title = this._defaultBrowserActionTitle; + if ( + title === null || + chrome.browserAction === null || + typeof chrome.browserAction !== 'object' + ) { + // Not ready or invalid + return; + } + + let text = ''; + let color = null; + let status = null; + + if (!this._isPrepared) { + text = '...'; + color = '#f0ad4e'; + status = 'Loading'; + } else if (!this._anyOptionsMatches((options) => options.general.enable)) { + text = 'off'; + color = '#555555'; + status = 'Disabled'; + } else if (!this._anyOptionsMatches((options) => this._isAnyDictionaryEnabled(options))) { + text = '!'; + color = '#f0ad4e'; + status = 'No dictionaries installed'; + } + + if (color !== null && typeof chrome.browserAction.setBadgeBackgroundColor === 'function') { + chrome.browserAction.setBadgeBackgroundColor({color}); + } + if (text !== null && typeof chrome.browserAction.setBadgeText === 'function') { + chrome.browserAction.setBadgeText({text}); + } + if (typeof chrome.browserAction.setTitle === 'function') { + if (status !== null) { + title = `${title} - ${status}`; + } + chrome.browserAction.setTitle({title}); + } + } + + _isAnyDictionaryEnabled(options) { + for (const {enabled} of Object.values(options.dictionaries)) { + if (enabled) { + return true; + } + } + return false; + } + + _anyOptionsMatches(predicate) { + for (const {options} of this.options.profiles) { + const value = predicate(options); + if (value) { return value; } + } + return false; + } + async _renderTemplate(template, data) { return handlebarsRenderDynamic(template, data); } -- cgit v1.2.3 From 5c5c70326d797b819cff33390e04dde93c63669c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 20:53:18 -0400 Subject: Add a delay before showing the loading state Intended to prevent flickering when startup is quick --- ext/bg/js/backend.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 1589524b..9cfa621a 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -77,6 +77,7 @@ class Backend { this._defaultBrowserActionTitle = null; this._isPrepared = false; + this._badgePrepareDelayTimer = null; this._messageHandlers = new Map([ ['yomichanCoreReady', {handler: this._onApiYomichanCoreReady.bind(this), async: false}], @@ -123,6 +124,10 @@ class Backend { async prepare() { this._defaultBrowserActionTitle = await this._getBrowserIconTitle(); + this._badgePrepareDelayTimer = setTimeout(() => { + this._badgePrepareDelayTimer = null; + this._updateBadge(); + }, 1000); this._updateBadge(); await this.database.prepare(); await this.translator.prepare(); @@ -158,6 +163,11 @@ class Backend { const callback = () => this.checkLastError(chrome.runtime.lastError); chrome.runtime.sendMessage({action: 'backendPrepared'}, callback); + if (this._badgePrepareDelayTimer !== null) { + clearTimeout(this._badgePrepareDelayTimer); + this._badgePrepareDelayTimer = null; + } + this._isPrepared = true; this._updateBadge(); } @@ -878,9 +888,11 @@ class Backend { let status = null; if (!this._isPrepared) { - text = '...'; - color = '#f0ad4e'; - status = 'Loading'; + if (this._badgePrepareDelayTimer === null) { + text = '...'; + color = '#f0ad4e'; + status = 'Loading'; + } } else if (!this._anyOptionsMatches((options) => options.general.enable)) { text = 'off'; color = '#555555'; -- cgit v1.2.3 From dee7d924a8555226721bbbdb045b86a21426f60b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 20:58:52 -0400 Subject: Show error status for prepare() errors --- ext/bg/js/backend.js | 84 ++++++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 38 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 9cfa621a..9f466647 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -77,6 +77,7 @@ class Backend { this._defaultBrowserActionTitle = null; this._isPrepared = false; + this._prepareError = false; this._badgePrepareDelayTimer = null; this._messageHandlers = new Map([ @@ -123,53 +124,56 @@ class Backend { } async prepare() { - this._defaultBrowserActionTitle = await this._getBrowserIconTitle(); - this._badgePrepareDelayTimer = setTimeout(() => { - this._badgePrepareDelayTimer = null; + try { + this._defaultBrowserActionTitle = await this._getBrowserIconTitle(); + this._badgePrepareDelayTimer = setTimeout(() => { + this._badgePrepareDelayTimer = null; + this._updateBadge(); + }, 1000); this._updateBadge(); - }, 1000); - this._updateBadge(); - await this.database.prepare(); - await this.translator.prepare(); - this.optionsSchema = await requestJson(chrome.runtime.getURL('/bg/data/options-schema.json'), 'GET'); - this.defaultAnkiFieldTemplates = await requestText(chrome.runtime.getURL('/bg/data/default-anki-field-templates.handlebars'), 'GET'); - this.options = await optionsLoad(); - try { + await this.database.prepare(); + await this.translator.prepare(); + + this.optionsSchema = await requestJson(chrome.runtime.getURL('/bg/data/options-schema.json'), 'GET'); + this.defaultAnkiFieldTemplates = await requestText(chrome.runtime.getURL('/bg/data/default-anki-field-templates.handlebars'), 'GET'); + this.options = await optionsLoad(); this.options = JsonSchema.getValidValueOrDefault(this.optionsSchema, this.options); - } catch (e) { - // This shouldn't happen, but catch errors just in case of bugs - logError(e); - } - this.onOptionsUpdated('background'); + this.onOptionsUpdated('background'); - if (isObject(chrome.commands) && isObject(chrome.commands.onCommand)) { - chrome.commands.onCommand.addListener(this._runCommand.bind(this)); - } - if (isObject(chrome.tabs) && isObject(chrome.tabs.onZoomChange)) { - chrome.tabs.onZoomChange.addListener(this._onZoomChange.bind(this)); - } - chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); + if (isObject(chrome.commands) && isObject(chrome.commands.onCommand)) { + chrome.commands.onCommand.addListener(this._runCommand.bind(this)); + } + if (isObject(chrome.tabs) && isObject(chrome.tabs.onZoomChange)) { + chrome.tabs.onZoomChange.addListener(this._onZoomChange.bind(this)); + } + chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); - const options = this.getOptions(this.optionsContext); - if (options.general.showGuide) { - chrome.tabs.create({url: chrome.runtime.getURL('/bg/guide.html')}); - } + const options = this.getOptions(this.optionsContext); + if (options.general.showGuide) { + chrome.tabs.create({url: chrome.runtime.getURL('/bg/guide.html')}); + } - this.clipboardMonitor.on('change', this._onClipboardText.bind(this)); + this.clipboardMonitor.on('change', this._onClipboardText.bind(this)); - this._sendMessageAllTabs('backendPrepared'); - const callback = () => this.checkLastError(chrome.runtime.lastError); - chrome.runtime.sendMessage({action: 'backendPrepared'}, callback); + this._sendMessageAllTabs('backendPrepared'); + const callback = () => this.checkLastError(chrome.runtime.lastError); + chrome.runtime.sendMessage({action: 'backendPrepared'}, callback); - if (this._badgePrepareDelayTimer !== null) { - clearTimeout(this._badgePrepareDelayTimer); - this._badgePrepareDelayTimer = null; - } + this._isPrepared = true; + } catch (e) { + this._prepareError = true; + logError(e); + throw e; + } finally { + if (this._badgePrepareDelayTimer !== null) { + clearTimeout(this._badgePrepareDelayTimer); + this._badgePrepareDelayTimer = null; + } - this._isPrepared = true; - this._updateBadge(); + this._updateBadge(); + } } isPrepared() { @@ -888,7 +892,11 @@ class Backend { let status = null; if (!this._isPrepared) { - if (this._badgePrepareDelayTimer === null) { + if (this._prepareError !== null) { + text = '!!'; + color = '#f04e4e'; + status = 'Error'; + } else if (this._badgePrepareDelayTimer === null) { text = '...'; color = '#f0ad4e'; status = 'Loading'; -- cgit v1.2.3 From 66ef7301198e4baa827fafe002818896a8bb6483 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 17 Apr 2020 19:19:38 -0400 Subject: Update style, use isObject --- ext/bg/js/backend.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 9f466647..aacebd2c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -868,21 +868,16 @@ class Backend { _getBrowserIconTitle() { return ( - chrome.browserAction !== null && - typeof chrome.browserAction === 'object' && + isObject(chrome.browserAction) && typeof chrome.browserAction.getTitle === 'function' ? - new Promise((resolve) => chrome.browserAction.getTitle({}, resolve)) : - Promise.resolve('') + new Promise((resolve) => chrome.browserAction.getTitle({}, resolve)) : + Promise.resolve('') ); } _updateBadge() { let title = this._defaultBrowserActionTitle; - if ( - title === null || - chrome.browserAction === null || - typeof chrome.browserAction !== 'object' - ) { + if (title === null || !isObject(chrome.browserAction)) { // Not ready or invalid return; } -- cgit v1.2.3 From 4638985b16951b1b1b3895ca0bf52575f1f0bd6b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 17 Apr 2020 22:16:51 -0400 Subject: Remove unused global --- ext/bg/js/backend.js | 1 - 1 file changed, 1 deletion(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index aacebd2c..8c0b531f 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -28,7 +28,6 @@ * Mecab * Translator * conditionsTestValue - * dictConfigured * dictTermsSort * handlebarsRenderDynamic * jp -- cgit v1.2.3 From 51d756eefc26402e4d6f9635acc77ba7f642d15b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 18 Apr 2020 21:15:15 -0400 Subject: Fix _prepareError check --- ext/bg/js/backend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 8c0b531f..e0814c17 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -886,7 +886,7 @@ class Backend { let status = null; if (!this._isPrepared) { - if (this._prepareError !== null) { + if (this._prepareError) { text = '!!'; color = '#f04e4e'; status = 'Error'; -- cgit v1.2.3 From 8106f4744b07833526d16acf656eda11d29b99ad Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 1 Mar 2020 22:36:42 -0500 Subject: Add support for importing and storing media files --- ext/bg/background.html | 1 + ext/bg/data/dictionary-term-bank-v3-schema.json | 81 +++++++++++++++++++++- ext/bg/js/database.js | 11 ++- ext/bg/js/dictionary-importer.js | 90 +++++++++++++++++++++++++ ext/bg/js/media-utility.js | 75 +++++++++++++++++++++ 5 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 ext/bg/js/media-utility.js (limited to 'ext/bg/js') diff --git a/ext/bg/background.html b/ext/bg/background.html index afe9c5d1..f1006f8d 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -36,6 +36,7 @@ + diff --git a/ext/bg/data/dictionary-term-bank-v3-schema.json b/ext/bg/data/dictionary-term-bank-v3-schema.json index bb982e36..4790e561 100644 --- a/ext/bg/data/dictionary-term-bank-v3-schema.json +++ b/ext/bg/data/dictionary-term-bank-v3-schema.json @@ -31,8 +31,85 @@ "type": "array", "description": "Array of definitions for the term/expression.", "items": { - "type": "string", - "description": "Single definition for the term/expression." + "oneOf": [ + { + "type": "string", + "description": "Single definition for the term/expression." + }, + { + "type": "object", + "description": "Single detailed definition for the term/expression.", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the data for this definition.", + "enum": ["text", "image"] + } + }, + "oneOf": [ + { + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["text"] + }, + "text": { + "type": "string", + "description": "Single definition for the term/expression." + } + } + }, + { + "required": [ + "type", + "path" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["image"] + }, + "path": { + "type": "string", + "description": "Path to the image file in the archive." + }, + "width": { + "type": "integer", + "description": "Preferred width of the image.", + "minimum": 1 + }, + "height": { + "type": "integer", + "description": "Preferred width of the image.", + "minimum": 1 + }, + "title": { + "type": "string", + "description": "Hover text for the image." + }, + "description": { + "type": "string", + "description": "Description of the image." + }, + "pixelated": { + "type": "boolean", + "description": "Whether or not the image should appear pixelated at sizes larger than the image's native resolution.", + "default": false + } + } + } + ] + } + ] } }, { diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 260c815a..0c7eee6a 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -33,7 +33,7 @@ class Database { } try { - this.db = await Database._open('dict', 5, (db, transaction, oldVersion) => { + this.db = await Database._open('dict', 6, (db, transaction, oldVersion) => { Database._upgrade(db, transaction, oldVersion, [ { version: 2, @@ -90,6 +90,15 @@ class Database { indices: ['dictionary', 'expression', 'reading', 'sequence', 'expressionReverse', 'readingReverse'] } } + }, + { + version: 6, + stores: { + media: { + primaryKey: {keyPath: 'id', autoIncrement: true}, + indices: ['dictionary', 'path'] + } + } } ]); }); diff --git a/ext/bg/js/dictionary-importer.js b/ext/bg/js/dictionary-importer.js index bf6809ec..8a4497a3 100644 --- a/ext/bg/js/dictionary-importer.js +++ b/ext/bg/js/dictionary-importer.js @@ -18,6 +18,7 @@ /* global * JSZip * JsonSchema + * mediaUtility * requestJson */ @@ -148,6 +149,22 @@ class DictionaryImporter { } } + // Extended data support + const extendedDataContext = { + archive, + media: new Map() + }; + for (const entry of termList) { + const glossaryList = entry.glossary; + for (let i = 0, ii = glossaryList.length; i < ii; ++i) { + const glossary = glossaryList[i]; + if (typeof glossary !== 'object' || glossary === null) { continue; } + glossaryList[i] = await this._formatDictionaryTermGlossaryObject(glossary, extendedDataContext, entry); + } + } + + const media = [...extendedDataContext.media.values()]; + // Add dictionary const summary = this._createSummary(dictionaryTitle, version, index, {prefixWildcardsSupported}); @@ -188,6 +205,7 @@ class DictionaryImporter { await bulkAdd('kanji', kanjiList); await bulkAdd('kanjiMeta', kanjiMetaList); await bulkAdd('tagMeta', tagList); + await bulkAdd('media', media); return {result: summary, errors}; } @@ -275,4 +293,76 @@ class DictionaryImporter { return [termBank, termMetaBank, kanjiBank, kanjiMetaBank, tagBank]; } + + async _formatDictionaryTermGlossaryObject(data, context, entry) { + switch (data.type) { + case 'text': + return data.text; + case 'image': + return await this._formatDictionaryTermGlossaryImage(data, context, entry); + default: + throw new Error(`Unhandled data type: ${data.type}`); + } + } + + async _formatDictionaryTermGlossaryImage(data, context, entry) { + const dictionary = entry.dictionary; + const {path, width: preferredWidth, height: preferredHeight, title, description, pixelated} = data; + if (context.media.has(path)) { + // Already exists + return data; + } + + let errorSource = entry.expression; + if (entry.reading.length > 0) { + errorSource += ` (${entry.reading});`; + } + + const file = context.archive.file(path); + if (file === null) { + throw new Error(`Could not find image at path ${JSON.stringify(path)} for ${errorSource}`); + } + + const source = await file.async('base64'); + const mediaType = mediaUtility.getImageMediaTypeFromFileName(path); + if (mediaType === null) { + throw new Error(`Could not determine media type for image at path ${JSON.stringify(path)} for ${errorSource}`); + } + + let image; + try { + image = await mediaUtility.loadImage(mediaType, source); + } catch (e) { + throw new Error(`Could not load image at path ${JSON.stringify(path)} for ${errorSource}`); + } + + const width = image.naturalWidth; + const height = image.naturalHeight; + + // Create image data + const mediaData = { + dictionary, + path, + mediaType, + width, + height, + source + }; + context.media.set(path, mediaData); + + // Create new data + const newData = { + type: 'image', + path, + width, + height + }; + if (typeof preferredWidth === 'number') { newData.preferredWidth = preferredWidth; } + if (typeof preferredHeight === 'number') { newData.preferredHeight = preferredHeight; } + if (typeof title === 'string') { newData.title = title; } + if (typeof description === 'string') { newData.description = description; } + if (typeof pixelated === 'boolean') { newData.pixelated = pixelated; } + + return newData; + } } diff --git a/ext/bg/js/media-utility.js b/ext/bg/js/media-utility.js new file mode 100644 index 00000000..24686838 --- /dev/null +++ b/ext/bg/js/media-utility.js @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 Yomichan Authors + * + * 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 . + */ + +const mediaUtility = (() => { + function getFileNameExtension(fileName) { + const match = /\.[^.]*$/.exec(fileName); + return match !== null ? match[0] : ''; + } + + function getImageMediaTypeFromFileName(fileName) { + switch (getFileNameExtension(fileName).toLowerCase()) { + case '.apng': + return 'image/apng'; + case '.bmp': + return 'image/bmp'; + case '.gif': + return 'image/gif'; + case '.ico': + case '.cur': + return 'image/x-icon'; + case '.jpg': + case '.jpeg': + case '.jfif': + case '.pjpeg': + case '.pjp': + return 'image/jpeg'; + case '.png': + return 'image/png'; + case '.svg': + return 'image/svg+xml'; + case '.tif': + case '.tiff': + return 'image/tiff'; + case '.webp': + return 'image/webp'; + default: + return null; + } + } + + function loadImage(mediaType, base64Source) { + return new Promise((resolve, reject) => { + const image = new Image(); + const eventListeners = new EventListenerCollection(); + eventListeners.addEventListener(image, 'load', () => { + eventListeners.removeAllEventListeners(); + resolve(image); + }, false); + eventListeners.addEventListener(image, 'error', () => { + eventListeners.removeAllEventListeners(); + reject(new Error('Image failed to load')); + }, false); + image.src = `data:${mediaType};base64,${base64Source}`; + }); + } + + return { + getImageMediaTypeFromFileName, + loadImage + }; +})(); -- cgit v1.2.3 From fd6ea0e404da2657f110599061af4034a524283a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 14:23:02 -0400 Subject: Add API for getting media data --- ext/bg/js/backend.js | 7 ++++++- ext/bg/js/database.js | 32 ++++++++++++++++++++++++++++++++ ext/mixed/js/api.js | 4 ++++ 3 files changed, 42 insertions(+), 1 deletion(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index e0814c17..8a19203f 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -111,7 +111,8 @@ class Backend { ['getAnkiModelFieldNames', {handler: this._onApiGetAnkiModelFieldNames.bind(this), async: true}], ['getDictionaryInfo', {handler: this._onApiGetDictionaryInfo.bind(this), async: true}], ['getDictionaryCounts', {handler: this._onApiGetDictionaryCounts.bind(this), async: true}], - ['purgeDatabase', {handler: this._onApiPurgeDatabase.bind(this), async: true}] + ['purgeDatabase', {handler: this._onApiPurgeDatabase.bind(this), async: true}], + ['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}] ]); this._commandHandlers = new Map([ @@ -762,6 +763,10 @@ class Backend { return await this.translator.purgeDatabase(); } + async _onApiGetMedia({targets}) { + return await this.database.getMedia(targets); + } + // Command handlers async _onCommandSearch(params) { diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 0c7eee6a..16612403 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -277,6 +277,34 @@ class Database { return result; } + async getMedia(targets) { + this._validate(); + + const count = targets.length; + const promises = []; + const results = new Array(count).fill(null); + const createResult = Database._createMedia; + const processRow = (row, [index, dictionaryName]) => { + if (row.dictionary === dictionaryName) { + results[index] = createResult(row, index); + } + }; + + const transaction = this.db.transaction(['media'], 'readonly'); + const objectStore = transaction.objectStore('media'); + const index = objectStore.index('path'); + + for (let i = 0; i < count; ++i) { + const {path, dictionaryName} = targets[i]; + const only = IDBKeyRange.only(path); + promises.push(Database._getAll(index, only, [i, dictionaryName], processRow)); + } + + await Promise.all(promises); + + return results; + } + async getDictionaryInfo() { this._validate(); @@ -441,6 +469,10 @@ class Database { return {character, mode, data, dictionary, index}; } + static _createMedia(row, index) { + return Object.assign({}, row, {index}); + } + static _getAll(dbIndex, query, context, processRow) { const fn = typeof dbIndex.getAll === 'function' ? Database._getAllFast : Database._getAllUsingCursor; return fn(dbIndex, query, context, processRow); diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index c97dc687..52f41646 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -140,6 +140,10 @@ function apiPurgeDatabase() { return _apiInvoke('purgeDatabase'); } +function apiGetMedia(targets) { + return _apiInvoke('getMedia', {targets}); +} + function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { -- cgit v1.2.3 From 7faaf4e45737dd06ab3fdf189bd9c1d26ad1349d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 19 Apr 2020 10:16:59 -0400 Subject: Use 'content' instead of 'source' to contain media file data --- ext/bg/js/dictionary-importer.js | 6 +++--- ext/bg/js/media-utility.js | 4 ++-- ext/mixed/js/media-loader.js | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/dictionary-importer.js b/ext/bg/js/dictionary-importer.js index 8a4497a3..b5c535bb 100644 --- a/ext/bg/js/dictionary-importer.js +++ b/ext/bg/js/dictionary-importer.js @@ -323,7 +323,7 @@ class DictionaryImporter { throw new Error(`Could not find image at path ${JSON.stringify(path)} for ${errorSource}`); } - const source = await file.async('base64'); + const content = await file.async('base64'); const mediaType = mediaUtility.getImageMediaTypeFromFileName(path); if (mediaType === null) { throw new Error(`Could not determine media type for image at path ${JSON.stringify(path)} for ${errorSource}`); @@ -331,7 +331,7 @@ class DictionaryImporter { let image; try { - image = await mediaUtility.loadImage(mediaType, source); + image = await mediaUtility.loadImage(mediaType, content); } catch (e) { throw new Error(`Could not load image at path ${JSON.stringify(path)} for ${errorSource}`); } @@ -346,7 +346,7 @@ class DictionaryImporter { mediaType, width, height, - source + content }; context.media.set(path, mediaData); diff --git a/ext/bg/js/media-utility.js b/ext/bg/js/media-utility.js index 24686838..febc509a 100644 --- a/ext/bg/js/media-utility.js +++ b/ext/bg/js/media-utility.js @@ -52,7 +52,7 @@ const mediaUtility = (() => { } } - function loadImage(mediaType, base64Source) { + function loadImage(mediaType, content) { return new Promise((resolve, reject) => { const image = new Image(); const eventListeners = new EventListenerCollection(); @@ -64,7 +64,7 @@ const mediaUtility = (() => { eventListeners.removeAllEventListeners(); reject(new Error('Image failed to load')); }, false); - image.src = `data:${mediaType};base64,${base64Source}`; + image.src = `data:${mediaType};base64,${content}`; }); } diff --git a/ext/mixed/js/media-loader.js b/ext/mixed/js/media-loader.js index c89c4f12..5e3177fc 100644 --- a/ext/mixed/js/media-loader.js +++ b/ext/mixed/js/media-loader.js @@ -86,7 +86,7 @@ class MediaLoader { const token = this._token; const data = (await apiGetMedia([{path, dictionaryName}]))[0]; if (token === this._token && data !== null) { - const sourceArrayBuffer = this._base64ToArrayBuffer(data.source); + const sourceArrayBuffer = this._base64ToArrayBuffer(data.content); const blob = new Blob([sourceArrayBuffer], {type: data.mediaType}); const url = URL.createObjectURL(blob); cachedData.data = data; @@ -95,8 +95,8 @@ class MediaLoader { return cachedData; } - _base64ToArrayBuffer(source) { - const binarySource = window.atob(source); + _base64ToArrayBuffer(content) { + const binarySource = window.atob(content); const length = binarySource.length; const array = new Uint8Array(length); for (let i = 0; i < length; ++i) { -- cgit v1.2.3 From 16893b52b141938a32027049b71cc6c6f46dfb93 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 19 Apr 2020 10:18:31 -0400 Subject: Make getFileNameExtension properly handle directory separators --- ext/bg/js/media-utility.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/media-utility.js b/ext/bg/js/media-utility.js index febc509a..8a46cb49 100644 --- a/ext/bg/js/media-utility.js +++ b/ext/bg/js/media-utility.js @@ -16,13 +16,13 @@ */ const mediaUtility = (() => { - function getFileNameExtension(fileName) { - const match = /\.[^.]*$/.exec(fileName); + function getFileNameExtension(path) { + const match = /\.[^./\\]*$/.exec(path); return match !== null ? match[0] : ''; } - function getImageMediaTypeFromFileName(fileName) { - switch (getFileNameExtension(fileName).toLowerCase()) { + function getImageMediaTypeFromFileName(path) { + switch (getFileNameExtension(path).toLowerCase()) { case '.apng': return 'image/apng'; case '.bmp': -- cgit v1.2.3 From 0e80c0d5d03b51c56e72657a46c868c9a2c1137d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 19 Apr 2020 10:24:36 -0400 Subject: Rename loadImage to loadImageBase64 for clarity --- ext/bg/js/dictionary-importer.js | 2 +- ext/bg/js/media-utility.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/dictionary-importer.js b/ext/bg/js/dictionary-importer.js index b5c535bb..3727f7ee 100644 --- a/ext/bg/js/dictionary-importer.js +++ b/ext/bg/js/dictionary-importer.js @@ -331,7 +331,7 @@ class DictionaryImporter { let image; try { - image = await mediaUtility.loadImage(mediaType, content); + image = await mediaUtility.loadImageBase64(mediaType, content); } catch (e) { throw new Error(`Could not load image at path ${JSON.stringify(path)} for ${errorSource}`); } diff --git a/ext/bg/js/media-utility.js b/ext/bg/js/media-utility.js index 8a46cb49..fcbf9d37 100644 --- a/ext/bg/js/media-utility.js +++ b/ext/bg/js/media-utility.js @@ -52,7 +52,7 @@ const mediaUtility = (() => { } } - function loadImage(mediaType, content) { + function loadImageBase64(mediaType, content) { return new Promise((resolve, reject) => { const image = new Image(); const eventListeners = new EventListenerCollection(); @@ -70,6 +70,6 @@ const mediaUtility = (() => { return { getImageMediaTypeFromFileName, - loadImage + loadImageBase64 }; })(); -- cgit v1.2.3 From a7e7d546c70c604e986d5c772d842c47fb701eda Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 19 Apr 2020 10:24:59 -0400 Subject: Add documentation --- ext/bg/js/media-utility.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'ext/bg/js') diff --git a/ext/bg/js/media-utility.js b/ext/bg/js/media-utility.js index fcbf9d37..1f93b2b4 100644 --- a/ext/bg/js/media-utility.js +++ b/ext/bg/js/media-utility.js @@ -15,12 +15,28 @@ * along with this program. If not, see . */ +/** + * mediaUtility is an object containing helper methods related to media processing. + */ const mediaUtility = (() => { + /** + * Gets the file extension of a file path. URL search queries and hash + * fragments are not handled. + * @param path The path to the file. + * @returns The file extension, including the '.', or an empty string + * if there is no file extension. + */ function getFileNameExtension(path) { const match = /\.[^./\\]*$/.exec(path); return match !== null ? match[0] : ''; } + /** + * Gets an image file's media type using a file path. + * @param path The path to the file. + * @returns The media type string if it can be determined from the file path, + * otherwise null. + */ function getImageMediaTypeFromFileName(path) { switch (getFileNameExtension(path).toLowerCase()) { case '.apng': @@ -52,6 +68,13 @@ const mediaUtility = (() => { } } + /** + * Attempts to load an image using a base64 encoded content and a media type. + * @param mediaType The media type for the image content. + * @param content The binary content for the image, encoded in base64. + * @returns A Promise which resolves with an HTMLImageElement instance on + * successful load, otherwise an error is thrown. + */ function loadImageBase64(mediaType, content) { return new Promise((resolve, reject) => { const image = new Image(); -- cgit v1.2.3 From 6c93d1984fbfe4fc21df4d0675fb4a82f2991ea3 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 19 Apr 2020 14:21:43 -0400 Subject: Change frontend-initialize.js to content-script-main.js --- ext/bg/js/search-frontend.js | 2 +- ext/fg/js/content-script-main.js | 133 +++++++++++++++++++++++++++++++++++++++ ext/fg/js/frontend-initialize.js | 133 --------------------------------------- ext/fg/js/popup-nested.js | 2 +- ext/manifest.json | 2 +- 5 files changed, 136 insertions(+), 136 deletions(-) create mode 100644 ext/fg/js/content-script-main.js delete mode 100644 ext/fg/js/frontend-initialize.js (limited to 'ext/bg/js') diff --git a/ext/bg/js/search-frontend.js b/ext/bg/js/search-frontend.js index e534e771..bda46a98 100644 --- a/ext/bg/js/search-frontend.js +++ b/ext/bg/js/search-frontend.js @@ -27,7 +27,7 @@ function injectSearchFrontend() { '/fg/js/popup.js', '/fg/js/popup-proxy-host.js', '/fg/js/frontend.js', - '/fg/js/frontend-initialize.js' + '/fg/js/content-script-main.js' ]; for (const src of scriptSrcs) { const node = document.querySelector(`script[src='${src}']`); diff --git a/ext/fg/js/content-script-main.js b/ext/fg/js/content-script-main.js new file mode 100644 index 00000000..974320cc --- /dev/null +++ b/ext/fg/js/content-script-main.js @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2019-2020 Yomichan Authors + * + * 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 . + */ + +/* global + * DOM + * FrameOffsetForwarder + * Frontend + * PopupProxy + * PopupProxyHost + * apiBroadcastTab + * apiOptionsGet + */ + +async function createIframePopupProxy(url, frameOffsetForwarder, setDisabled) { + const rootPopupInformationPromise = yomichan.getTemporaryListenerResult( + chrome.runtime.onMessage, + ({action, params}, {resolve}) => { + if (action === 'rootPopupInformation') { + resolve(params); + } + } + ); + apiBroadcastTab('rootPopupRequestInformationBroadcast'); + const {popupId, frameId} = await rootPopupInformationPromise; + + const getFrameOffset = frameOffsetForwarder.getOffset.bind(frameOffsetForwarder); + + const popup = new PopupProxy(popupId, 0, null, frameId, url, getFrameOffset, setDisabled); + await popup.prepare(); + + return popup; +} + +async function getOrCreatePopup(depth) { + const popupHost = new PopupProxyHost(); + await popupHost.prepare(); + + const popup = popupHost.getOrCreatePopup(null, null, depth); + + return popup; +} + +async function createPopupProxy(depth, id, parentFrameId, url) { + const popup = new PopupProxy(null, depth + 1, id, parentFrameId, url); + await popup.prepare(); + + return popup; +} + +async function contentScriptMain() { + await yomichan.prepare(); + + const data = window.frontendInitializationData || {}; + const {id, depth=0, parentFrameId, url=window.location.href, proxy=false, isSearchPage=false} = data; + + const isIframe = !proxy && (window !== window.parent); + + const popups = { + iframe: null, + proxy: null, + normal: null + }; + + let frontend = null; + let frontendPreparePromise = null; + let frameOffsetForwarder = null; + + let iframePopupsInRootFrameAvailable = true; + + const disableIframePopupsInRootFrame = () => { + iframePopupsInRootFrameAvailable = false; + applyOptions(); + }; + + const applyOptions = async () => { + const optionsContext = {depth: isSearchPage ? 0 : depth, url}; + const options = await apiOptionsGet(optionsContext); + + if (!proxy && frameOffsetForwarder === null) { + frameOffsetForwarder = new FrameOffsetForwarder(); + frameOffsetForwarder.start(); + } + + let popup; + if (isIframe && options.general.showIframePopupsInRootFrame && DOM.getFullscreenElement() === null && iframePopupsInRootFrameAvailable) { + popup = popups.iframe || await createIframePopupProxy(url, frameOffsetForwarder, disableIframePopupsInRootFrame); + popups.iframe = popup; + } else if (proxy) { + popup = popups.proxy || await createPopupProxy(depth, id, parentFrameId, url); + popups.proxy = popup; + } else { + popup = popups.normal || await getOrCreatePopup(depth); + popups.normal = popup; + } + + if (frontend === null) { + frontend = new Frontend(popup); + frontendPreparePromise = frontend.prepare(); + await frontendPreparePromise; + } else { + await frontendPreparePromise; + if (isSearchPage) { + const disabled = !options.scanning.enableOnSearchPage; + frontend.setDisabledOverride(disabled); + } + + if (isIframe) { + await frontend.setPopup(popup); + } + } + }; + + yomichan.on('optionsUpdated', applyOptions); + window.addEventListener('fullscreenchange', applyOptions, false); + + await applyOptions(); +} + +contentScriptMain(); diff --git a/ext/fg/js/frontend-initialize.js b/ext/fg/js/frontend-initialize.js deleted file mode 100644 index 2df59e20..00000000 --- a/ext/fg/js/frontend-initialize.js +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2019-2020 Yomichan Authors - * - * 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 . - */ - -/* global - * DOM - * FrameOffsetForwarder - * Frontend - * PopupProxy - * PopupProxyHost - * apiBroadcastTab - * apiOptionsGet - */ - -async function createIframePopupProxy(url, frameOffsetForwarder, setDisabled) { - const rootPopupInformationPromise = yomichan.getTemporaryListenerResult( - chrome.runtime.onMessage, - ({action, params}, {resolve}) => { - if (action === 'rootPopupInformation') { - resolve(params); - } - } - ); - apiBroadcastTab('rootPopupRequestInformationBroadcast'); - const {popupId, frameId} = await rootPopupInformationPromise; - - const getFrameOffset = frameOffsetForwarder.getOffset.bind(frameOffsetForwarder); - - const popup = new PopupProxy(popupId, 0, null, frameId, url, getFrameOffset, setDisabled); - await popup.prepare(); - - return popup; -} - -async function getOrCreatePopup(depth) { - const popupHost = new PopupProxyHost(); - await popupHost.prepare(); - - const popup = popupHost.getOrCreatePopup(null, null, depth); - - return popup; -} - -async function createPopupProxy(depth, id, parentFrameId, url) { - const popup = new PopupProxy(null, depth + 1, id, parentFrameId, url); - await popup.prepare(); - - return popup; -} - -async function main() { - await yomichan.prepare(); - - const data = window.frontendInitializationData || {}; - const {id, depth=0, parentFrameId, url=window.location.href, proxy=false, isSearchPage=false} = data; - - const isIframe = !proxy && (window !== window.parent); - - const popups = { - iframe: null, - proxy: null, - normal: null - }; - - let frontend = null; - let frontendPreparePromise = null; - let frameOffsetForwarder = null; - - let iframePopupsInRootFrameAvailable = true; - - const disableIframePopupsInRootFrame = () => { - iframePopupsInRootFrameAvailable = false; - applyOptions(); - }; - - const applyOptions = async () => { - const optionsContext = {depth: isSearchPage ? 0 : depth, url}; - const options = await apiOptionsGet(optionsContext); - - if (!proxy && frameOffsetForwarder === null) { - frameOffsetForwarder = new FrameOffsetForwarder(); - frameOffsetForwarder.start(); - } - - let popup; - if (isIframe && options.general.showIframePopupsInRootFrame && DOM.getFullscreenElement() === null && iframePopupsInRootFrameAvailable) { - popup = popups.iframe || await createIframePopupProxy(url, frameOffsetForwarder, disableIframePopupsInRootFrame); - popups.iframe = popup; - } else if (proxy) { - popup = popups.proxy || await createPopupProxy(depth, id, parentFrameId, url); - popups.proxy = popup; - } else { - popup = popups.normal || await getOrCreatePopup(depth); - popups.normal = popup; - } - - if (frontend === null) { - frontend = new Frontend(popup); - frontendPreparePromise = frontend.prepare(); - await frontendPreparePromise; - } else { - await frontendPreparePromise; - if (isSearchPage) { - const disabled = !options.scanning.enableOnSearchPage; - frontend.setDisabledOverride(disabled); - } - - if (isIframe) { - await frontend.setPopup(popup); - } - } - }; - - yomichan.on('optionsUpdated', applyOptions); - window.addEventListener('fullscreenchange', applyOptions, false); - - await applyOptions(); -} - -main(); diff --git a/ext/fg/js/popup-nested.js b/ext/fg/js/popup-nested.js index c140f9c8..27482931 100644 --- a/ext/fg/js/popup-nested.js +++ b/ext/fg/js/popup-nested.js @@ -26,7 +26,7 @@ function injectPopupNested() { '/fg/js/popup.js', '/fg/js/popup-proxy.js', '/fg/js/frontend.js', - '/fg/js/frontend-initialize.js' + '/fg/js/content-script-main.js' ]; for (const src of scriptSrcs) { const script = document.createElement('script'); diff --git a/ext/manifest.json b/ext/manifest.json index d383dab0..4f35b03c 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -31,7 +31,7 @@ "fg/js/popup-proxy.js", "fg/js/popup-proxy-host.js", "fg/js/frontend.js", - "fg/js/frontend-initialize.js" + "fg/js/content-script-main.js" ], "match_about_blank": true, "all_frames": true -- cgit v1.2.3 From 3c8eb9eee009ebe265fbae3f7d7ac0d74fcbdd94 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 19 Apr 2020 14:26:44 -0400 Subject: Create background-main.js --- ext/bg/background.html | 3 ++- ext/bg/js/backend.js | 3 --- ext/bg/js/background-main.js | 27 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 ext/bg/js/background-main.js (limited to 'ext/bg/js') diff --git a/ext/bg/background.html b/ext/bg/background.html index f1006f8d..3446d9ce 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -24,6 +24,7 @@ + @@ -45,6 +46,6 @@ - + diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 8a19203f..d23fda10 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -1054,6 +1054,3 @@ class Backend { } } } - -window.yomichanBackend = new Backend(); -window.yomichanBackend.prepare(); diff --git a/ext/bg/js/background-main.js b/ext/bg/js/background-main.js new file mode 100644 index 00000000..c000c38d --- /dev/null +++ b/ext/bg/js/background-main.js @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 Yomichan Authors + * + * 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 . + */ + +/* global + * Backend + */ + +async function main() { + window.yomichanBackend = new Backend(); + await window.yomichanBackend.prepare(); +} + +main(); -- cgit v1.2.3 From 3edaf319da31ef943ada9661eb67019a75e6b7ac Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 19 Apr 2020 14:27:15 -0400 Subject: Rename context.js to context-main.js --- ext/bg/context.html | 2 +- ext/bg/js/context-main.js | 95 +++++++++++++++++++++++++++++++++++++++++++++++ ext/bg/js/context.js | 89 -------------------------------------------- 3 files changed, 96 insertions(+), 90 deletions(-) create mode 100644 ext/bg/js/context-main.js delete mode 100644 ext/bg/js/context.js (limited to 'ext/bg/js') diff --git a/ext/bg/context.html b/ext/bg/context.html index 0e50ed7c..394869b1 100644 --- a/ext/bg/context.html +++ b/ext/bg/context.html @@ -185,6 +185,6 @@ - + diff --git a/ext/bg/js/context-main.js b/ext/bg/js/context-main.js new file mode 100644 index 00000000..67bb4e18 --- /dev/null +++ b/ext/bg/js/context-main.js @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017-2020 Yomichan Authors + * + * 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 . + */ + +/* global + * apiCommandExec + * apiGetEnvironmentInfo + * apiOptionsGet + */ + +function showExtensionInfo() { + const node = document.getElementById('extension-info'); + if (node === null) { return; } + + const manifest = chrome.runtime.getManifest(); + node.textContent = `${manifest.name} v${manifest.version}`; +} + +function setupButtonEvents(selector, command, url) { + const nodes = document.querySelectorAll(selector); + for (const node of nodes) { + node.addEventListener('click', (e) => { + if (e.button !== 0) { return; } + apiCommandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'}); + e.preventDefault(); + }, false); + node.addEventListener('auxclick', (e) => { + if (e.button !== 1) { return; } + apiCommandExec(command, {mode: 'newTab'}); + e.preventDefault(); + }, false); + + if (typeof url === 'string') { + node.href = url; + node.target = '_blank'; + node.rel = 'noopener'; + } + } +} + +async function mainInner() { + await yomichan.prepare(); + + showExtensionInfo(); + + apiGetEnvironmentInfo().then(({browser}) => { + // Firefox mobile opens this page as a full webpage. + document.documentElement.dataset.mode = (browser === 'firefox-mobile' ? 'full' : 'mini'); + }); + + const manifest = chrome.runtime.getManifest(); + + setupButtonEvents('.action-open-search', 'search', chrome.runtime.getURL('/bg/search.html')); + setupButtonEvents('.action-open-options', 'options', chrome.runtime.getURL(manifest.options_ui.page)); + setupButtonEvents('.action-open-help', 'help'); + + const optionsContext = { + depth: 0, + url: window.location.href + }; + apiOptionsGet(optionsContext).then((options) => { + const toggle = document.querySelector('#enable-search'); + toggle.checked = options.general.enable; + toggle.addEventListener('change', () => apiCommandExec('toggle'), false); + + const toggle2 = document.querySelector('#enable-search2'); + toggle2.checked = options.general.enable; + toggle2.addEventListener('change', () => apiCommandExec('toggle'), false); + + setTimeout(() => { + for (const n of document.querySelectorAll('.toggle-group')) { + n.classList.add('toggle-group-animated'); + } + }, 10); + }); +} + +async function main() { + window.addEventListener('DOMContentLoaded', mainInner, false); +} + +main(); diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js deleted file mode 100644 index e3d4ad4a..00000000 --- a/ext/bg/js/context.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2017-2020 Yomichan Authors - * - * 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 . - */ - -/* global - * apiCommandExec - * apiGetEnvironmentInfo - * apiOptionsGet - */ - -function showExtensionInfo() { - const node = document.getElementById('extension-info'); - if (node === null) { return; } - - const manifest = chrome.runtime.getManifest(); - node.textContent = `${manifest.name} v${manifest.version}`; -} - -function setupButtonEvents(selector, command, url) { - const nodes = document.querySelectorAll(selector); - for (const node of nodes) { - node.addEventListener('click', (e) => { - if (e.button !== 0) { return; } - apiCommandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'}); - e.preventDefault(); - }, false); - node.addEventListener('auxclick', (e) => { - if (e.button !== 1) { return; } - apiCommandExec(command, {mode: 'newTab'}); - e.preventDefault(); - }, false); - - if (typeof url === 'string') { - node.href = url; - node.target = '_blank'; - node.rel = 'noopener'; - } - } -} - -window.addEventListener('DOMContentLoaded', async () => { - await yomichan.prepare(); - - showExtensionInfo(); - - apiGetEnvironmentInfo().then(({browser}) => { - // Firefox mobile opens this page as a full webpage. - document.documentElement.dataset.mode = (browser === 'firefox-mobile' ? 'full' : 'mini'); - }); - - const manifest = chrome.runtime.getManifest(); - - setupButtonEvents('.action-open-search', 'search', chrome.runtime.getURL('/bg/search.html')); - setupButtonEvents('.action-open-options', 'options', chrome.runtime.getURL(manifest.options_ui.page)); - setupButtonEvents('.action-open-help', 'help'); - - const optionsContext = { - depth: 0, - url: window.location.href - }; - apiOptionsGet(optionsContext).then((options) => { - const toggle = document.querySelector('#enable-search'); - toggle.checked = options.general.enable; - toggle.addEventListener('change', () => apiCommandExec('toggle'), false); - - const toggle2 = document.querySelector('#enable-search2'); - toggle2.checked = options.general.enable; - toggle2.addEventListener('change', () => apiCommandExec('toggle'), false); - - setTimeout(() => { - for (const n of document.querySelectorAll('.toggle-group')) { - n.classList.add('toggle-group-animated'); - } - }, 10); - }); -}); -- cgit v1.2.3 From 4d3d5d9ccb1c8ffbac0be0dc348790b34f68a564 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 19 Apr 2020 14:28:07 -0400 Subject: Rename search-frontend.js to search-main.js Also move DisplaySearch creation into the main() function. --- ext/bg/js/search-frontend.js | 79 ----------------------------------------- ext/bg/js/search-main.js | 83 ++++++++++++++++++++++++++++++++++++++++++++ ext/bg/js/search.js | 8 ----- ext/bg/search.html | 3 +- 4 files changed, 85 insertions(+), 88 deletions(-) delete mode 100644 ext/bg/js/search-frontend.js create mode 100644 ext/bg/js/search-main.js (limited to 'ext/bg/js') diff --git a/ext/bg/js/search-frontend.js b/ext/bg/js/search-frontend.js deleted file mode 100644 index bda46a98..00000000 --- a/ext/bg/js/search-frontend.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2019-2020 Yomichan Authors - * - * 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 . - */ - -/* global - * apiOptionsGet - */ - -function injectSearchFrontend() { - const scriptSrcs = [ - '/mixed/js/text-scanner.js', - '/fg/js/frontend-api-receiver.js', - '/fg/js/frame-offset-forwarder.js', - '/fg/js/popup.js', - '/fg/js/popup-proxy-host.js', - '/fg/js/frontend.js', - '/fg/js/content-script-main.js' - ]; - for (const src of scriptSrcs) { - const node = document.querySelector(`script[src='${src}']`); - if (node !== null) { continue; } - - const script = document.createElement('script'); - script.async = false; - script.src = src; - document.body.appendChild(script); - } - - const styleSrcs = [ - '/fg/css/client.css' - ]; - for (const src of styleSrcs) { - const style = document.createElement('link'); - style.rel = 'stylesheet'; - style.type = 'text/css'; - style.href = src; - document.head.appendChild(style); - } -} - -async function main() { - await yomichan.prepare(); - - let optionsApplied = false; - - const applyOptions = async () => { - const optionsContext = { - depth: 0, - url: window.location.href - }; - const options = await apiOptionsGet(optionsContext); - if (!options.scanning.enableOnSearchPage || optionsApplied) { return; } - optionsApplied = true; - - window.frontendInitializationData = {depth: 1, proxy: false, isSearchPage: true}; - injectSearchFrontend(); - - yomichan.off('optionsUpdated', applyOptions); - }; - - yomichan.on('optionsUpdated', applyOptions); - - await applyOptions(); -} - -main(); diff --git a/ext/bg/js/search-main.js b/ext/bg/js/search-main.js new file mode 100644 index 00000000..91c39222 --- /dev/null +++ b/ext/bg/js/search-main.js @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019-2020 Yomichan Authors + * + * 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 . + */ + +/* global + * DisplaySearch + * apiOptionsGet + */ + +function injectSearchFrontend() { + const scriptSrcs = [ + '/mixed/js/text-scanner.js', + '/fg/js/frontend-api-receiver.js', + '/fg/js/frame-offset-forwarder.js', + '/fg/js/popup.js', + '/fg/js/popup-proxy-host.js', + '/fg/js/frontend.js', + '/fg/js/content-script-main.js' + ]; + for (const src of scriptSrcs) { + const node = document.querySelector(`script[src='${src}']`); + if (node !== null) { continue; } + + const script = document.createElement('script'); + script.async = false; + script.src = src; + document.body.appendChild(script); + } + + const styleSrcs = [ + '/fg/css/client.css' + ]; + for (const src of styleSrcs) { + const style = document.createElement('link'); + style.rel = 'stylesheet'; + style.type = 'text/css'; + style.href = src; + document.head.appendChild(style); + } +} + +async function main() { + await yomichan.prepare(); + + const displaySearch = new DisplaySearch(); + await displaySearch.prepare(); + + let optionsApplied = false; + + const applyOptions = async () => { + const optionsContext = { + depth: 0, + url: window.location.href + }; + const options = await apiOptionsGet(optionsContext); + if (!options.scanning.enableOnSearchPage || optionsApplied) { return; } + optionsApplied = true; + + window.frontendInitializationData = {depth: 1, proxy: false, isSearchPage: true}; + injectSearchFrontend(); + + yomichan.off('optionsUpdated', applyOptions); + }; + + yomichan.on('optionsUpdated', applyOptions); + + await applyOptions(); +} + +main(); diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 871c576b..266cccc8 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -72,12 +72,6 @@ class DisplaySearch extends Display { ]); } - static create() { - const instance = new DisplaySearch(); - instance.prepare(); - return instance; - } - async prepare() { try { await super.prepare(); @@ -376,5 +370,3 @@ class DisplaySearch extends Display { } } } - -DisplaySearch.instance = DisplaySearch.create(); diff --git a/ext/bg/search.html b/ext/bg/search.html index fe88e264..9a824776 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -94,6 +94,7 @@ - + + -- cgit v1.2.3 From 7c578f75827ae0a72b13f04b8342fd8129f702d4 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 19 Apr 2020 14:28:29 -0400 Subject: Create popup-preview-frame-main.js --- ext/bg/js/settings/popup-preview-frame-main.js | 27 ++++++++++++++++++++++++++ ext/bg/js/settings/popup-preview-frame.js | 11 ----------- ext/bg/settings-popup-preview.html | 2 ++ 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 ext/bg/js/settings/popup-preview-frame-main.js (limited to 'ext/bg/js') diff --git a/ext/bg/js/settings/popup-preview-frame-main.js b/ext/bg/js/settings/popup-preview-frame-main.js new file mode 100644 index 00000000..e6e4727f --- /dev/null +++ b/ext/bg/js/settings/popup-preview-frame-main.js @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019-2020 Yomichan Authors + * + * 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 . + */ + +/* global + * SettingsPopupPreview + */ + +async function main() { + const instance = new SettingsPopupPreview(); + await instance.prepare(); +} + +main(); diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index fba114e2..420a7c5a 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -41,12 +41,6 @@ class SettingsPopupPreview { ]); } - static create() { - const instance = new SettingsPopupPreview(); - instance.prepare(); - return instance; - } - async prepare() { // Setup events window.addEventListener('message', this.onMessage.bind(this), false); @@ -178,8 +172,3 @@ class SettingsPopupPreview { this.setInfoVisible(!this.popupShown); } } - -SettingsPopupPreview.instance = SettingsPopupPreview.create(); - - - diff --git a/ext/bg/settings-popup-preview.html b/ext/bg/settings-popup-preview.html index f33ecedf..66475b7c 100644 --- a/ext/bg/settings-popup-preview.html +++ b/ext/bg/settings-popup-preview.html @@ -129,5 +129,7 @@ + + -- cgit v1.2.3 From c43dac19a467b055b2e6d16e08cab25dcc748419 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 20 Apr 2020 22:18:37 -0400 Subject: Use wanakana object directly --- ext/bg/js/search.js | 9 +++++---- ext/bg/js/settings/popup-preview.js | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 871c576b..006536c0 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -22,6 +22,7 @@ * apiClipboardGet * apiOptionsSet * apiTermsFind + * wanakana */ class DisplaySearch extends Display { @@ -89,7 +90,7 @@ class DisplaySearch extends Display { if (this.options.general.enableWanakana === true) { this.wanakanaEnable.checked = true; - window.wanakana.bind(this.query); + wanakana.bind(this.query); } else { this.wanakanaEnable.checked = false; } @@ -256,9 +257,9 @@ class DisplaySearch extends Display { onWanakanaEnableChange(e) { const enableWanakana = e.target.checked; if (enableWanakana) { - window.wanakana.bind(this.query); + wanakana.bind(this.query); } else { - window.wanakana.unbind(this.query); + wanakana.unbind(this.query); } apiOptionsSet({general: {enableWanakana}}, this.getOptionsContext()); } @@ -298,7 +299,7 @@ class DisplaySearch extends Display { } setQuery(query) { - const interpretedQuery = this.isWanakanaEnabled() ? window.wanakana.toKana(query) : query; + const interpretedQuery = this.isWanakanaEnabled() ? wanakana.toKana(query) : query; this.query.value = interpretedQuery; this.queryParser.setText(interpretedQuery); } diff --git a/ext/bg/js/settings/popup-preview.js b/ext/bg/js/settings/popup-preview.js index 091872be..45bf531e 100644 --- a/ext/bg/js/settings/popup-preview.js +++ b/ext/bg/js/settings/popup-preview.js @@ -15,6 +15,9 @@ * along with this program. If not, see . */ +/* global + * wanakana + */ function appearanceInitialize() { let previewVisible = false; @@ -37,7 +40,7 @@ function showAppearancePreview() { frame.src = '/bg/settings-popup-preview.html'; frame.id = 'settings-popup-preview-frame'; - window.wanakana.bind(text[0]); + wanakana.bind(text[0]); const targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); -- cgit v1.2.3 From d8276a9d5d119edf1747593608d3e135947019f0 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 22 Apr 2020 21:42:20 -0400 Subject: Use IIFE for entry points --- ext/bg/js/background-main.js | 6 ++---- ext/bg/js/context-main.js | 6 ++---- ext/bg/js/search-main.js | 6 ++---- ext/bg/js/settings/popup-preview-frame-main.js | 6 ++---- ext/fg/js/content-script-main.js | 6 ++---- ext/fg/js/float-main.js | 6 ++---- 6 files changed, 12 insertions(+), 24 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/background-main.js b/ext/bg/js/background-main.js index c000c38d..24117f4e 100644 --- a/ext/bg/js/background-main.js +++ b/ext/bg/js/background-main.js @@ -19,9 +19,7 @@ * Backend */ -async function main() { +(async () => { window.yomichanBackend = new Backend(); await window.yomichanBackend.prepare(); -} - -main(); +})(); diff --git a/ext/bg/js/context-main.js b/ext/bg/js/context-main.js index 67bb4e18..e2086a96 100644 --- a/ext/bg/js/context-main.js +++ b/ext/bg/js/context-main.js @@ -88,8 +88,6 @@ async function mainInner() { }); } -async function main() { +(async () => { window.addEventListener('DOMContentLoaded', mainInner, false); -} - -main(); +})(); diff --git a/ext/bg/js/search-main.js b/ext/bg/js/search-main.js index 91c39222..38b6d99a 100644 --- a/ext/bg/js/search-main.js +++ b/ext/bg/js/search-main.js @@ -52,7 +52,7 @@ function injectSearchFrontend() { } } -async function main() { +(async () => { await yomichan.prepare(); const displaySearch = new DisplaySearch(); @@ -78,6 +78,4 @@ async function main() { yomichan.on('optionsUpdated', applyOptions); await applyOptions(); -} - -main(); +})(); diff --git a/ext/bg/js/settings/popup-preview-frame-main.js b/ext/bg/js/settings/popup-preview-frame-main.js index e6e4727f..86c2814c 100644 --- a/ext/bg/js/settings/popup-preview-frame-main.js +++ b/ext/bg/js/settings/popup-preview-frame-main.js @@ -19,9 +19,7 @@ * SettingsPopupPreview */ -async function main() { +(async () => { const instance = new SettingsPopupPreview(); await instance.prepare(); -} - -main(); +})(); diff --git a/ext/fg/js/content-script-main.js b/ext/fg/js/content-script-main.js index 974320cc..14285536 100644 --- a/ext/fg/js/content-script-main.js +++ b/ext/fg/js/content-script-main.js @@ -61,7 +61,7 @@ async function createPopupProxy(depth, id, parentFrameId, url) { return popup; } -async function contentScriptMain() { +(async () => { await yomichan.prepare(); const data = window.frontendInitializationData || {}; @@ -128,6 +128,4 @@ async function contentScriptMain() { window.addEventListener('fullscreenchange', applyOptions, false); await applyOptions(); -} - -contentScriptMain(); +})(); diff --git a/ext/fg/js/float-main.js b/ext/fg/js/float-main.js index 072c86e0..f056f707 100644 --- a/ext/fg/js/float-main.js +++ b/ext/fg/js/float-main.js @@ -67,8 +67,6 @@ async function popupNestedInitialize(id, depth, parentFrameId, url) { await applyOptions(); } -async function main() { +(async () => { new DisplayFloat(); -} - -main(); +})(); -- cgit v1.2.3 From 6cd86e203ac243b7cde09df46920319dfb2b56b4 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Thu, 23 Apr 2020 19:58:31 +0300 Subject: fix custom anki templates --- ext/bg/js/backend.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 8a19203f..6b696f2c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -496,7 +496,7 @@ class Backend { async _onApiDefinitionAdd({definition, mode, context, details, optionsContext}) { const options = this.getOptions(optionsContext); - const templates = this.defaultAnkiFieldTemplates; + const templates = this._getTemplates(options); if (mode !== 'kanji') { const {customSourceUrl} = options.audio; @@ -522,7 +522,7 @@ class Backend { async _onApiDefinitionsAddable({definitions, modes, context, optionsContext}) { const options = this.getOptions(optionsContext); - const templates = this.defaultAnkiFieldTemplates; + const templates = this._getTemplates(options); const states = []; try { @@ -945,6 +945,11 @@ class Backend { return handlebarsRenderDynamic(template, data); } + _getTemplates(options) { + const templates = options.anki.fieldTemplates; + return typeof templates === 'string' ? templates : this.defaultAnkiFieldTemplates; + } + static _getTabUrl(tab) { return new Promise((resolve) => { chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => { -- cgit v1.2.3 From ca033a87a0d302151b430acfdf9d480514c14aed Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 26 Apr 2020 22:33:50 +0300 Subject: Update Popup and DisplayFloat optionsContext from Frontend (#464) * set optionsContext from Frontend * update Popup+Display options on Frontend change * remove popup setOptions * only update DisplayFloat options from Frontend * fix optionsContext usage * fix preview frame arguments * keep Frontend URL up to date * cache url * fix preview frame * trigger modifyingProfileChange in correct places * remove async from function not using await * refactor optionsContext in Frontend --- ext/bg/js/search.js | 4 +- ext/bg/js/settings/popup-preview-frame-main.js | 5 +-- ext/bg/js/settings/popup-preview-frame.js | 22 ++++++++--- ext/bg/js/settings/popup-preview.js | 18 +++++++++ ext/bg/js/settings/profiles.js | 10 +++++ ext/fg/js/content-script-main.js | 31 +++++++++++---- ext/fg/js/float.js | 23 ++++++----- ext/fg/js/frontend.js | 55 ++++++++++++++------------ ext/fg/js/popup-proxy-host.js | 17 +++++--- ext/fg/js/popup-proxy.js | 19 +++++---- ext/fg/js/popup.js | 27 ++++++++----- ext/mixed/js/display.js | 2 - 12 files changed, 154 insertions(+), 79 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 684ea6d3..a5484fc3 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -76,6 +76,8 @@ class DisplaySearch extends Display { async prepare() { try { await super.prepare(); + await this.updateOptions(); + yomichan.on('optionsUpdated', () => this.updateOptions()); await this.queryParser.prepare(); const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); @@ -231,7 +233,7 @@ class DisplaySearch extends Display { this.setIntroVisible(!valid, animate); this.updateSearchButton(); if (valid) { - const {definitions} = await apiTermsFind(query, details, this.optionsContext); + const {definitions} = await apiTermsFind(query, details, this.getOptionsContext()); this.setContent('terms', {definitions, context: { focus: false, disableHistory: true, diff --git a/ext/bg/js/settings/popup-preview-frame-main.js b/ext/bg/js/settings/popup-preview-frame-main.js index 86c2814c..2ab6af6b 100644 --- a/ext/bg/js/settings/popup-preview-frame-main.js +++ b/ext/bg/js/settings/popup-preview-frame-main.js @@ -19,7 +19,6 @@ * SettingsPopupPreview */ -(async () => { - const instance = new SettingsPopupPreview(); - await instance.prepare(); +(() => { + new SettingsPopupPreview(); })(); diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 420a7c5a..05a2a41b 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -32,19 +32,24 @@ class SettingsPopupPreview { this.popupShown = false; this.themeChangeTimeout = null; this.textSource = null; + this.optionsContext = null; this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); this._windowMessageHandlers = new Map([ + ['prepare', ({optionsContext}) => this.prepare(optionsContext)], ['setText', ({text}) => this.setText(text)], ['setCustomCss', ({css}) => this.setCustomCss(css)], - ['setCustomOuterCss', ({css}) => this.setCustomOuterCss(css)] + ['setCustomOuterCss', ({css}) => this.setCustomOuterCss(css)], + ['updateOptionsContext', ({optionsContext}) => this.updateOptionsContext(optionsContext)] ]); - } - async prepare() { - // Setup events window.addEventListener('message', this.onMessage.bind(this), false); + } + + async prepare(optionsContext) { + this.optionsContext = optionsContext; + // Setup events document.querySelector('#theme-dark-checkbox').addEventListener('change', this.onThemeDarkCheckboxChanged.bind(this), false); // Overwrite API functions @@ -62,8 +67,9 @@ class SettingsPopupPreview { this.frontend = new Frontend(this.popup); + this.frontend.getOptionsContext = async () => this.optionsContext; this.frontend.setEnabled = () => {}; - this.frontend.searchClear = () => {}; + this.frontend.onSearchClear = () => {}; await this.frontend.prepare(); @@ -145,6 +151,12 @@ class SettingsPopupPreview { this.frontend.popup.setCustomOuterCss(css, false); } + async updateOptionsContext(optionsContext) { + this.optionsContext = optionsContext; + await this.frontend.updateOptions(); + await this.updateSearch(); + } + async updateSearch() { const exampleText = document.querySelector('#example-text'); if (exampleText === null) { return; } diff --git a/ext/bg/js/settings/popup-preview.js b/ext/bg/js/settings/popup-preview.js index 45bf531e..fdc3dd94 100644 --- a/ext/bg/js/settings/popup-preview.js +++ b/ext/bg/js/settings/popup-preview.js @@ -16,6 +16,7 @@ */ /* global + * getOptionsContext * wanakana */ @@ -60,6 +61,23 @@ function showAppearancePreview() { frame.contentWindow.postMessage({action, params}, targetOrigin); }); + const updateOptionsContext = () => { + const action = 'updateOptionsContext'; + const params = { + optionsContext: getOptionsContext() + }; + frame.contentWindow.postMessage({action, params}, targetOrigin); + }; + yomichan.on('modifyingProfileChange', updateOptionsContext); + + frame.addEventListener('load', () => { + const action = 'prepare'; + const params = { + optionsContext: getOptionsContext() + }; + frame.contentWindow.postMessage({action, params}, targetOrigin); + }); + container.append(frame); buttonContainer.remove(); settings.css('display', ''); diff --git a/ext/bg/js/settings/profiles.js b/ext/bg/js/settings/profiles.js index 867b17aa..3f4b1da7 100644 --- a/ext/bg/js/settings/profiles.js +++ b/ext/bg/js/settings/profiles.js @@ -190,6 +190,8 @@ async function onTargetProfileChanged() { currentProfileIndex = index; await profileOptionsUpdateTarget(optionsFull); + + yomichan.trigger('modifyingProfileChange'); } async function onProfileAdd() { @@ -197,9 +199,13 @@ async function onProfileAdd() { const profile = utilBackgroundIsolate(optionsFull.profiles[currentProfileIndex]); profile.name = profileOptionsCreateCopyName(profile.name, optionsFull.profiles, 100); optionsFull.profiles.push(profile); + currentProfileIndex = optionsFull.profiles.length - 1; + await profileOptionsUpdateTarget(optionsFull); await settingsSaveOptions(); + + yomichan.trigger('modifyingProfileChange'); } async function onProfileRemove(e) { @@ -238,6 +244,8 @@ async function onProfileRemoveConfirm() { await profileOptionsUpdateTarget(optionsFull); await settingsSaveOptions(); + + yomichan.trigger('modifyingProfileChange'); } function onProfileNameChanged() { @@ -263,6 +271,8 @@ async function onProfileMove(offset) { await profileOptionsUpdateTarget(optionsFull); await settingsSaveOptions(); + + yomichan.trigger('modifyingProfileChange'); } async function onProfileCopy() { diff --git a/ext/fg/js/content-script-main.js b/ext/fg/js/content-script-main.js index 14285536..0b852644 100644 --- a/ext/fg/js/content-script-main.js +++ b/ext/fg/js/content-script-main.js @@ -25,7 +25,7 @@ * apiOptionsGet */ -async function createIframePopupProxy(url, frameOffsetForwarder, setDisabled) { +async function createIframePopupProxy(frameOffsetForwarder, setDisabled) { const rootPopupInformationPromise = yomichan.getTemporaryListenerResult( chrome.runtime.onMessage, ({action, params}, {resolve}) => { @@ -39,7 +39,7 @@ async function createIframePopupProxy(url, frameOffsetForwarder, setDisabled) { const getFrameOffset = frameOffsetForwarder.getOffset.bind(frameOffsetForwarder); - const popup = new PopupProxy(popupId, 0, null, frameId, url, getFrameOffset, setDisabled); + const popup = new PopupProxy(popupId, 0, null, frameId, getFrameOffset, setDisabled); await popup.prepare(); return popup; @@ -54,8 +54,8 @@ async function getOrCreatePopup(depth) { return popup; } -async function createPopupProxy(depth, id, parentFrameId, url) { - const popup = new PopupProxy(null, depth + 1, id, parentFrameId, url); +async function createPopupProxy(depth, id, parentFrameId) { + const popup = new PopupProxy(null, depth + 1, id, parentFrameId); await popup.prepare(); return popup; @@ -86,8 +86,22 @@ async function createPopupProxy(depth, id, parentFrameId, url) { applyOptions(); }; + let urlUpdatedAt = 0; + let proxyHostUrlCached = url; + const getProxyHostUrl = async () => { + const now = Date.now(); + if (popups.proxy !== null && now - urlUpdatedAt > 500) { + proxyHostUrlCached = await popups.proxy.getHostUrl(); + urlUpdatedAt = now; + } + return proxyHostUrlCached; + }; + const applyOptions = async () => { - const optionsContext = {depth: isSearchPage ? 0 : depth, url}; + const optionsContext = { + depth: isSearchPage ? 0 : depth, + url: proxy ? await getProxyHostUrl() : window.location.href + }; const options = await apiOptionsGet(optionsContext); if (!proxy && frameOffsetForwarder === null) { @@ -97,10 +111,10 @@ async function createPopupProxy(depth, id, parentFrameId, url) { let popup; if (isIframe && options.general.showIframePopupsInRootFrame && DOM.getFullscreenElement() === null && iframePopupsInRootFrameAvailable) { - popup = popups.iframe || await createIframePopupProxy(url, frameOffsetForwarder, disableIframePopupsInRootFrame); + popup = popups.iframe || await createIframePopupProxy(frameOffsetForwarder, disableIframePopupsInRootFrame); popups.iframe = popup; } else if (proxy) { - popup = popups.proxy || await createPopupProxy(depth, id, parentFrameId, url); + popup = popups.proxy || await createPopupProxy(depth, id, parentFrameId); popups.proxy = popup; } else { popup = popups.normal || await getOrCreatePopup(depth); @@ -108,7 +122,8 @@ async function createPopupProxy(depth, id, parentFrameId, url) { } if (frontend === null) { - frontend = new Frontend(popup); + const getUrl = proxy ? getProxyHostUrl : null; + frontend = new Frontend(popup, getUrl); frontendPreparePromise = frontend.prepare(); await frontendPreparePromise; } else { diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 18d15a72..2a5eba83 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -29,11 +29,6 @@ class DisplayFloat extends Display { this._popupId = null; - this.optionsContext = { - depth: 0, - url: window.location.href - }; - this._orphaned = false; this._prepareInvoked = false; this._messageToken = null; @@ -51,10 +46,11 @@ class DisplayFloat extends Display { ]); this._windowMessageHandlers = new Map([ + ['setOptionsContext', ({optionsContext}) => this.setOptionsContext(optionsContext)], ['setContent', ({type, details}) => this.setContent(type, details)], ['clearAutoPlayTimer', () => this.clearAutoPlayTimer()], ['setCustomCss', ({css}) => this.setCustomCss(css)], - ['prepare', ({popupInfo, url, childrenSupported, scale}) => this.prepare(popupInfo, url, childrenSupported, scale)], + ['prepare', ({popupInfo, optionsContext, childrenSupported, scale}) => this.prepare(popupInfo, optionsContext, childrenSupported, scale)], ['setContentScale', ({scale}) => this.setContentScale(scale)] ]); @@ -62,18 +58,20 @@ class DisplayFloat extends Display { window.addEventListener('message', this.onMessage.bind(this), false); } - async prepare(popupInfo, url, childrenSupported, scale) { + async prepare(popupInfo, optionsContext, childrenSupported, scale) { if (this._prepareInvoked) { return; } this._prepareInvoked = true; - const {id, depth, parentFrameId} = popupInfo; + const {id, parentFrameId} = popupInfo; this._popupId = id; - this.optionsContext.depth = depth; - this.optionsContext.url = url; + + this.optionsContext = optionsContext; await super.prepare(); + await this.updateOptions(); if (childrenSupported) { + const {depth, url} = optionsContext; popupNestedInitialize(id, depth, parentFrameId, url); } @@ -158,6 +156,11 @@ class DisplayFloat extends Display { } } + async setOptionsContext(optionsContext) { + this.optionsContext = optionsContext; + await this.updateOptions(); + } + setContentScale(scale) { document.body.style.fontSize = `${scale}em`; } diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index eecfe2e1..46921d36 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -26,24 +26,23 @@ */ class Frontend extends TextScanner { - constructor(popup) { + constructor(popup, getUrl=null) { super( window, () => this.popup.isProxy() ? [] : [this.popup.getContainer()], [(x, y) => this.popup.containsPoint(x, y)] ); + this._id = yomichan.generateId(16); + this.popup = popup; + this._getUrl = getUrl; + this._disabledOverride = false; this.options = null; - this.optionsContext = { - depth: popup.depth, - url: popup.url - }; - this._pageZoomFactor = 1.0; this._contentScale = 1.0; this._orphaned = false; @@ -143,11 +142,12 @@ class Frontend extends TextScanner { async setPopup(popup) { this.onSearchClear(false); this.popup = popup; - await popup.setOptions(this.options); + await popup.setOptionsContext(await this.getOptionsContext(), this._id); } async updateOptions() { - this.options = await apiOptionsGet(this.getOptionsContext()); + const optionsContext = await this.getOptionsContext(); + this.options = await apiOptionsGet(optionsContext); this.setOptions(this.options, this._canEnable()); const ignoreNodes = ['.scan-disable', '.scan-disable *']; @@ -156,7 +156,7 @@ class Frontend extends TextScanner { } this.ignoreNodes = ignoreNodes.join(','); - await this.popup.setOptions(this.options); + await this.popup.setOptionsContext(optionsContext, this._id); this._updateContentScale(); @@ -170,19 +170,20 @@ class Frontend extends TextScanner { try { if (textSource !== null) { + const optionsContext = await this.getOptionsContext(); results = ( - await this.findTerms(textSource) || - await this.findKanji(textSource) + await this.findTerms(textSource, optionsContext) || + await this.findKanji(textSource, optionsContext) ); if (results !== null) { const focus = (cause === 'mouse'); - this.showContent(textSource, focus, results.definitions, results.type); + this.showContent(textSource, focus, results.definitions, results.type, optionsContext); } } } catch (e) { if (this._orphaned) { if (textSource !== null && this.options.scanning.modifier !== 'none') { - this._showPopupContent(textSource, 'orphaned'); + this._showPopupContent(textSource, await this.getOptionsContext(), 'orphaned'); } } else { this.onError(e); @@ -196,11 +197,12 @@ class Frontend extends TextScanner { return results; } - showContent(textSource, focus, definitions, type) { + showContent(textSource, focus, definitions, type, optionsContext) { + const {url} = optionsContext; const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); - const url = window.location.href; this._showPopupContent( textSource, + optionsContext, type, {definitions, context: {sentence, url, focus, disableHistory: true}} ); @@ -210,13 +212,13 @@ class Frontend extends TextScanner { return this._lastShowPromise; } - async findTerms(textSource) { + async findTerms(textSource, optionsContext) { this.setTextSourceScanLength(textSource, this.options.scanning.length); const searchText = textSource.text(); if (searchText.length === 0) { return null; } - const {definitions, length} = await apiTermsFind(searchText, {}, this.getOptionsContext()); + const {definitions, length} = await apiTermsFind(searchText, {}, optionsContext); if (definitions.length === 0) { return null; } textSource.setEndOffset(length); @@ -224,13 +226,13 @@ class Frontend extends TextScanner { return {definitions, type: 'terms'}; } - async findKanji(textSource) { + async findKanji(textSource, optionsContext) { this.setTextSourceScanLength(textSource, 1); const searchText = textSource.text(); if (searchText.length === 0) { return null; } - const definitions = await apiKanjiFind(searchText, this.getOptionsContext()); + const definitions = await apiKanjiFind(searchText, optionsContext); if (definitions.length === 0) { return null; } return {definitions, type: 'kanji'}; @@ -242,17 +244,20 @@ class Frontend extends TextScanner { super.onSearchClear(changeFocus); } - getOptionsContext() { - this.optionsContext.url = this.popup.url; - return this.optionsContext; + async getOptionsContext() { + const url = this._getUrl !== null ? await this._getUrl() : window.location.href; + const depth = this.popup.depth; + return {depth, url}; } - _showPopupContent(textSource, type=null, details=null) { + _showPopupContent(textSource, optionsContext, type=null, details=null) { + const context = {optionsContext, source: this._id}; this._lastShowPromise = this.popup.showContent( textSource.getRect(), textSource.getWritingMode(), type, - details + details, + context ); return this._lastShowPromise; } @@ -294,7 +299,7 @@ class Frontend extends TextScanner { async _updatePopupPosition() { const textSource = this.getCurrentTextSource(); if (textSource !== null && await this.popup.isVisible()) { - this._showPopupContent(textSource); + this._showPopupContent(textSource, await this.getOptionsContext()); } } diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 958462ff..d87553e6 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -37,7 +37,7 @@ class PopupProxyHost { this._apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${this._frameId}`, new Map([ ['getOrCreatePopup', this._onApiGetOrCreatePopup.bind(this)], - ['setOptions', this._onApiSetOptions.bind(this)], + ['setOptionsContext', this._onApiSetOptionsContext.bind(this)], ['hide', this._onApiHide.bind(this)], ['isVisible', this._onApiIsVisibleAsync.bind(this)], ['setVisibleOverride', this._onApiSetVisibleOverride.bind(this)], @@ -45,7 +45,8 @@ class PopupProxyHost { ['showContent', this._onApiShowContent.bind(this)], ['setCustomCss', this._onApiSetCustomCss.bind(this)], ['clearAutoPlayTimer', this._onApiClearAutoPlayTimer.bind(this)], - ['setContentScale', this._onApiSetContentScale.bind(this)] + ['setContentScale', this._onApiSetContentScale.bind(this)], + ['getHostUrl', this._onApiGetHostUrl.bind(this)] ])); } @@ -103,9 +104,9 @@ class PopupProxyHost { }; } - async _onApiSetOptions({id, options}) { + async _onApiSetOptionsContext({id, optionsContext, source}) { const popup = this._getPopup(id); - return await popup.setOptions(options); + return await popup.setOptionsContext(optionsContext, source); } async _onApiHide({id, changeFocus}) { @@ -129,11 +130,11 @@ class PopupProxyHost { return await popup.containsPoint(x, y); } - async _onApiShowContent({id, elementRect, writingMode, type, details}) { + async _onApiShowContent({id, elementRect, writingMode, type, details, context}) { const popup = this._getPopup(id); elementRect = PopupProxyHost._convertJsonRectToDOMRect(popup, elementRect); if (!PopupProxyHost._popupCanShow(popup)) { return; } - return await popup.showContent(elementRect, writingMode, type, details); + return await popup.showContent(elementRect, writingMode, type, details, context); } async _onApiSetCustomCss({id, css}) { @@ -151,6 +152,10 @@ class PopupProxyHost { return popup.setContentScale(scale); } + async _onApiGetHostUrl() { + return window.location.href; + } + // Private functions _getPopup(id) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 3af83db2..cd3c1bc9 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -20,12 +20,11 @@ */ class PopupProxy { - constructor(id, depth, parentId, parentFrameId, url, getFrameOffset=null, setDisabled=null) { + constructor(id, depth, parentId, parentFrameId, getFrameOffset=null, setDisabled=null) { this._parentId = parentId; this._parentFrameId = parentFrameId; this._id = id; this._depth = depth; - this._url = url; this._apiSender = new FrontendApiSender(); this._getFrameOffset = getFrameOffset; this._setDisabled = setDisabled; @@ -49,10 +48,6 @@ class PopupProxy { return this._depth; } - get url() { - return this._url; - } - // Public functions async prepare() { @@ -64,8 +59,8 @@ class PopupProxy { return true; } - async setOptions(options) { - return await this._invokeHostApi('setOptions', {id: this._id, options}); + async setOptionsContext(optionsContext, source) { + return await this._invokeHostApi('setOptionsContext', {id: this._id, optionsContext, source}); } hide(changeFocus) { @@ -88,14 +83,14 @@ class PopupProxy { return await this._invokeHostApi('containsPoint', {id: this._id, x, y}); } - async showContent(elementRect, writingMode, type=null, details=null) { + async showContent(elementRect, writingMode, type, details, context) { let {x, y, width, height} = elementRect; if (this._getFrameOffset !== null) { await this._updateFrameOffset(); [x, y] = this._applyFrameOffset(x, y); } elementRect = {x, y, width, height}; - return await this._invokeHostApi('showContent', {id: this._id, elementRect, writingMode, type, details}); + return await this._invokeHostApi('showContent', {id: this._id, elementRect, writingMode, type, details, context}); } async setCustomCss(css) { @@ -110,6 +105,10 @@ class PopupProxy { this._invokeHostApi('setContentScale', {id: this._id, scale}); } + async getHostUrl() { + return await this._invokeHostApi('getHostUrl', {}); + } + // Private _invokeHostApi(action, params={}) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index ae158263..e735431b 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -19,6 +19,7 @@ * DOM * apiGetMessageToken * apiInjectStylesheet + * apiOptionsGet */ class Popup { @@ -33,10 +34,12 @@ class Popup { this._visible = false; this._visibleOverride = null; this._options = null; + this._optionsContext = null; this._contentScale = 1.0; this._containerSizeContentScale = null; this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); this._messageToken = null; + this._previousOptionsContextSource = null; this._container = document.createElement('iframe'); this._container.className = 'yomichan-float'; @@ -72,19 +75,20 @@ class Popup { return this._frameId; } - get url() { - return window.location.href; - } - // Public functions isProxy() { return false; } - async setOptions(options) { - this._options = options; + async setOptionsContext(optionsContext, source) { + this._optionsContext = optionsContext; + this._previousOptionsContextSource = source; + + this._options = await apiOptionsGet(optionsContext); this.updateTheme(); + + this._invokeApi('setOptionsContext', {optionsContext}); } hide(changeFocus) { @@ -120,8 +124,14 @@ class Popup { return false; } - async showContent(elementRect, writingMode, type=null, details=null) { + async showContent(elementRect, writingMode, type, details, context) { if (this._options === null) { throw new Error('Options not assigned'); } + + const {optionsContext, source} = context; + if (source !== this._previousOptionsContextSource) { + await this.setOptionsContext(optionsContext, source); + } + await this._show(elementRect, writingMode); if (type === null) { return; } this._invokeApi('setContent', {type, details}); @@ -219,10 +229,9 @@ class Popup { this._invokeApi('prepare', { popupInfo: { id: this._id, - depth: this._depth, parentFrameId }, - url: this.url, + optionsContext: this._optionsContext, childrenSupported: this._childrenSupported, scale: this._contentScale }); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 9587ec3b..70b7fcd3 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -177,8 +177,6 @@ class Display { async prepare() { await yomichan.prepare(); await this.displayGenerator.prepare(); - await this.updateOptions(); - yomichan.on('optionsUpdated', () => this.updateOptions()); } onError(_error) { -- cgit v1.2.3 From 5b96559df819f496b39acb75c679f6b3d8c8e65d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Apr 2020 16:55:25 -0400 Subject: Error logging refactoring (#454) * Create new logging methods on yomichan object * Use new yomichan.logError instead of global logError * Remove old logError * Handle unhandledrejection events * Add addEventListener stub * Update log function * Update error conversion to support more types * Add log event * Add API log function * Log errors to the backend * Make error/warning logs update the badge * Clear log error indicator on extension button click * Log correct URL on the background page * Fix incorrect error conversion * Remove unhandledrejection handling Firefox doesn't support it properly. * Remove unused argument type from log function * Improve function name * Change console.warn to yomichan.logWarning * Move log forwarding initialization into main scripts --- .eslintrc.json | 1 - ext/bg/js/backend.js | 50 ++++++++++- ext/bg/js/context-main.js | 5 ++ ext/bg/js/database.js | 2 +- ext/bg/js/mecab.js | 2 +- ext/bg/js/search-main.js | 2 + ext/bg/js/search-query-parser.js | 2 +- ext/bg/js/search.js | 2 +- ext/bg/js/settings/backup.js | 2 +- ext/bg/js/settings/dictionaries.js | 2 +- ext/bg/js/settings/main.js | 2 + ext/bg/js/settings/popup-preview-frame-main.js | 2 + ext/fg/js/content-script-main.js | 2 + ext/fg/js/float-main.js | 2 + ext/fg/js/float.js | 2 +- ext/fg/js/frontend-api-sender.js | 8 +- ext/fg/js/popup-proxy.js | 2 +- ext/mixed/js/api.js | 22 +++++ ext/mixed/js/core.js | 112 +++++++++++++++++++------ ext/mixed/js/text-scanner.js | 2 +- test/test-database.js | 5 +- 21 files changed, 186 insertions(+), 45 deletions(-) (limited to 'ext/bg/js') diff --git a/.eslintrc.json b/.eslintrc.json index 8882cb42..78fec27c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -80,7 +80,6 @@ "yomichan": "readonly", "errorToJson": "readonly", "jsonToError": "readonly", - "logError": "readonly", "isObject": "readonly", "hasOwn": "readonly", "toIterable": "readonly", diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 693a9ad6..3c47b14e 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -78,6 +78,7 @@ class Backend { this._isPrepared = false; this._prepareError = false; this._badgePrepareDelayTimer = null; + this._logErrorLevel = null; this._messageHandlers = new Map([ ['yomichanCoreReady', {handler: this._onApiYomichanCoreReady.bind(this), async: false}], @@ -112,7 +113,9 @@ class Backend { ['getDictionaryInfo', {handler: this._onApiGetDictionaryInfo.bind(this), async: true}], ['getDictionaryCounts', {handler: this._onApiGetDictionaryCounts.bind(this), async: true}], ['purgeDatabase', {handler: this._onApiPurgeDatabase.bind(this), async: true}], - ['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}] + ['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}], + ['log', {handler: this._onApiLog.bind(this), async: false}], + ['logIndicatorClear', {handler: this._onApiLogIndicatorClear.bind(this), async: false}] ]); this._commandHandlers = new Map([ @@ -164,7 +167,7 @@ class Backend { this._isPrepared = true; } catch (e) { this._prepareError = true; - logError(e); + yomichan.logError(e); throw e; } finally { if (this._badgePrepareDelayTimer !== null) { @@ -260,7 +263,7 @@ class Backend { this.options = JsonSchema.getValidValueOrDefault(this.optionsSchema, utilIsolate(options)); } catch (e) { // This shouldn't happen, but catch errors just in case of bugs - logError(e); + yomichan.logError(e); } } @@ -767,8 +770,34 @@ class Backend { return await this.database.getMedia(targets); } + _onApiLog({error, level, context}) { + yomichan.log(jsonToError(error), level, context); + + const levelValue = this._getErrorLevelValue(level); + if (levelValue <= this._getErrorLevelValue(this._logErrorLevel)) { return; } + + this._logErrorLevel = level; + this._updateBadge(); + } + + _onApiLogIndicatorClear() { + if (this._logErrorLevel === null) { return; } + this._logErrorLevel = null; + this._updateBadge(); + } + // Command handlers + _getErrorLevelValue(errorLevel) { + switch (errorLevel) { + case 'info': return 0; + case 'debug': return 0; + case 'warn': return 1; + case 'error': return 2; + default: return 0; + } + } + async _onCommandSearch(params) { const {mode='existingOrNewTab', query} = params || {}; @@ -890,7 +919,20 @@ class Backend { let color = null; let status = null; - if (!this._isPrepared) { + if (this._logErrorLevel !== null) { + switch (this._logErrorLevel) { + case 'error': + text = '!!'; + color = '#f04e4e'; + status = 'Error'; + break; + default: // 'warn' + text = '!'; + color = '#f0ad4e'; + status = 'Warning'; + break; + } + } else if (!this._isPrepared) { if (this._prepareError) { text = '!!'; color = '#f04e4e'; diff --git a/ext/bg/js/context-main.js b/ext/bg/js/context-main.js index e2086a96..dbba0272 100644 --- a/ext/bg/js/context-main.js +++ b/ext/bg/js/context-main.js @@ -17,7 +17,9 @@ /* global * apiCommandExec + * apiForwardLogsToBackend * apiGetEnvironmentInfo + * apiLogIndicatorClear * apiOptionsGet */ @@ -52,8 +54,11 @@ function setupButtonEvents(selector, command, url) { } async function mainInner() { + apiForwardLogsToBackend(); await yomichan.prepare(); + await apiLogIndicatorClear(); + showExtensionInfo(); apiGetEnvironmentInfo().then(({browser}) => { diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 16612403..a94aa720 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -104,7 +104,7 @@ class Database { }); return true; } catch (e) { - logError(e); + yomichan.logError(e); return false; } } diff --git a/ext/bg/js/mecab.js b/ext/bg/js/mecab.js index 597dceae..815ee860 100644 --- a/ext/bg/js/mecab.js +++ b/ext/bg/js/mecab.js @@ -24,7 +24,7 @@ class Mecab { } onError(error) { - logError(error, false); + yomichan.logError(error); } async checkVersion() { diff --git a/ext/bg/js/search-main.js b/ext/bg/js/search-main.js index 38b6d99a..5e4d7a20 100644 --- a/ext/bg/js/search-main.js +++ b/ext/bg/js/search-main.js @@ -17,6 +17,7 @@ /* global * DisplaySearch + * apiForwardLogsToBackend * apiOptionsGet */ @@ -53,6 +54,7 @@ function injectSearchFrontend() { } (async () => { + apiForwardLogsToBackend(); await yomichan.prepare(); const displaySearch = new DisplaySearch(); diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index eb3b681c..0001c9ff 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -45,7 +45,7 @@ class QueryParser extends TextScanner { } onError(error) { - logError(error, false); + yomichan.logError(error); } onClick(e) { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index a5484fc3..cbd7b562 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -122,7 +122,7 @@ class DisplaySearch extends Display { } onError(error) { - logError(error, true); + yomichan.logError(error); } onSearchClear() { diff --git a/ext/bg/js/settings/backup.js b/ext/bg/js/settings/backup.js index bdfef658..faf4e592 100644 --- a/ext/bg/js/settings/backup.js +++ b/ext/bg/js/settings/backup.js @@ -133,7 +133,7 @@ async function _settingsImportSetOptionsFull(optionsFull) { } function _showSettingsImportError(error) { - logError(error); + yomichan.logError(error); document.querySelector('#settings-import-error-modal-message').textContent = `${error}`; $('#settings-import-error-modal').modal('show'); } diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 7eed4273..50add4c7 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -554,7 +554,7 @@ function dictionaryErrorsShow(errors) { if (errors !== null && errors.length > 0) { const uniqueErrors = new Map(); for (let e of errors) { - logError(e); + yomichan.logError(e); e = dictionaryErrorToString(e); let count = uniqueErrors.get(e); if (typeof count === 'undefined') { diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 308e92eb..f03cc631 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -21,6 +21,7 @@ * ankiInitialize * ankiTemplatesInitialize * ankiTemplatesUpdateValue + * apiForwardLogsToBackend * apiOptionsSave * appearanceInitialize * audioSettingsInitialize @@ -284,6 +285,7 @@ function showExtensionInformation() { async function onReady() { + apiForwardLogsToBackend(); await yomichan.prepare(); showExtensionInformation(); diff --git a/ext/bg/js/settings/popup-preview-frame-main.js b/ext/bg/js/settings/popup-preview-frame-main.js index 2ab6af6b..8228125f 100644 --- a/ext/bg/js/settings/popup-preview-frame-main.js +++ b/ext/bg/js/settings/popup-preview-frame-main.js @@ -17,8 +17,10 @@ /* global * SettingsPopupPreview + * apiForwardLogsToBackend */ (() => { + apiForwardLogsToBackend(); new SettingsPopupPreview(); })(); diff --git a/ext/fg/js/content-script-main.js b/ext/fg/js/content-script-main.js index 0b852644..277e6567 100644 --- a/ext/fg/js/content-script-main.js +++ b/ext/fg/js/content-script-main.js @@ -22,6 +22,7 @@ * PopupProxy * PopupProxyHost * apiBroadcastTab + * apiForwardLogsToBackend * apiOptionsGet */ @@ -62,6 +63,7 @@ async function createPopupProxy(depth, id, parentFrameId) { } (async () => { + apiForwardLogsToBackend(); await yomichan.prepare(); const data = window.frontendInitializationData || {}; diff --git a/ext/fg/js/float-main.js b/ext/fg/js/float-main.js index f056f707..5ef4b07c 100644 --- a/ext/fg/js/float-main.js +++ b/ext/fg/js/float-main.js @@ -17,6 +17,7 @@ /* global * DisplayFloat + * apiForwardLogsToBackend * apiOptionsGet */ @@ -68,5 +69,6 @@ async function popupNestedInitialize(id, depth, parentFrameId, url) { } (async () => { + apiForwardLogsToBackend(); new DisplayFloat(); })(); diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 2a5eba83..fd3b92cc 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -84,7 +84,7 @@ class DisplayFloat extends Display { if (this._orphaned) { this.setContent('orphaned'); } else { - logError(error, true); + yomichan.logError(error); } } diff --git a/ext/fg/js/frontend-api-sender.js b/ext/fg/js/frontend-api-sender.js index 1d539cab..0ad3f085 100644 --- a/ext/fg/js/frontend-api-sender.js +++ b/ext/fg/js/frontend-api-sender.js @@ -81,12 +81,12 @@ class FrontendApiSender { onAck(id) { const info = this.callbacks.get(id); if (typeof info === 'undefined') { - console.warn(`ID ${id} not found for ack`); + yomichan.logWarning(new Error(`ID ${id} not found for ack`)); return; } if (info.ack) { - console.warn(`Request ${id} already ack'd`); + yomichan.logWarning(new Error(`Request ${id} already ack'd`)); return; } @@ -98,12 +98,12 @@ class FrontendApiSender { onResult(id, data) { const info = this.callbacks.get(id); if (typeof info === 'undefined') { - console.warn(`ID ${id} not found`); + yomichan.logWarning(new Error(`ID ${id} not found`)); return; } if (!info.ack) { - console.warn(`Request ${id} not ack'd`); + yomichan.logWarning(new Error(`Request ${id} not ack'd`)); return; } diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index cd3c1bc9..93418202 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -148,7 +148,7 @@ class PopupProxy { } this._frameOffsetUpdatedAt = now; } catch (e) { - logError(e); + yomichan.logError(e); } finally { this._frameOffsetPromise = null; } diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 52f41646..afd68aa2 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -144,6 +144,14 @@ function apiGetMedia(targets) { return _apiInvoke('getMedia', {targets}); } +function apiLog(error, level, context) { + return _apiInvoke('log', {error, level, context}); +} + +function apiLogIndicatorClear() { + return _apiInvoke('logIndicatorClear'); +} + function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { @@ -171,3 +179,17 @@ function _apiInvoke(action, params={}) { function _apiCheckLastError() { // NOP } + +let _apiForwardLogsToBackendEnabled = false; +function apiForwardLogsToBackend() { + if (_apiForwardLogsToBackendEnabled) { return; } + _apiForwardLogsToBackendEnabled = true; + + yomichan.on('log', async ({error, level, context}) => { + try { + await apiLog(errorToJson(error), level, context); + } catch (e) { + // NOP + } + }); +} diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 6a3298fc..fbe9943a 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -52,15 +52,28 @@ if (EXTENSION_IS_BROWSER_EDGE) { */ function errorToJson(error) { + try { + if (isObject(error)) { + return { + name: error.name, + message: error.message, + stack: error.stack, + data: error.data + }; + } + } catch (e) { + // NOP + } return { - name: error.name, - message: error.message, - stack: error.stack, - data: error.data + value: error, + hasValue: true }; } function jsonToError(jsonError) { + if (jsonError.hasValue) { + return jsonError.value; + } const error = new Error(jsonError.message); error.name = jsonError.name; error.stack = jsonError.stack; @@ -68,28 +81,6 @@ function jsonToError(jsonError) { return error; } -function logError(error, alert) { - const manifest = chrome.runtime.getManifest(); - let errorMessage = `${manifest.name} v${manifest.version} has encountered an error.\n`; - errorMessage += `Originating URL: ${window.location.href}\n`; - - const errorString = `${error.toString ? error.toString() : error}`; - const stack = `${error.stack}`.trimRight(); - if (!stack.startsWith(errorString)) { errorMessage += `${errorString}\n`; } - errorMessage += stack; - - const data = error.data; - if (typeof data !== 'undefined') { errorMessage += `\nData: ${JSON.stringify(data, null, 4)}`; } - - errorMessage += '\n\nIssues can be reported at https://github.com/FooSoft/yomichan/issues'; - - console.error(errorMessage); - - if (alert) { - window.alert(`${errorString}\n\nCheck the developer console for more details.`); - } -} - /* * Common helpers @@ -361,8 +352,77 @@ const yomichan = (() => { }); } + logWarning(error) { + this.log(error, 'warn'); + } + + logError(error) { + this.log(error, 'error'); + } + + log(error, level, context=null) { + if (!isObject(context)) { + context = this._getLogContext(); + } + + let errorString; + try { + errorString = error.toString(); + if (/^\[object \w+\]$/.test(errorString)) { + errorString = JSON.stringify(error); + } + } catch (e) { + errorString = `${error}`; + } + + let errorStack; + try { + errorStack = (typeof error.stack === 'string' ? error.stack.trimRight() : ''); + } catch (e) { + errorStack = ''; + } + + let errorData; + try { + errorData = error.data; + } catch (e) { + // NOP + } + + if (errorStack.startsWith(errorString)) { + errorString = errorStack; + } else if (errorStack.length > 0) { + errorString += `\n${errorStack}`; + } + + const manifest = chrome.runtime.getManifest(); + let message = `${manifest.name} v${manifest.version} has encountered a problem.`; + message += `\nOriginating URL: ${context.url}\n`; + message += errorString; + if (typeof errorData !== 'undefined') { + message += `\nData: ${JSON.stringify(errorData, null, 4)}`; + } + message += '\n\nIssues can be reported at https://github.com/FooSoft/yomichan/issues'; + + switch (level) { + case 'info': console.info(message); break; + case 'debug': console.debug(message); break; + case 'warn': console.warn(message); break; + case 'error': console.error(message); break; + default: console.log(message); break; + } + + this.trigger('log', {error, level, context}); + } + // Private + _getLogContext() { + return { + url: window.location.href + }; + } + _onMessage({action, params}, sender, callback) { const handler = this._messageHandlers.get(action); if (typeof handler !== 'function') { return false; } diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index 0cd12cd7..1c32714b 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -201,7 +201,7 @@ class TextScanner { } onError(error) { - logError(error, false); + yomichan.logError(error); } async scanTimerWait() { diff --git a/test/test-database.js b/test/test-database.js index e9ec3f0b..3684051b 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -145,7 +145,10 @@ const vm = new VM({ XMLHttpRequest, indexedDB: global.indexedDB, IDBKeyRange: global.IDBKeyRange, - JSZip: yomichanTest.JSZip + JSZip: yomichanTest.JSZip, + addEventListener() { + // NOP + } }); vm.context.window = vm.context; -- cgit v1.2.3 From 887d769786f2909dbd74e3465cef3551b780a49b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Apr 2020 16:56:34 -0400 Subject: Use dynamicLoader for main (#481) * Update style of search-main and float-main to have better parity * Use dynamicLoader to inject scripts and CSS --- ext/bg/js/search-main.js | 41 +++++++++++------------------------------ ext/bg/search.html | 1 + ext/fg/float.html | 1 + ext/fg/js/float-main.js | 30 ++++++++---------------------- 4 files changed, 21 insertions(+), 52 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/search-main.js b/ext/bg/js/search-main.js index 5e4d7a20..1075d46e 100644 --- a/ext/bg/js/search-main.js +++ b/ext/bg/js/search-main.js @@ -19,10 +19,14 @@ * DisplaySearch * apiForwardLogsToBackend * apiOptionsGet + * dynamicLoader */ -function injectSearchFrontend() { - const scriptSrcs = [ +async function injectSearchFrontend() { + dynamicLoader.loadStyles([ + '/fg/css/client.css' + ]); + await dynamicLoader.loadScripts([ '/mixed/js/text-scanner.js', '/fg/js/frontend-api-receiver.js', '/fg/js/frame-offset-forwarder.js', @@ -30,27 +34,7 @@ function injectSearchFrontend() { '/fg/js/popup-proxy-host.js', '/fg/js/frontend.js', '/fg/js/content-script-main.js' - ]; - for (const src of scriptSrcs) { - const node = document.querySelector(`script[src='${src}']`); - if (node !== null) { continue; } - - const script = document.createElement('script'); - script.async = false; - script.src = src; - document.body.appendChild(script); - } - - const styleSrcs = [ - '/fg/css/client.css' - ]; - for (const src of styleSrcs) { - const style = document.createElement('link'); - style.rel = 'stylesheet'; - style.type = 'text/css'; - style.href = src; - document.head.appendChild(style); - } + ]); } (async () => { @@ -63,18 +47,15 @@ function injectSearchFrontend() { let optionsApplied = false; const applyOptions = async () => { - const optionsContext = { - depth: 0, - url: window.location.href - }; + const optionsContext = {depth: 0, url: window.location.href}; const options = await apiOptionsGet(optionsContext); if (!options.scanning.enableOnSearchPage || optionsApplied) { return; } + optionsApplied = true; + yomichan.off('optionsUpdated', applyOptions); window.frontendInitializationData = {depth: 1, proxy: false, isSearchPage: true}; - injectSearchFrontend(); - - yomichan.off('optionsUpdated', applyOptions); + await injectSearchFrontend(); }; yomichan.on('optionsUpdated', applyOptions); diff --git a/ext/bg/search.html b/ext/bg/search.html index 8ed6c838..52915b76 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -86,6 +86,7 @@ + diff --git a/ext/fg/float.html b/ext/fg/float.html index deb9e9d2..6f37de52 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -52,6 +52,7 @@ + diff --git a/ext/fg/js/float-main.js b/ext/fg/js/float-main.js index 5ef4b07c..e7e50a54 100644 --- a/ext/fg/js/float-main.js +++ b/ext/fg/js/float-main.js @@ -19,23 +19,18 @@ * DisplayFloat * apiForwardLogsToBackend * apiOptionsGet + * dynamicLoader */ -function injectPopupNested() { - const scriptSrcs = [ +async function injectPopupNested() { + await dynamicLoader.loadScripts([ '/mixed/js/text-scanner.js', '/fg/js/frontend-api-sender.js', '/fg/js/popup.js', '/fg/js/popup-proxy.js', '/fg/js/frontend.js', '/fg/js/content-script-main.js' - ]; - for (const src of scriptSrcs) { - const script = document.createElement('script'); - script.async = false; - script.src = src; - document.body.appendChild(script); - } + ]); } async function popupNestedInitialize(id, depth, parentFrameId, url) { @@ -44,23 +39,14 @@ async function popupNestedInitialize(id, depth, parentFrameId, url) { const applyOptions = async () => { const optionsContext = {depth, url}; const options = await apiOptionsGet(optionsContext); - const popupNestingMaxDepth = options.scanning.popupNestingMaxDepth; - - const maxPopupDepthExceeded = !( - typeof popupNestingMaxDepth === 'number' && - typeof depth === 'number' && - depth < popupNestingMaxDepth - ); - if (maxPopupDepthExceeded || optionsApplied) { - return; - } + const maxPopupDepthExceeded = !(typeof depth === 'number' && depth < options.scanning.popupNestingMaxDepth); + if (maxPopupDepthExceeded || optionsApplied) { return; } optionsApplied = true; + yomichan.off('optionsUpdated', applyOptions); window.frontendInitializationData = {id, depth, parentFrameId, url, proxy: true}; - injectPopupNested(); - - yomichan.off('optionsUpdated', applyOptions); + await injectPopupNested(); }; yomichan.on('optionsUpdated', applyOptions); -- cgit v1.2.3 From 48c7010f4ea8daafd30e5650625c377affa0cecd Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Apr 2020 18:10:37 -0400 Subject: Frontend refactor (part 1) (#484) * Remove _getVisualViewportScale * Use super's mouse event listener definitions * Remove redundant override * Remove getTouchEventListeners override * Rename Display.onSearchClear to onEscape * Change onSearchClear to clearSelection and use an event * Update how text is marked for selection and deselection * Replace onError with yomichan.logError * Update setEnabled to refresh all event listeners --- ext/bg/js/search-query-parser.js | 25 ++--------------- ext/bg/js/search.js | 2 +- ext/bg/js/settings/popup-preview-frame.js | 2 +- ext/fg/js/float.js | 2 +- ext/fg/js/frontend.js | 26 ++++++++--------- ext/mixed/js/display.js | 4 +-- ext/mixed/js/text-scanner.js | 46 +++++++++++++++---------------- 7 files changed, 42 insertions(+), 65 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 0001c9ff..3215f8e4 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -44,12 +44,7 @@ class QueryParser extends TextScanner { await this.queryParserGenerator.prepare(); } - onError(error) { - yomichan.logError(error); - } - - onClick(e) { - super.onClick(e); + onClick2(e) { this.searchAt(e.clientX, e.clientY, 'click'); } @@ -84,22 +79,8 @@ class QueryParser extends TextScanner { getMouseEventListeners() { return [ - [this.node, 'click', this.onClick.bind(this)], - [this.node, 'mousedown', this.onMouseDown.bind(this)], - [this.node, 'mousemove', this.onMouseMove.bind(this)], - [this.node, 'mouseover', this.onMouseOver.bind(this)], - [this.node, 'mouseout', this.onMouseOut.bind(this)] - ]; - } - - getTouchEventListeners() { - return [ - [this.node, 'auxclick', this.onAuxClick.bind(this)], - [this.node, 'touchstart', this.onTouchStart.bind(this)], - [this.node, 'touchend', this.onTouchEnd.bind(this)], - [this.node, 'touchcancel', this.onTouchCancel.bind(this)], - [this.node, 'touchmove', this.onTouchMove.bind(this), {passive: false}], - [this.node, 'contextmenu', this.onContextMenu.bind(this)] + ...super.getMouseEventListeners(), + [this.node, 'click', this.onClick2.bind(this)] ]; } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index cbd7b562..b7d2eed8 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -125,7 +125,7 @@ class DisplaySearch extends Display { yomichan.logError(error); } - onSearchClear() { + onEscape() { if (this.query === null) { return; } diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 05a2a41b..e73c04a0 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -69,7 +69,7 @@ class SettingsPopupPreview { this.frontend.getOptionsContext = async () => this.optionsContext; this.frontend.setEnabled = () => {}; - this.frontend.onSearchClear = () => {}; + this.frontend.clearSelection = () => {}; await this.frontend.prepare(); diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index fd3b92cc..294093cd 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -92,7 +92,7 @@ class DisplayFloat extends Display { this._orphaned = true; } - onSearchClear() { + onEscape() { window.parent.postMessage('popupClose', '*'); } diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 46921d36..50f52724 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -49,7 +49,7 @@ class Frontend extends TextScanner { this._lastShowPromise = Promise.resolve(); this._windowMessageHandlers = new Map([ - ['popupClose', () => this.onSearchClear(true)], + ['popupClose', () => this.clearSelection(false)], ['selectionCopy', () => document.execCommand('copy')] ]); @@ -79,10 +79,12 @@ class Frontend extends TextScanner { yomichan.on('zoomChanged', this.onZoomChanged.bind(this)); chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); + this.on('clearSelection', this.onClearSelection.bind(this)); + this._updateContentScale(); this._broadcastRootPopupInformation(); } catch (e) { - this.onError(e); + yomichan.logError(e); } } @@ -140,7 +142,7 @@ class Frontend extends TextScanner { } async setPopup(popup) { - this.onSearchClear(false); + this.clearSelection(true); this.popup = popup; await popup.setOptionsContext(await this.getOptionsContext(), this._id); } @@ -186,11 +188,11 @@ class Frontend extends TextScanner { this._showPopupContent(textSource, await this.getOptionsContext(), 'orphaned'); } } else { - this.onError(e); + yomichan.logError(e); } } finally { if (results === null && this.options.scanning.autoHideResults) { - this.onSearchClear(true); + this.clearSelection(false); } } @@ -238,10 +240,9 @@ class Frontend extends TextScanner { return {definitions, type: 'kanji'}; } - onSearchClear(changeFocus) { - this.popup.hide(changeFocus); + onClearSelection({passive}) { + this.popup.hide(!passive); this.popup.clearAutoPlayTimer(); - super.onSearchClear(changeFocus); } async getOptionsContext() { @@ -269,7 +270,9 @@ class Frontend extends TextScanner { contentScale /= this._pageZoomFactor; } if (popupScaleRelativeToVisualViewport) { - contentScale /= Frontend._getVisualViewportScale(); + const visualViewport = window.visualViewport; + const visualViewportScale = (visualViewport !== null && typeof visualViewport === 'object' ? visualViewport.scale : 1.0); + contentScale /= visualViewportScale; } if (contentScale === this._contentScale) { return; } @@ -302,9 +305,4 @@ class Frontend extends TextScanner { this._showPopupContent(textSource, await this.getOptionsContext()); } } - - static _getVisualViewportScale() { - const visualViewport = window.visualViewport; - return visualViewport !== null && typeof visualViewport === 'object' ? visualViewport.scale : 1.0; - } } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 70b7fcd3..32081c70 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -69,7 +69,7 @@ class Display { this._onKeyDownHandlers = new Map([ ['Escape', () => { - this.onSearchClear(); + this.onEscape(); return true; }], ['PageUp', (e) => { @@ -183,7 +183,7 @@ class Display { throw new Error('Override me'); } - onSearchClear() { + onEscape() { throw new Error('Override me'); } diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index 1c32714b..c582ccd8 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -21,8 +21,9 @@ * docRangeFromPoint */ -class TextScanner { +class TextScanner extends EventDispatcher { constructor(node, ignoreElements, ignorePoints) { + super(); this.node = node; this.ignoreElements = ignoreElements; this.ignorePoints = ignorePoints; @@ -32,6 +33,7 @@ class TextScanner { this.scanTimerPromise = null; this.causeCurrent = null; this.textSourceCurrent = null; + this.textSourceCurrentSelected = false; this.pendingLookup = false; this.options = null; @@ -92,7 +94,7 @@ class TextScanner { if (DOM.isMouseButtonDown(e, 'primary')) { this.scanTimerClear(); - this.onSearchClear(true); + this.clearSelection(false); } } @@ -200,10 +202,6 @@ class TextScanner { throw new Error('Override me'); } - onError(error) { - yomichan.logError(error); - } - async scanTimerWait() { const delay = this.options.scanning.delay; const promise = promiseTimeout(delay, true); @@ -225,17 +223,12 @@ class TextScanner { } setEnabled(enabled, canEnable) { - if (enabled && canEnable) { - if (!this.enabled) { - this.hookEvents(); - this.enabled = true; - } + this.eventListeners.removeAllEventListeners(); + this.enabled = enabled && canEnable; + if (this.enabled) { + this.hookEvents(); } else { - if (this.enabled) { - this.eventListeners.removeAllEventListeners(); - this.enabled = false; - } - this.onSearchClear(false); + this.clearSelection(true); } } @@ -300,10 +293,7 @@ class TextScanner { const result = await this.onSearchSource(textSource, cause); if (result !== null) { this.causeCurrent = cause; - this.textSourceCurrent = textSource; - if (this.options.scanning.selectText) { - textSource.select(); - } + this.setCurrentTextSource(textSource); } this.pendingLookup = false; } finally { @@ -312,7 +302,7 @@ class TextScanner { } } } catch (e) { - this.onError(e); + yomichan.logError(e); } } @@ -333,13 +323,15 @@ class TextScanner { } } - onSearchClear(_) { + clearSelection(passive) { if (this.textSourceCurrent !== null) { - if (this.options.scanning.selectText) { + if (this.textSourceCurrentSelected) { this.textSourceCurrent.deselect(); } this.textSourceCurrent = null; + this.textSourceCurrentSelected = false; } + this.trigger('clearSelection', {passive}); } getCurrentTextSource() { @@ -347,7 +339,13 @@ class TextScanner { } setCurrentTextSource(textSource) { - return this.textSourceCurrent = textSource; + this.textSourceCurrent = textSource; + if (this.options.scanning.selectText) { + this.textSourceCurrent.select(); + this.textSourceCurrentSelected = true; + } else { + this.textSourceCurrentSelected = false; + } } static isScanningModifierPressed(scanningModifier, mouseEvent) { -- cgit v1.2.3 From 0956634d61ef2b6202645ec4b502239573c2e743 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Apr 2020 18:10:59 -0400 Subject: Add duplicateScope: 'deck' option (#476) * Add duplicateScope: 'deck' option * Add option to control duplicate scope * Use duplicateScope for findNoteIds * Update location of quotes --- ext/bg/data/options-schema.json | 6 ++++++ ext/bg/js/anki-note-builder.js | 5 ++++- ext/bg/js/anki.js | 15 +++++++-------- ext/bg/js/backend.js | 2 +- ext/bg/js/options.js | 1 + ext/bg/js/settings/main.js | 2 ++ ext/bg/settings.html | 8 ++++++++ 7 files changed, 29 insertions(+), 10 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index 4f9e694d..8622f16b 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -492,6 +492,7 @@ "screenshot", "terms", "kanji", + "duplicateScope", "fieldTemplates" ], "properties": { @@ -587,6 +588,11 @@ } } }, + "duplicateScope": { + "type": "string", + "default": "collection", + "enum": ["collection", "deck"] + }, "fieldTemplates": { "type": ["string", "null"], "default": null diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index 9bab095d..dc1e9427 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -32,7 +32,10 @@ class AnkiNoteBuilder { fields: {}, tags, deckName: modeOptions.deck, - modelName: modeOptions.model + modelName: modeOptions.model, + options: { + duplicateScope: options.anki.duplicateScope + } }; for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index 38823431..0d38837c 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -87,15 +87,14 @@ class AnkiConnect { return await this._invoke('storeMediaFile', {filename, data: dataBase64}); } - async findNoteIds(notes) { + async findNoteIds(notes, duplicateScope) { if (!this._enabled) { return []; } await this._checkVersion(); - const actions = notes.map((note) => ({ - action: 'findNotes', - params: { - query: `deck:"${this._escapeQuery(note.deckName)}" ${this._fieldsToQuery(note.fields)}` - } - })); + const actions = notes.map((note) => { + let query = (duplicateScope === 'deck' ? `"deck:${this._escapeQuery(note.deckName)}" ` : ''); + query += this._fieldsToQuery(note.fields); + return {action: 'findNotes', params: {query}}; + }); return await this._invoke('multi', {actions}); } @@ -132,6 +131,6 @@ class AnkiConnect { } const key = fieldNames[0]; - return `${key.toLowerCase()}:"${this._escapeQuery(fields[key])}"`; + return `"${key.toLowerCase()}:${this._escapeQuery(fields[key])}"`; } } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 3c47b14e..dd1fd8e9 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -555,7 +555,7 @@ class Backend { } if (cannotAdd.length > 0) { - const noteIdsArray = await this.anki.findNoteIds(cannotAdd.map((e) => e[0])); + const noteIdsArray = await this.anki.findNoteIds(cannotAdd.map((e) => e[0]), options.anki.duplicateScope); for (let i = 0, ii = Math.min(cannotAdd.length, noteIdsArray.length); i < ii; ++i) { const noteIds = noteIdsArray[i]; if (noteIds.length > 0) { diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index da26b628..8e1814ed 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -201,6 +201,7 @@ function profileOptionsCreateDefaults() { screenshot: {format: 'png', quality: 92}, terms: {deck: '', model: '', fields: {}}, kanji: {deck: '', model: '', fields: {}}, + duplicateScope: 'collection', fieldTemplates: null } }; diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index f03cc631..cf75d629 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -131,6 +131,7 @@ async function formRead(options) { options.anki.tags = utilBackgroundIsolate($('#card-tags').val().split(/[,; ]+/)); options.anki.sentenceExt = parseInt($('#sentence-detection-extent').val(), 10); options.anki.server = $('#interface-server').val(); + options.anki.duplicateScope = $('#duplicate-scope').val(); options.anki.screenshot.format = $('#screenshot-format').val(); options.anki.screenshot.quality = parseInt($('#screenshot-quality').val(), 10); @@ -212,6 +213,7 @@ async function formWrite(options) { $('#card-tags').val(options.anki.tags.join(' ')); $('#sentence-detection-extent').val(options.anki.sentenceExt); $('#interface-server').val(options.anki.server); + $('#duplicate-scope').val(options.anki.duplicateScope); $('#screenshot-format').val(options.anki.screenshot.format); $('#screenshot-quality').val(options.anki.screenshot.quality); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index f0236193..b6120b5f 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -820,6 +820,14 @@ +
+ + +
+
-
+