diff options
| -rw-r--r-- | ext/bg/background.html | 1 | ||||
| -rw-r--r-- | ext/bg/js/backend.js | 138 | ||||
| -rw-r--r-- | ext/bg/js/search-query-parser.js | 22 | ||||
| -rw-r--r-- | ext/bg/js/search.js | 30 | ||||
| -rw-r--r-- | ext/mixed/js/api.js | 8 | 
5 files changed, 132 insertions, 67 deletions
| diff --git a/ext/bg/background.html b/ext/bg/background.html index ee5a1f32..9c740adf 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -46,6 +46,7 @@          <script src="/bg/js/translator.js"></script>          <script src="/bg/js/util.js"></script>          <script src="/mixed/js/audio-system.js"></script> +        <script src="/mixed/js/object-property-accessor.js"></script>          <script src="/bg/js/background-main.js"></script>      </body> diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index d454aa22..8677e04c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -26,6 +26,7 @@   * DictionaryImporter   * JsonSchema   * Mecab + * ObjectPropertyAccessor   * Translator   * conditionsTestValue   * dictTermsSort @@ -84,7 +85,6 @@ class Backend {              ['optionsSchemaGet', {handler: this._onApiOptionsSchemaGet.bind(this), async: false}],              ['optionsGet', {handler: this._onApiOptionsGet.bind(this), async: false}],              ['optionsGetFull', {handler: this._onApiOptionsGetFull.bind(this), async: false}], -            ['optionsSet', {handler: this._onApiOptionsSet.bind(this), async: true}],              ['optionsSave', {handler: this._onApiOptionsSave.bind(this), async: true}],              ['kanjiFind', {handler: this._onApiKanjiFind.bind(this), async: true}],              ['termsFind', {handler: this._onApiTermsFind.bind(this), async: true}], @@ -115,7 +115,8 @@ class Backend {              ['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}],              ['log', {handler: this._onApiLog.bind(this), async: false}],              ['logIndicatorClear', {handler: this._onApiLogIndicatorClear.bind(this), async: false}], -            ['createActionPort', {handler: this._onApiCreateActionPort.bind(this), async: false}] +            ['createActionPort', {handler: this._onApiCreateActionPort.bind(this), async: false}], +            ['modifySettings', {handler: this._onApiModifySettings.bind(this), async: true}]          ]);          this._messageHandlersWithProgress = new Map([              ['importDictionaryArchive', {handler: this._onApiImportDictionaryArchive.bind(this), async: true}], @@ -258,8 +259,9 @@ class Backend {          return this.optionsSchema;      } -    getFullOptions() { -        return this.options; +    getFullOptions(useSchema=false) { +        const options = this.options; +        return useSchema ? JsonSchema.createProxy(options, this.optionsSchema) : options;      }      setFullOptions(options) { @@ -271,21 +273,22 @@ class Backend {          }      } -    getOptions(optionsContext) { -        return this.getProfile(optionsContext).options; +    getOptions(optionsContext, useSchema=false) { +        return this.getProfile(optionsContext, useSchema).options;      } -    getProfile(optionsContext) { -        const profiles = this.options.profiles; +    getProfile(optionsContext, useSchema=false) { +        const options = this.getFullOptions(useSchema); +        const profiles = options.profiles;          if (typeof optionsContext.index === 'number') {              return profiles[optionsContext.index];          } -        const profile = this.getProfileFromContext(optionsContext); -        return profile !== null ? profile : this.options.profiles[this.options.profileCurrent]; +        const profile = this.getProfileFromContext(options, optionsContext); +        return profile !== null ? profile : options.profiles[options.profileCurrent];      } -    getProfileFromContext(optionsContext) { -        for (const profile of this.options.profiles) { +    getProfileFromContext(options, optionsContext) { +        for (const profile of options.profiles) {              const conditionGroups = profile.conditionGroups;              if (conditionGroups.length > 0 && Backend.testConditionGroups(conditionGroups, optionsContext)) {                  return profile; @@ -413,46 +416,6 @@ class Backend {          return this.getFullOptions();      } -    async _onApiOptionsSet({changedOptions, optionsContext, source}) { -        const options = this.getOptions(optionsContext); - -        function getValuePaths(obj) { -            const valuePaths = []; -            const nodes = [{obj, path: []}]; -            while (nodes.length > 0) { -                const node = nodes.pop(); -                for (const key of Object.keys(node.obj)) { -                    const path = node.path.concat(key); -                    const obj2 = node.obj[key]; -                    if (obj2 !== null && typeof obj2 === 'object') { -                        nodes.unshift({obj: obj2, path}); -                    } else { -                        valuePaths.push([obj2, path]); -                    } -                } -            } -            return valuePaths; -        } - -        function modifyOption(path, value) { -            let pivot = options; -            for (const key of path.slice(0, -1)) { -                if (!hasOwn(pivot, key)) { -                    return false; -                } -                pivot = pivot[key]; -            } -            pivot[path[path.length - 1]] = value; -            return true; -        } - -        for (const [value, path] of getValuePaths(changedOptions)) { -            modifyOption(path, value); -        } - -        await this._onApiOptionsSave({source}); -    } -      async _onApiOptionsSave({source}) {          const options = this.getFullOptions();          await optionsSave(options); @@ -829,6 +792,20 @@ class Backend {          await this.database.deleteDictionary(dictionaryName, {rate: 1000}, onProgress);      } +    async _onApiModifySettings({targets, source}) { +        const results = []; +        for (const target of targets) { +            try { +                this._modifySetting(target); +                results.push({result: true}); +            } catch (e) { +                results.push({error: errorToJson(e)}); +            } +        } +        await this._onApiOptionsSave({source}); +        return results; +    } +      // Command handlers      _createActionListenerPort(port, sender, handlers) { @@ -988,6 +965,63 @@ class Backend {      // Utilities +    _getModifySettingObject(target) { +        const scope = target.scope; +        switch (scope) { +            case 'profile': +                if (!isObject(target.optionsContext)) { throw new Error('Invalid optionsContext'); } +                return this.getOptions(target.optionsContext, true); +            case 'global': +                return this.getFullOptions(true); +            default: +                throw new Error(`Invalid scope: ${scope}`); +        } +    } + +    async _modifySetting(target) { +        const options = this._getModifySettingObject(target); +        const accessor = new ObjectPropertyAccessor(options); +        const action = target.action; +        switch (action) { +            case 'set': +                { +                    const {path, value} = target; +                    if (typeof path !== 'string') { throw new Error('Invalid path'); } +                    accessor.set(ObjectPropertyAccessor.getPathArray(path), value); +                } +                break; +            case 'delete': +                { +                    const {path} = target; +                    if (typeof path !== 'string') { throw new Error('Invalid path'); } +                    accessor.delete(ObjectPropertyAccessor.getPathArray(path)); +                } +                break; +            case 'swap': +                { +                    const {path1, path2} = target; +                    if (typeof path1 !== 'string') { throw new Error('Invalid path1'); } +                    if (typeof path2 !== 'string') { throw new Error('Invalid path2'); } +                    accessor.swap(ObjectPropertyAccessor.getPathArray(path1), ObjectPropertyAccessor.getPathArray(path2)); +                } +                break; +            case 'splice': +                { +                    const {path, start, deleteCount, items} = target; +                    if (typeof path !== 'string') { throw new Error('Invalid path'); } +                    if (typeof start !== 'number' || Math.floor(start) !== start) { throw new Error('Invalid start'); } +                    if (typeof deleteCount !== 'number' || Math.floor(deleteCount) !== deleteCount) { throw new Error('Invalid deleteCount'); } +                    if (!Array.isArray(items)) { throw new Error('Invalid items'); } +                    const array = accessor.get(ObjectPropertyAccessor.getPathArray(path)); +                    if (!Array.isArray(array)) { throw new Error('Invalid target type'); } +                    array.splice(start, deleteCount, ...items); +                } +                break; +            default: +                throw new Error(`Unknown action: ${action}`); +        } +    } +      _validatePrivilegedMessageSender(sender) {          const url = sender.url;          if (!(typeof url === 'string' && yomichan.isExtensionUrl(url))) { diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 935f01f2..1c89583f 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -18,7 +18,7 @@  /* global   * QueryParserGenerator   * TextScanner - * apiOptionsSet + * apiModifySettings   * apiTermsFind   * apiTextParse   * docSentenceExtract @@ -72,8 +72,14 @@ class QueryParser extends TextScanner {      }      onParserChange(e) { -        const selectedParser = e.target.value; -        apiOptionsSet({parsing: {selectedParser}}, this.getOptionsContext()); +        const value = e.target.value; +        apiModifySettings([{ +            action: 'set', +            path: 'parsing.selectedParser', +            value, +            scope: 'profile', +            optionsContext: this.getOptionsContext() +        }], 'search');      }      getMouseEventListeners() { @@ -92,8 +98,14 @@ class QueryParser extends TextScanner {      refreshSelectedParser() {          if (this.parseResults.length > 0) {              if (!this.getParseResult()) { -                const selectedParser = this.parseResults[0].id; -                apiOptionsSet({parsing: {selectedParser}}, this.getOptionsContext()); +                const value = this.parseResults[0].id; +                apiModifySettings([{ +                    action: 'set', +                    path: 'parsing.selectedParser', +                    value, +                    scope: 'profile', +                    optionsContext: this.getOptionsContext() +                }], 'search');              }          }      } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index d69daea6..96e8a70b 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -21,7 +21,7 @@   * Display   * QueryParser   * apiClipboardGet - * apiOptionsSet + * apiModifySettings   * apiTermsFind   * wanakana   */ @@ -252,13 +252,19 @@ class DisplaySearch extends Display {      }      onWanakanaEnableChange(e) { -        const enableWanakana = e.target.checked; -        if (enableWanakana) { +        const value = e.target.checked; +        if (value) {              wanakana.bind(this.query);          } else {              wanakana.unbind(this.query);          } -        apiOptionsSet({general: {enableWanakana}}, this.getOptionsContext()); +        apiModifySettings([{ +            action: 'set', +            path: 'general.enableWanakana', +            value, +            scope: 'profile', +            optionsContext: this.getOptionsContext() +        }], 'search');      }      onClipboardMonitorEnableChange(e) { @@ -268,7 +274,13 @@ class DisplaySearch extends Display {                  (granted) => {                      if (granted) {                          this.clipboardMonitor.start(); -                        apiOptionsSet({general: {enableClipboardMonitor: true}}, this.getOptionsContext()); +                        apiModifySettings([{ +                            action: 'set', +                            path: 'general.enableClipboardMonitor', +                            value: true, +                            scope: 'profile', +                            optionsContext: this.getOptionsContext() +                        }], 'search');                      } else {                          e.target.checked = false;                      } @@ -276,7 +288,13 @@ class DisplaySearch extends Display {              );          } else {              this.clipboardMonitor.stop(); -            apiOptionsSet({general: {enableClipboardMonitor: false}}, this.getOptionsContext()); +            apiModifySettings([{ +                action: 'set', +                path: 'general.enableClipboardMonitor', +                value: false, +                scope: 'profile', +                optionsContext: this.getOptionsContext() +            }], 'search');          }      } diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index af97ac3d..0bc91759 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -28,10 +28,6 @@ function apiOptionsGetFull() {      return _apiInvoke('optionsGetFull');  } -function apiOptionsSet(changedOptions, optionsContext, source) { -    return _apiInvoke('optionsSet', {changedOptions, optionsContext, source}); -} -  function apiOptionsSave(source) {      return _apiInvoke('optionsSave', {source});  } @@ -160,6 +156,10 @@ function apiDeleteDictionary(dictionaryName, onProgress) {      return _apiInvokeWithProgress('deleteDictionary', {dictionaryName}, onProgress);  } +function apiModifySettings(targets, source) { +    return _apiInvoke('modifySettings', {targets, source}); +} +  function _apiCreateActionPort(timeout=5000) {      return new Promise((resolve, reject) => {          let timer = null; |