aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/bg/background.html1
-rw-r--r--ext/bg/js/backend.js138
-rw-r--r--ext/bg/js/search-query-parser.js22
-rw-r--r--ext/bg/js/search.js30
-rw-r--r--ext/mixed/js/api.js8
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;