diff options
| -rw-r--r-- | ext/bg/js/api.js | 36 | ||||
| -rw-r--r-- | ext/bg/js/backend.js | 88 | ||||
| -rw-r--r-- | ext/bg/js/context.js | 3 | ||||
| -rw-r--r-- | ext/bg/js/options.js | 15 | ||||
| -rw-r--r-- | ext/bg/js/search-frontend.js | 3 | ||||
| -rw-r--r-- | ext/bg/js/search.js | 8 | ||||
| -rw-r--r-- | ext/bg/js/settings.js | 321 | ||||
| -rw-r--r-- | ext/bg/js/translator.js | 6 | ||||
| -rw-r--r-- | ext/bg/js/util.js | 4 | ||||
| -rw-r--r-- | ext/bg/settings.html | 33 | ||||
| -rw-r--r-- | ext/fg/js/api.js | 20 | ||||
| -rw-r--r-- | ext/fg/js/float.js | 5 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 28 | ||||
| -rw-r--r-- | ext/fg/js/popup-nested.js | 3 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy.js | 4 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 9 | 
16 files changed, 361 insertions, 225 deletions
| 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')});          } -    } -    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.isPreparedResolve(); +        this.isPreparedResolve = null; +        this.isPreparedPromise = null; +    } -        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 <http://www.gnu.org/licenses/>.   */ +function getOptionsContext() { +    return { +        depth: 0 +    }; +} -async function formRead() { -    const optionsOld = await optionsLoad(); -    const optionsNew = $.extend(true, {}, optionsOld); - -    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(); - -    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); - -    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')); +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(); + +    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); + +    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(); + +    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')          };      }); +} + +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]); +    } -    return {optionsNew, optionsOld}; +    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,75 +239,49 @@ 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); +    formSetupEventListeners(); +    await formWrite(options); -    $('#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); +    storageInfoInitialize(); -    $('#dict-purge-link').click(utilAsync(onDictionaryPurge)); -    $('#dict-file').change(utilAsync(onDictionaryImport)); +    chrome.runtime.onMessage.addListener(onMessage); +} -    $('#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)); +$(document).ready(utilAsync(onReady)); -    try { -        await dictionaryGroupsPopulate(options); -        await formMainDictionaryOptionsPopulate(options); -    } catch (e) { -        dictionaryErrorsShow([e]); -    } -    try { -        await ankiDeckAndModelPopulate(options); -    } catch (e) { -        ankiErrorShow(e); -    } +/* + * Remote options updates + */ -    formUpdateVisibility(options); +function settingsGetSource() { +    return new Promise((resolve) => { +        chrome.tabs.getCurrent((tab) => resolve(`settings${tab ? tab.id : ''}`)); +    }); +} -    storageInfoInitialize(); +async function settingsSaveOptions() { +    const source = await settingsGetSource(); +    await apiOptionsSave(source);  } -$(document).ready(utilAsync(onReady)); +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); +    } +}  /* @@ -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 @@ -71,6 +71,10 @@                  <h3>General Options</h3>                  <div class="checkbox"> +                    <label><input type="checkbox" id="enable"> Enable content scanning</label> +                </div> + +                <div class="checkbox">                      <label><input type="checkbox" id="show-usage-guide"> Show usage guide on startup</label>                  </div> @@ -222,14 +226,6 @@                  </div>                  <div class="checkbox options-advanced"> -                    <label><input type="checkbox" id="enable-scanning-of-popup-expressions"> Enable scanning of popup expressions</label> -                </div> - -                <div class="checkbox"> -                    <label><input type="checkbox" id="enable-scanning-on-search-page"> Enable scanning on search page</label> -                </div> - -                <div class="checkbox options-advanced">                      <label><input type="checkbox" id="deep-dom-scan"> Deep DOM scan</label>                  </div> @@ -252,9 +248,26 @@                          <option value="shift">Shift</option>                      </select>                  </div> +            </div> -                <div class="form-group options-advanced"> -                    <label for="popup-nesting-max-depth">Maximum nested popup depth</label> +            <div id="popup-content-scanning"> +                <h3>Popup Content Scanning Options</h3> + +                <p class="help-block"> +                    Yomichan is able to create additional popups in order to scan the content of other popups. +                    This feature can be enabled if the <strong>Maximum number of additional popups</strong> option is set to a value greater than 0. +                </p> + +                <div class="checkbox"> +                    <label><input type="checkbox" id="enable-scanning-on-search-page"> Enable scanning on search page</label> +                </div> + +                <div class="checkbox"> +                    <label><input type="checkbox" id="enable-scanning-of-popup-expressions"> Enable scanning of popup expressions</label> +                </div> + +                <div class="form-group"> +                    <label for="popup-nesting-max-depth">Maximum number of additional popups</label>                      <input type="number" min="0" id="popup-nesting-max-depth" class="form-control">                  </div>              </div> 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'); |