diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 9839aef5..81772d08 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -17,16 +17,19 @@ */ -function apiOptionsGetSync() { - return utilBackend().options; +function apiOptionsGet(optionsContext) { + return utilBackend().getOptions(optionsContext); } -async function apiOptionsGet() { - return apiOptionsGetSync(); +async function apiOptionsSave(source) { + const backend = utilBackend(); + const options = await backend.getFullOptions(); + await optionsSave(options); + backend.onOptionsUpdated(source); } -async function apiTermsFind(text) { - const options = apiOptionsGetSync(); +async function apiTermsFind(text, optionsContext) { + const options = await apiOptionsGet(optionsContext); const translator = utilBackend().translator; const searcher = { @@ -38,7 +41,8 @@ async function apiTermsFind(text) { const {definitions, length} = await searcher( text, dictEnabledSet(options), - options.scanning.alphanumeric + options.scanning.alphanumeric, + options ); return { @@ -47,14 +51,14 @@ async function apiTermsFind(text) { }; } -async function apiKanjiFind(text) { - const options = apiOptionsGetSync(); +async function apiKanjiFind(text, optionsContext) { + const options = await apiOptionsGet(optionsContext); const definitions = await utilBackend().translator.findKanji(text, dictEnabledSet(options)); return definitions.slice(0, options.general.maxResults); } -async function apiDefinitionAdd(definition, mode, context) { - const options = apiOptionsGetSync(); +async function apiDefinitionAdd(definition, mode, context, optionsContext) { + const options = await apiOptionsGet(optionsContext); if (mode !== 'kanji') { await audioInject( @@ -76,14 +80,15 @@ async function apiDefinitionAdd(definition, mode, context) { return utilBackend().anki.addNote(note); } -async function apiDefinitionsAddable(definitions, modes) { +async function apiDefinitionsAddable(definitions, modes, optionsContext) { + const options = await apiOptionsGet(optionsContext); const states = []; try { const notes = []; for (const definition of definitions) { for (const mode of modes) { - const note = await dictNoteFormat(definition, mode, apiOptionsGetSync()); + const note = await dictNoteFormat(definition, mode, options); notes.push(note); } } @@ -131,9 +136,10 @@ async function apiCommandExec(command) { }, toggle: async () => { - const options = apiOptionsGetSync(); + const optionsContext = {depth: 0}; + const options = await apiOptionsGet(optionsContext); options.general.enable = !options.general.enable; - await optionsSave(options); + await apiOptionsSave('popup'); } }; diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index b3e737da..9a300d62 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -22,44 +22,43 @@ class Backend { this.translator = new Translator(); this.anki = new AnkiNull(); this.options = null; + this.optionsContext = { + depth: 0 + }; + + this.isPreparedResolve = null; + this.isPreparedPromise = new Promise((resolve) => (this.isPreparedResolve = resolve)); this.apiForwarder = new BackendApiForwarder(); } async prepare() { await this.translator.prepare(); - this.onOptionsUpdated(await optionsLoad()); + this.options = await optionsLoad(); + this.onOptionsUpdated('background'); if (chrome.commands !== null && typeof chrome.commands === 'object') { chrome.commands.onCommand.addListener(this.onCommand.bind(this)); } chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); - if (this.options.general.showGuide) { + const options = this.getOptionsSync(this.optionsContext); + if (options.general.showGuide) { chrome.tabs.create({url: chrome.extension.getURL('/bg/guide.html')}); } + + this.isPreparedResolve(); + this.isPreparedResolve = null; + this.isPreparedPromise = null; } - onOptionsUpdated(options) { - options = utilIsolate(options); - this.options = options; - - if (!options.general.enable) { - this.setExtensionBadgeBackgroundColor('#555555'); - this.setExtensionBadgeText('off'); - } else if (!dictConfigured(options)) { - this.setExtensionBadgeBackgroundColor('#f0ad4e'); - this.setExtensionBadgeText('!'); - } else { - this.setExtensionBadgeText(''); - } - - this.anki = options.anki.enable ? new AnkiConnect(options.anki.server) : new AnkiNull(); + onOptionsUpdated(source) { + this.applyOptions(); const callback = () => this.checkLastError(chrome.runtime.lastError); chrome.tabs.query({}, tabs => { for (const tab of tabs) { - chrome.tabs.sendMessage(tab.id, {action: 'optionsSet', params: {options}}, callback); + chrome.tabs.sendMessage(tab.id, {action: 'optionsUpdate', params: {source}}, callback); } }); } @@ -78,24 +77,24 @@ class Backend { }; const handlers = { - optionsGet: ({callback}) => { - forward(apiOptionsGet(), callback); + optionsGet: ({optionsContext, callback}) => { + forward(apiOptionsGet(optionsContext), callback); }, - kanjiFind: ({text, callback}) => { - forward(apiKanjiFind(text), callback); + kanjiFind: ({text, optionsContext, callback}) => { + forward(apiKanjiFind(text, optionsContext), callback); }, - termsFind: ({text, callback}) => { - forward(apiTermsFind(text), callback); + termsFind: ({text, optionsContext, callback}) => { + forward(apiTermsFind(text, optionsContext), callback); }, - definitionAdd: ({definition, mode, context, callback}) => { - forward(apiDefinitionAdd(definition, mode, context), callback); + definitionAdd: ({definition, mode, context, optionsContext, callback}) => { + forward(apiDefinitionAdd(definition, mode, context, optionsContext), callback); }, - definitionsAddable: ({definitions, modes, callback}) => { - forward(apiDefinitionsAddable(definitions, modes), callback); + definitionsAddable: ({definitions, modes, optionsContext, callback}) => { + forward(apiDefinitionsAddable(definitions, modes, optionsContext), callback); }, noteView: ({noteId}) => { @@ -136,6 +135,39 @@ class Backend { return true; } + applyOptions() { + const options = this.getOptionsSync(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.anki = options.anki.enable ? new AnkiConnect(options.anki.server) : new AnkiNull(); + } + + async getFullOptions() { + if (this.isPreparedPromise !== null) { + await this.isPreparedPromise; + } + return this.options; + } + + async getOptions(optionsContext) { + if (this.isPreparedPromise !== null) { + await this.isPreparedPromise; + } + return this.getOptionsSync(optionsContext); + } + + getOptionsSync(optionsContext) { + return this.options; + } + setExtensionBadgeBackgroundColor(color) { if (typeof chrome.browserAction.setBadgeBackgroundColor === 'function') { chrome.browserAction.setBadgeBackgroundColor({color}); diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js index 689d6863..dfa224a7 100644 --- a/ext/bg/js/context.js +++ b/ext/bg/js/context.js @@ -22,7 +22,8 @@ $(document).ready(utilAsync(() => { $('#open-options').click(() => apiCommandExec('options')); $('#open-help').click(() => apiCommandExec('help')); - optionsLoad().then(options => { + const optionsContext = {depth: 0}; + apiOptionsGet(optionsContext).then(options => { const toggle = $('#enable-search'); toggle.prop('checked', options.general.enable).change(); toggle.bootstrapToggle(); diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 69c662e6..d093d0b4 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -330,7 +330,7 @@ function optionsLoad() { }).then(optionsStr => { if (typeof optionsStr === 'string') { const options = JSON.parse(optionsStr); - if (typeof options === 'object' && options !== null && !Array.isArray(options)) { + if (utilIsObject(options)) { return options; } } @@ -343,9 +343,14 @@ function optionsLoad() { } function optionsSave(options) { - return new Promise((resolve) => { - chrome.storage.local.set({options: JSON.stringify(options)}, resolve); - }).then(() => { - utilBackend().onOptionsUpdated(options); + return new Promise((resolve, reject) => { + chrome.storage.local.set({options: JSON.stringify(options)}, () => { + const error = chrome.runtime.lastError; + if (error) { + reject(error); + } else { + resolve(); + } + }); }); } diff --git a/ext/bg/js/search-frontend.js b/ext/bg/js/search-frontend.js index 840a1ea8..df5ccf81 100644 --- a/ext/bg/js/search-frontend.js +++ b/ext/bg/js/search-frontend.js @@ -18,7 +18,8 @@ async function searchFrontendSetup() { - const options = await apiOptionsGet(); + const optionsContext = {depth: 0}; + const options = await apiOptionsGet(optionsContext); if (!options.scanning.enableOnSearchPage) { return; } const scriptSrcs = [ diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index a3382398..6bdc47d8 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -21,6 +21,10 @@ class DisplaySearch extends Display { constructor() { super($('#spinner'), $('#content')); + this.optionsContext = { + depth: 0 + }; + this.search = $('#search').click(this.onSearch.bind(this)); this.query = $('#query').on('input', this.onSearchInput.bind(this)); this.intro = $('#intro'); @@ -46,8 +50,8 @@ class DisplaySearch extends Display { try { e.preventDefault(); this.intro.slideUp(); - const {length, definitions} = await apiTermsFind(this.query.val()); - super.termsShow(definitions, await apiOptionsGet()); + const {length, definitions} = await apiTermsFind(this.query.val(), this.optionsContext); + super.termsShow(definitions, await apiOptionsGet(this.optionsContext)); } catch (e) { this.onError(e); } diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 83f4528c..7f3e5c69 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -16,73 +16,144 @@ * along with this program. If not, see . */ +function getOptionsContext() { + return { + depth: 0 + }; +} -async function formRead() { - const optionsOld = await optionsLoad(); - const optionsNew = $.extend(true, {}, optionsOld); +async function formRead(options) { + options.general.enable = $('#enable').prop('checked'); + options.general.showGuide = $('#show-usage-guide').prop('checked'); + options.general.compactTags = $('#compact-tags').prop('checked'); + options.general.compactGlossaries = $('#compact-glossaries').prop('checked'); + options.general.autoPlayAudio = $('#auto-play-audio').prop('checked'); + options.general.resultOutputMode = $('#result-output-mode').val(); + options.general.audioSource = $('#audio-playback-source').val(); + options.general.audioVolume = parseFloat($('#audio-playback-volume').val()); + options.general.debugInfo = $('#show-debug-info').prop('checked'); + options.general.showAdvanced = $('#show-advanced-options').prop('checked'); + options.general.maxResults = parseInt($('#max-displayed-results').val(), 10); + options.general.popupDisplayMode = $('#popup-display-mode').val(); + options.general.popupHorizontalTextPosition = $('#popup-horizontal-text-position').val(); + options.general.popupVerticalTextPosition = $('#popup-vertical-text-position').val(); + options.general.popupWidth = parseInt($('#popup-width').val(), 10); + options.general.popupHeight = parseInt($('#popup-height').val(), 10); + options.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0); + options.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10); + options.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0); + options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10); + options.general.customPopupCss = $('#custom-popup-css').val(); - optionsNew.general.showGuide = $('#show-usage-guide').prop('checked'); - optionsNew.general.compactTags = $('#compact-tags').prop('checked'); - optionsNew.general.compactGlossaries = $('#compact-glossaries').prop('checked'); - optionsNew.general.autoPlayAudio = $('#auto-play-audio').prop('checked'); - optionsNew.general.resultOutputMode = $('#result-output-mode').val(); - optionsNew.general.audioSource = $('#audio-playback-source').val(); - optionsNew.general.audioVolume = parseFloat($('#audio-playback-volume').val()); - optionsNew.general.debugInfo = $('#show-debug-info').prop('checked'); - optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked'); - optionsNew.general.maxResults = parseInt($('#max-displayed-results').val(), 10); - optionsNew.general.popupDisplayMode = $('#popup-display-mode').val(); - optionsNew.general.popupHorizontalTextPosition = $('#popup-horizontal-text-position').val(); - optionsNew.general.popupVerticalTextPosition = $('#popup-vertical-text-position').val(); - optionsNew.general.popupWidth = parseInt($('#popup-width').val(), 10); - optionsNew.general.popupHeight = parseInt($('#popup-height').val(), 10); - optionsNew.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0); - optionsNew.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10); - optionsNew.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0); - optionsNew.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10); - optionsNew.general.customPopupCss = $('#custom-popup-css').val(); + options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); + options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked'); + options.scanning.selectText = $('#select-matched-text').prop('checked'); + options.scanning.alphanumeric = $('#search-alphanumeric').prop('checked'); + options.scanning.autoHideResults = $('#auto-hide-results').prop('checked'); + options.scanning.deepDomScan = $('#deep-dom-scan').prop('checked'); + options.scanning.enableOnPopupExpressions = $('#enable-scanning-of-popup-expressions').prop('checked'); + options.scanning.enableOnSearchPage = $('#enable-scanning-on-search-page').prop('checked'); + options.scanning.delay = parseInt($('#scan-delay').val(), 10); + options.scanning.length = parseInt($('#scan-length').val(), 10); + options.scanning.modifier = $('#scan-modifier-key').val(); + options.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10); - optionsNew.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); - optionsNew.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked'); - optionsNew.scanning.selectText = $('#select-matched-text').prop('checked'); - optionsNew.scanning.alphanumeric = $('#search-alphanumeric').prop('checked'); - optionsNew.scanning.autoHideResults = $('#auto-hide-results').prop('checked'); - optionsNew.scanning.deepDomScan = $('#deep-dom-scan').prop('checked'); - optionsNew.scanning.enableOnPopupExpressions = $('#enable-scanning-of-popup-expressions').prop('checked'); - optionsNew.scanning.enableOnSearchPage = $('#enable-scanning-on-search-page').prop('checked'); - optionsNew.scanning.delay = parseInt($('#scan-delay').val(), 10); - optionsNew.scanning.length = parseInt($('#scan-length').val(), 10); - optionsNew.scanning.modifier = $('#scan-modifier-key').val(); - optionsNew.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10); + const optionsAnkiEnableOld = options.anki.enable; + options.anki.enable = $('#anki-enable').prop('checked'); + options.anki.tags = $('#card-tags').val().split(/[,; ]+/); + options.anki.sentenceExt = parseInt($('#sentence-detection-extent').val(), 10); + options.anki.server = $('#interface-server').val(); + options.anki.screenshot.format = $('#screenshot-format').val(); + options.anki.screenshot.quality = parseInt($('#screenshot-quality').val(), 10); + options.anki.fieldTemplates = $('#field-templates').val(); - optionsNew.anki.enable = $('#anki-enable').prop('checked'); - optionsNew.anki.tags = $('#card-tags').val().split(/[,; ]+/); - optionsNew.anki.sentenceExt = parseInt($('#sentence-detection-extent').val(), 10); - optionsNew.anki.server = $('#interface-server').val(); - optionsNew.anki.screenshot.format = $('#screenshot-format').val(); - optionsNew.anki.screenshot.quality = parseInt($('#screenshot-quality').val(), 10); - optionsNew.anki.fieldTemplates = $('#field-templates').val(); - - if (optionsOld.anki.enable && !ankiErrorShown()) { - optionsNew.anki.terms.deck = $('#anki-terms-deck').val(); - optionsNew.anki.terms.model = $('#anki-terms-model').val(); - optionsNew.anki.terms.fields = ankiFieldsToDict($('#terms .anki-field-value')); - optionsNew.anki.kanji.deck = $('#anki-kanji-deck').val(); - optionsNew.anki.kanji.model = $('#anki-kanji-model').val(); - optionsNew.anki.kanji.fields = ankiFieldsToDict($('#kanji .anki-field-value')); + if (optionsAnkiEnableOld && !ankiErrorShown()) { + options.anki.terms.deck = $('#anki-terms-deck').val(); + options.anki.terms.model = $('#anki-terms-model').val(); + options.anki.terms.fields = ankiFieldsToDict($('#terms .anki-field-value')); + options.anki.kanji.deck = $('#anki-kanji-deck').val(); + options.anki.kanji.model = $('#anki-kanji-model').val(); + options.anki.kanji.fields = ankiFieldsToDict($('#kanji .anki-field-value')); } - optionsNew.general.mainDictionary = $('#dict-main').val(); + options.general.mainDictionary = $('#dict-main').val(); $('.dict-group').each((index, element) => { const dictionary = $(element); - optionsNew.dictionaries[dictionary.data('title')] = { + options.dictionaries[dictionary.data('title')] = { priority: parseInt(dictionary.find('.dict-priority').val(), 10), enabled: dictionary.find('.dict-enabled').prop('checked'), allowSecondarySearches: dictionary.find('.dict-allow-secondary-searches').prop('checked') }; }); +} - return {optionsNew, optionsOld}; +async function formWrite(options) { + $('#enable').prop('checked', options.general.enable); + $('#show-usage-guide').prop('checked', options.general.showGuide); + $('#compact-tags').prop('checked', options.general.compactTags); + $('#compact-glossaries').prop('checked', options.general.compactGlossaries); + $('#auto-play-audio').prop('checked', options.general.autoPlayAudio); + $('#result-output-mode').val(options.general.resultOutputMode); + $('#audio-playback-source').val(options.general.audioSource); + $('#audio-playback-volume').val(options.general.audioVolume); + $('#show-debug-info').prop('checked', options.general.debugInfo); + $('#show-advanced-options').prop('checked', options.general.showAdvanced); + $('#max-displayed-results').val(options.general.maxResults); + $('#popup-display-mode').val(options.general.popupDisplayMode); + $('#popup-horizontal-text-position').val(options.general.popupHorizontalTextPosition); + $('#popup-vertical-text-position').val(options.general.popupVerticalTextPosition); + $('#popup-width').val(options.general.popupWidth); + $('#popup-height').val(options.general.popupHeight); + $('#popup-horizontal-offset').val(options.general.popupHorizontalOffset); + $('#popup-vertical-offset').val(options.general.popupVerticalOffset); + $('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2); + $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2); + $('#custom-popup-css').val(options.general.customPopupCss); + + $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); + $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled); + $('#select-matched-text').prop('checked', options.scanning.selectText); + $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric); + $('#auto-hide-results').prop('checked', options.scanning.autoHideResults); + $('#deep-dom-scan').prop('checked', options.scanning.deepDomScan); + $('#enable-scanning-of-popup-expressions').prop('checked', options.scanning.enableOnPopupExpressions); + $('#enable-scanning-on-search-page').prop('checked', options.scanning.enableOnSearchPage); + $('#scan-delay').val(options.scanning.delay); + $('#scan-length').val(options.scanning.length); + $('#scan-modifier-key').val(options.scanning.modifier); + $('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth); + + $('#anki-enable').prop('checked', options.anki.enable); + $('#card-tags').val(options.anki.tags.join(' ')); + $('#sentence-detection-extent').val(options.anki.sentenceExt); + $('#interface-server').val(options.anki.server); + $('#screenshot-format').val(options.anki.screenshot.format); + $('#screenshot-quality').val(options.anki.screenshot.quality); + $('#field-templates').val(options.anki.fieldTemplates); + + try { + await dictionaryGroupsPopulate(options); + await formMainDictionaryOptionsPopulate(options); + } catch (e) { + dictionaryErrorsShow([e]); + } + + try { + await ankiDeckAndModelPopulate(options); + } catch (e) { + ankiErrorShow(e); + } + + formUpdateVisibility(options); +} + +function formSetupEventListeners() { + $('#dict-purge-link').click(utilAsync(onDictionaryPurge)); + $('#dict-file').change(utilAsync(onDictionaryImport)); + + $('#field-templates-reset').click(utilAsync(onAnkiFieldTemplatesReset)); + $('input, select, textarea').not('.anki-model').not('.profile-form *').change(utilAsync(onFormOptionsChanged)); + $('.anki-model').change(utilAsync(onAnkiModelChanged)); } function formUpdateVisibility(options) { @@ -141,18 +212,23 @@ async function onFormOptionsChanged(e) { return; } - const {optionsNew, optionsOld} = await formRead(); - await optionsSave(optionsNew); - formUpdateVisibility(optionsNew); + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + const optionsAnkiEnableOld = options.anki.enable; + const optionsAnkiServerOld = options.anki.server; + + await formRead(options); + await settingsSaveOptions(); + formUpdateVisibility(options); try { const ankiUpdated = - optionsNew.anki.enable !== optionsOld.anki.enable || - optionsNew.anki.server !== optionsOld.anki.server; + options.anki.enable !== optionsAnkiEnableOld || + options.anki.server !== optionsAnkiServerOld; if (ankiUpdated) { ankiSpinnerShow(true); - await ankiDeckAndModelPopulate(optionsNew); + await ankiDeckAndModelPopulate(options); ankiErrorShow(); } } catch (e) { @@ -163,77 +239,51 @@ async function onFormOptionsChanged(e) { } async function onReady() { - const options = await optionsLoad(); + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); - $('#show-usage-guide').prop('checked', options.general.showGuide); - $('#compact-tags').prop('checked', options.general.compactTags); - $('#compact-glossaries').prop('checked', options.general.compactGlossaries); - $('#auto-play-audio').prop('checked', options.general.autoPlayAudio); - $('#result-output-mode').val(options.general.resultOutputMode); - $('#audio-playback-source').val(options.general.audioSource); - $('#audio-playback-volume').val(options.general.audioVolume); - $('#show-debug-info').prop('checked', options.general.debugInfo); - $('#show-advanced-options').prop('checked', options.general.showAdvanced); - $('#max-displayed-results').val(options.general.maxResults); - $('#popup-display-mode').val(options.general.popupDisplayMode); - $('#popup-horizontal-text-position').val(options.general.popupHorizontalTextPosition); - $('#popup-vertical-text-position').val(options.general.popupVerticalTextPosition); - $('#popup-width').val(options.general.popupWidth); - $('#popup-height').val(options.general.popupHeight); - $('#popup-horizontal-offset').val(options.general.popupHorizontalOffset); - $('#popup-vertical-offset').val(options.general.popupVerticalOffset); - $('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2); - $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2); - $('#custom-popup-css').val(options.general.customPopupCss); - - $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); - $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled); - $('#select-matched-text').prop('checked', options.scanning.selectText); - $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric); - $('#auto-hide-results').prop('checked', options.scanning.autoHideResults); - $('#deep-dom-scan').prop('checked', options.scanning.deepDomScan); - $('#enable-scanning-of-popup-expressions').prop('checked', options.scanning.enableOnPopupExpressions); - $('#enable-scanning-on-search-page').prop('checked', options.scanning.enableOnSearchPage); - $('#scan-delay').val(options.scanning.delay); - $('#scan-length').val(options.scanning.length); - $('#scan-modifier-key').val(options.scanning.modifier); - $('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth); - - $('#dict-purge-link').click(utilAsync(onDictionaryPurge)); - $('#dict-file').change(utilAsync(onDictionaryImport)); - - $('#anki-enable').prop('checked', options.anki.enable); - $('#card-tags').val(options.anki.tags.join(' ')); - $('#sentence-detection-extent').val(options.anki.sentenceExt); - $('#interface-server').val(options.anki.server); - $('#screenshot-format').val(options.anki.screenshot.format); - $('#screenshot-quality').val(options.anki.screenshot.quality); - $('#field-templates').val(options.anki.fieldTemplates); - $('#field-templates-reset').click(utilAsync(onAnkiFieldTemplatesReset)); - $('input, select, textarea').not('.anki-model').change(utilAsync(onFormOptionsChanged)); - $('.anki-model').change(utilAsync(onAnkiModelChanged)); - - try { - await dictionaryGroupsPopulate(options); - await formMainDictionaryOptionsPopulate(options); - } catch (e) { - dictionaryErrorsShow([e]); - } - - try { - await ankiDeckAndModelPopulate(options); - } catch (e) { - ankiErrorShow(e); - } - - formUpdateVisibility(options); + formSetupEventListeners(); + await formWrite(options); storageInfoInitialize(); + + chrome.runtime.onMessage.addListener(onMessage); } $(document).ready(utilAsync(onReady)); +/* + * Remote options updates + */ + +function settingsGetSource() { + return new Promise((resolve) => { + chrome.tabs.getCurrent((tab) => resolve(`settings${tab ? tab.id : ''}`)); + }); +} + +async function settingsSaveOptions() { + const source = await settingsGetSource(); + await apiOptionsSave(source); +} + +async function onOptionsUpdate({source}) { + const thisSource = await settingsGetSource(); + if (source === thisSource) { return; } + + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + await formWrite(options); +} + +function onMessage({action, params}) { + if (action === 'optionsUpdate') { + onOptionsUpdate(params); + } +} + + /* * Dictionary */ @@ -374,10 +424,11 @@ async function onDictionaryPurge(e) { dictionarySpinnerShow(true); await utilDatabasePurge(); - const options = await optionsLoad(); + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); options.dictionaries = {}; options.general.mainDictionary = ''; - await optionsSave(options); + await settingsSaveOptions(); await dictionaryGroupsPopulate(options); await formMainDictionaryOptionsPopulate(options); @@ -414,8 +465,9 @@ async function onDictionaryImport(e) { setProgress(0.0); const exceptions = []; - const options = await optionsLoad(); const summary = await utilDatabaseImport(e.target.files[0], updateProgress, exceptions); + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); options.dictionaries[summary.title] = {enabled: true, priority: 0, allowSecondarySearches: false}; if (summary.sequenced && options.general.mainDictionary === '') { options.general.mainDictionary = summary.title; @@ -426,7 +478,7 @@ async function onDictionaryImport(e) { dictionaryErrorsShow(exceptions); } - await optionsSave(options); + await settingsSaveOptions(); await dictionaryGroupsPopulate(options); await formMainDictionaryOptionsPopulate(options); @@ -566,12 +618,14 @@ async function onAnkiModelChanged(e) { const tab = element.closest('.tab-pane'); const tabId = tab.attr('id'); - const {optionsNew, optionsOld} = await formRead(); - optionsNew.anki[tabId].fields = {}; - await optionsSave(optionsNew); + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + await formRead(options); + options.anki[tabId].fields = {}; + await settingsSaveOptions(); ankiSpinnerShow(true); - await ankiFieldsPopulate(element, optionsNew); + await ankiFieldsPopulate(element, options); ankiErrorShow(); } catch (e) { ankiErrorShow(e); @@ -583,9 +637,12 @@ async function onAnkiModelChanged(e) { async function onAnkiFieldTemplatesReset(e) { try { e.preventDefault(); - const options = await optionsLoad(); - $('#field-templates').val(options.anki.fieldTemplates = optionsFieldTemplates()); - await optionsSave(options); + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + const fieldTemplates = optionsFieldTemplates(); + options.anki.fieldTemplates = fieldTemplates; + $('#field-templates').val(fieldTemplates); + await settingsSaveOptions(); } catch (e) { ankiErrorShow(e); } diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index c89b43ff..7b952622 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -36,8 +36,7 @@ class Translator { } } - async findTermsGrouped(text, dictionaries, alphanumeric) { - const options = await apiOptionsGet(); + async findTermsGrouped(text, dictionaries, alphanumeric, options) { const titles = Object.keys(dictionaries); const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); @@ -55,8 +54,7 @@ class Translator { return {length, definitions: definitionsGrouped}; } - async findTermsMerged(text, dictionaries, alphanumeric) { - const options = await apiOptionsGet(); + async findTermsMerged(text, dictionaries, alphanumeric, options) { const secondarySearchTitles = Object.keys(options.dictionaries).filter(dict => options.dictionaries[dict].allowSecondarySearches); const titles = Object.keys(dictionaries); const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 3dc7c900..79229229 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -104,3 +104,7 @@ function utilReadFile(file) { reader.readAsBinaryString(file); }); } + +function utilIsObject(value) { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} diff --git a/ext/bg/settings.html b/ext/bg/settings.html index ddda8303..577e1a1f 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -70,6 +70,10 @@

General Options

+
+ +
+
@@ -221,14 +225,6 @@
-
- -
- -
- -
-
@@ -252,9 +248,26 @@ + -
- + diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js index aa3b2629..d0ac649a 100644 --- a/ext/fg/js/api.js +++ b/ext/fg/js/api.js @@ -17,24 +17,24 @@ */ -function apiOptionsGet() { - return utilInvoke('optionsGet'); +function apiOptionsGet(optionsContext) { + return utilInvoke('optionsGet', {optionsContext}); } -function apiTermsFind(text) { - return utilInvoke('termsFind', {text}); +function apiTermsFind(text, optionsContext) { + return utilInvoke('termsFind', {text, optionsContext}); } -function apiKanjiFind(text) { - return utilInvoke('kanjiFind', {text}); +function apiKanjiFind(text, optionsContext) { + return utilInvoke('kanjiFind', {text, optionsContext}); } -function apiDefinitionAdd(definition, mode, context) { - return utilInvoke('definitionAdd', {definition, mode, context}); +function apiDefinitionAdd(definition, mode, context, optionsContext) { + return utilInvoke('definitionAdd', {definition, mode, context, optionsContext}); } -function apiDefinitionsAddable(definitions, modes) { - return utilInvoke('definitionsAddable', {definitions, modes}).catch(() => null); +function apiDefinitionsAddable(definitions, modes, optionsContext) { + return utilInvoke('definitionsAddable', {definitions, modes, optionsContext}).catch(() => null); } function apiNoteView(noteId) { diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 3c521714..348c114e 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -23,6 +23,10 @@ class DisplayFloat extends Display { this.autoPlayAudioTimer = null; this.styleNode = null; + this.optionsContext = { + depth: 0 + }; + this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); $(window).on('message', utilAsync(this.onMessage.bind(this))); @@ -75,6 +79,7 @@ class DisplayFloat extends Display { }, popupNestedInitialize: ({id, depth, parentFrameId}) => { + this.optionsContext.depth = depth; popupNestedInitialize(id, depth, parentFrameId); } }; diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 52620933..0b60aa2b 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -28,6 +28,10 @@ class Frontend { this.options = null; this.ignoreNodes = (Array.isArray(ignoreNodes) && ignoreNodes.length > 0 ? ignoreNodes.join(',') : null); + this.optionsContext = { + depth: popup.depth + }; + this.primaryTouchIdentifier = null; this.contextMenuChecking = false; this.contextMenuPrevent = false; @@ -40,9 +44,9 @@ class Frontend { static create() { const initializationData = window.frontendInitializationData; const isNested = (initializationData !== null && typeof initializationData === 'object'); - const {id, parentFrameId, ignoreNodes} = isNested ? initializationData : {}; + const {id, depth, parentFrameId, ignoreNodes} = isNested ? initializationData : {}; - const popup = isNested ? new PopupProxy(id, parentFrameId) : PopupProxyHost.instance.createPopup(null); + const popup = isNested ? new PopupProxy(depth + 1, id, parentFrameId) : PopupProxyHost.instance.createPopup(null); const frontend = new Frontend(popup, ignoreNodes); frontend.prepare(); return frontend; @@ -50,7 +54,7 @@ class Frontend { async prepare() { try { - this.options = await apiOptionsGet(); + this.options = await apiOptionsGet(this.optionsContext); window.addEventListener('message', this.onFrameMessage.bind(this)); window.addEventListener('mousedown', this.onMouseDown.bind(this)); @@ -261,11 +265,8 @@ class Frontend { onBgMessage({action, params}, sender, callback) { const handlers = { - optionsSet: ({options}) => { - this.options = options; - if (!this.options.enable) { - this.searchClear(); - } + optionsUpdate: () => { + this.updateOptions(); }, popupSetVisible: ({visible}) => { @@ -284,6 +285,13 @@ class Frontend { console.log(error); } + async updateOptions() { + this.options = await apiOptionsGet(this.optionsContext); + if (!this.options.enable) { + this.searchClear(); + } + } + popupTimerSet(callback) { this.popupTimerClear(); this.popupTimer = window.setTimeout(callback, this.options.scanning.delay); @@ -347,7 +355,7 @@ class Frontend { return; } - const {definitions, length} = await apiTermsFind(searchText); + const {definitions, length} = await apiTermsFind(searchText, this.optionsContext); if (definitions.length === 0) { return false; } @@ -380,7 +388,7 @@ class Frontend { return; } - const definitions = await apiKanjiFind(searchText); + const definitions = await apiKanjiFind(searchText, this.optionsContext); if (definitions.length === 0) { return false; } diff --git a/ext/fg/js/popup-nested.js b/ext/fg/js/popup-nested.js index e0376bb2..de2acccc 100644 --- a/ext/fg/js/popup-nested.js +++ b/ext/fg/js/popup-nested.js @@ -25,7 +25,8 @@ async function popupNestedInitialize(id, depth, parentFrameId) { } popupNestedInitialized = true; - const options = await apiOptionsGet(); + const optionsContext = {depth}; + const options = await apiOptionsGet(optionsContext); const popupNestingMaxDepth = options.scanning.popupNestingMaxDepth; if (!(typeof popupNestingMaxDepth === 'number' && typeof depth === 'number' && depth < popupNestingMaxDepth)) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index f6295079..56e710eb 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -18,14 +18,14 @@ class PopupProxy { - constructor(parentId, parentFrameId) { + constructor(depth, parentId, parentFrameId) { this.parentId = parentId; this.parentFrameId = parentFrameId; this.id = null; this.idPromise = null; this.parent = null; this.child = null; - this.depth = 0; + this.depth = depth; this.container = null; diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index ebf56897..eca67b5e 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -27,6 +27,7 @@ class Display { this.sequence = 0; this.index = 0; this.audioCache = {}; + this.optionsContext = {}; this.dependencies = {}; @@ -66,7 +67,7 @@ class Display { context.source.source = this.context.source; } - const kanjiDefs = await apiKanjiFind(link.text()); + const kanjiDefs = await apiKanjiFind(link.text(), this.optionsContext); this.kanjiShow(kanjiDefs, this.options, context); } catch (e) { this.onError(e); @@ -89,7 +90,7 @@ class Display { try { textSource.setEndOffset(this.options.scanning.length); - ({definitions, length} = await apiTermsFind(textSource.text())); + ({definitions, length} = await apiTermsFind(textSource.text(), this.optionsContext)); if (definitions.length === 0) { return false; } @@ -379,7 +380,7 @@ class Display { async adderButtonUpdate(modes, sequence) { try { - const states = await apiDefinitionsAddable(this.definitions, modes); + const states = await apiDefinitionsAddable(this.definitions, modes, this.optionsContext); if (!states || sequence !== this.sequence) { return; } @@ -453,7 +454,7 @@ class Display { } } - const noteId = await apiDefinitionAdd(definition, mode, context); + const noteId = await apiDefinitionAdd(definition, mode, context, this.optionsContext); if (noteId) { const index = this.definitions.indexOf(definition); Display.adderButtonFind(index, mode).addClass('disabled');