aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/background/controllers/SettingController.ts3
-rw-r--r--src/background/domains/GlobalMark.ts3
-rw-r--r--src/background/domains/Setting.ts59
-rw-r--r--src/background/repositories/PersistentSettingRepository.ts6
-rw-r--r--src/background/repositories/SettingRepository.ts34
-rw-r--r--src/background/usecases/CommandUseCase.ts7
-rw-r--r--src/background/usecases/CompletionsUseCase.ts30
-rw-r--r--src/background/usecases/SettingUseCase.ts17
-rw-r--r--src/background/usecases/parsers.ts21
-rw-r--r--src/content/actions/index.ts3
-rw-r--r--src/content/actions/operation.ts4
-rw-r--r--src/content/actions/setting.ts23
-rw-r--r--src/content/components/common/index.ts5
-rw-r--r--src/content/components/common/input.ts1
-rw-r--r--src/content/components/common/keymapper.ts28
-rw-r--r--src/content/components/common/mark.ts10
-rw-r--r--src/content/components/top-content/follow-controller.ts4
-rw-r--r--src/content/reducers/setting.ts24
-rw-r--r--src/settings/actions/index.ts16
-rw-r--r--src/settings/actions/setting.ts60
-rw-r--r--src/settings/components/form/KeymapsForm.tsx23
-rw-r--r--src/settings/components/form/SearchForm.tsx30
-rw-r--r--src/settings/components/index.tsx104
-rw-r--r--src/settings/keymaps.ts3
-rw-r--r--src/settings/reducers/setting.ts22
-rw-r--r--src/settings/storage.ts15
-rw-r--r--src/shared/SettingData.ts414
-rw-r--r--src/shared/Settings.ts200
-rw-r--r--src/shared/operations.ts2
-rw-r--r--src/shared/properties.ts50
-rw-r--r--src/shared/property-defs.ts50
-rw-r--r--src/shared/settings/default.ts85
-rw-r--r--src/shared/settings/properties.ts24
-rw-r--r--src/shared/settings/storage.ts32
-rw-r--r--src/shared/settings/validator.ts76
-rw-r--r--src/shared/settings/values.ts108
36 files changed, 1009 insertions, 587 deletions
diff --git a/src/background/controllers/SettingController.ts b/src/background/controllers/SettingController.ts
index f8b7302..dfd2817 100644
--- a/src/background/controllers/SettingController.ts
+++ b/src/background/controllers/SettingController.ts
@@ -1,5 +1,6 @@
import SettingUseCase from '../usecases/SettingUseCase';
import ContentMessageClient from '../infrastructures/ContentMessageClient';
+import Settings from '../../shared/Settings';
export default class SettingController {
private settingUseCase: SettingUseCase;
@@ -11,7 +12,7 @@ export default class SettingController {
this.contentMessageClient = new ContentMessageClient();
}
- getSetting(): any {
+ getSetting(): Promise<Settings> {
return this.settingUseCase.get();
}
diff --git a/src/background/domains/GlobalMark.ts b/src/background/domains/GlobalMark.ts
index 0964373..1ae912e 100644
--- a/src/background/domains/GlobalMark.ts
+++ b/src/background/domains/GlobalMark.ts
@@ -1,6 +1,7 @@
-export interface GlobalMark {
+export default interface GlobalMark {
readonly tabId: number;
readonly url: string;
readonly x: number;
readonly y: number;
+ // eslint-disable-next-line semi
}
diff --git a/src/background/domains/Setting.ts b/src/background/domains/Setting.ts
deleted file mode 100644
index b2b1ff2..0000000
--- a/src/background/domains/Setting.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import DefaultSettings from '../../shared/settings/default';
-import * as settingsValues from '../../shared/settings/values';
-
-type SettingValue = {
- source: string,
- json: string,
- form: any
-}
-
-export default class Setting {
- private obj: SettingValue;
-
- constructor({ source, json, form }: SettingValue) {
- this.obj = {
- source, json, form
- };
- }
-
- get source(): string {
- return this.obj.source;
- }
-
- get json(): string {
- return this.obj.json;
- }
-
- get form(): any {
- return this.obj.form;
- }
-
- value() {
- let value = JSON.parse(DefaultSettings.json);
- if (this.obj.source === 'json') {
- value = settingsValues.valueFromJson(this.obj.json);
- } else if (this.obj.source === 'form') {
- value = settingsValues.valueFromForm(this.obj.form);
- }
- if (!value.properties) {
- value.properties = {};
- }
- return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value };
- }
-
- serialize(): SettingValue {
- return this.obj;
- }
-
- static deserialize(obj: SettingValue): Setting {
- return new Setting({ source: obj.source, json: obj.json, form: obj.form });
- }
-
- static defaultSettings() {
- return new Setting({
- source: DefaultSettings.source,
- json: DefaultSettings.json,
- form: {},
- });
- }
-}
diff --git a/src/background/repositories/PersistentSettingRepository.ts b/src/background/repositories/PersistentSettingRepository.ts
index 3f2f4a1..18476fd 100644
--- a/src/background/repositories/PersistentSettingRepository.ts
+++ b/src/background/repositories/PersistentSettingRepository.ts
@@ -1,12 +1,12 @@
-import Setting from '../domains/Setting';
+import SettingData from '../../shared/SettingData';
export default class SettingRepository {
- async load(): Promise<any> {
+ async load(): Promise<SettingData | null> {
let { settings } = await browser.storage.local.get('settings');
if (!settings) {
return null;
}
- return Setting.deserialize(settings);
+ return SettingData.valueOf(settings);
}
}
diff --git a/src/background/repositories/SettingRepository.ts b/src/background/repositories/SettingRepository.ts
index 15355ba..eb83a2c 100644
--- a/src/background/repositories/SettingRepository.ts
+++ b/src/background/repositories/SettingRepository.ts
@@ -1,4 +1,6 @@
import MemoryStorage from '../infrastructures/MemoryStorage';
+import Settings from '../../shared/Settings';
+import * as PropertyDefs from '../../shared/property-defs';
const CACHED_SETTING_KEY = 'setting';
@@ -9,17 +11,41 @@ export default class SettingRepository {
this.cache = new MemoryStorage();
}
- get(): Promise<any> {
+ get(): Promise<Settings> {
return Promise.resolve(this.cache.get(CACHED_SETTING_KEY));
}
- update(value: any): any {
+ update(value: Settings): void {
return this.cache.set(CACHED_SETTING_KEY, value);
}
- async setProperty(name: string, value: string): Promise<any> {
+ async setProperty(
+ name: string, value: string | number | boolean,
+ ): Promise<void> {
+ let def = PropertyDefs.defs.find(d => name === d.name);
+ if (!def) {
+ throw new Error('unknown property: ' + name);
+ }
+ if (typeof value !== def.type) {
+ throw new TypeError(`property type of ${name} mismatch: ${typeof value}`);
+ }
+ let newValue = value;
+ if (typeof value === 'string' && value === '') {
+ newValue = def.defaultValue;
+ }
+
let current = await this.get();
- current.properties[name] = value;
+ switch (name) {
+ case 'hintchars':
+ current.properties.hintchars = newValue as string;
+ break;
+ case 'smoothscroll':
+ current.properties.smoothscroll = newValue as boolean;
+ break;
+ case 'complete':
+ current.properties.complete = newValue as string;
+ break;
+ }
return this.update(current);
}
}
diff --git a/src/background/usecases/CommandUseCase.ts b/src/background/usecases/CommandUseCase.ts
index e0e3ada..2247d7b 100644
--- a/src/background/usecases/CommandUseCase.ts
+++ b/src/background/usecases/CommandUseCase.ts
@@ -6,7 +6,6 @@ import SettingRepository from '../repositories/SettingRepository';
import BookmarkRepository from '../repositories/BookmarkRepository';
import ConsoleClient from '../infrastructures/ConsoleClient';
import ContentMessageClient from '../infrastructures/ContentMessageClient';
-import * as properties from '../../shared/settings/properties';
export default class CommandIndicator {
private tabPresenter: TabPresenter;
@@ -115,16 +114,16 @@ export default class CommandIndicator {
async addbookmark(title: string): Promise<any> {
let tab = await this.tabPresenter.getCurrent();
- let item = await this.bookmarkRepository.create(title, tab.url);
+ let item = await this.bookmarkRepository.create(title, tab.url as string);
let message = 'Saved current page: ' + item.url;
- return this.consoleClient.showInfo(tab.id, message);
+ return this.consoleClient.showInfo(tab.id as number, message);
}
async set(keywords: string): Promise<any> {
if (keywords.length === 0) {
return;
}
- let [name, value] = parsers.parseSetOption(keywords, properties.types);
+ let [name, value] = parsers.parseSetOption(keywords);
await this.settingRepository.setProperty(name, value);
return this.contentMessageClient.broadcastSettingsChanged();
diff --git a/src/background/usecases/CompletionsUseCase.ts b/src/background/usecases/CompletionsUseCase.ts
index f3a808b..ae1ceed 100644
--- a/src/background/usecases/CompletionsUseCase.ts
+++ b/src/background/usecases/CompletionsUseCase.ts
@@ -4,7 +4,7 @@ import CompletionsRepository from '../repositories/CompletionsRepository';
import * as filters from './filters';
import SettingRepository from '../repositories/SettingRepository';
import TabPresenter from '../presenters/TabPresenter';
-import * as properties from '../../shared/settings/properties';
+import * as PropertyDefs from '../../shared/property-defs';
const COMPLETION_ITEM_LIMIT = 10;
@@ -44,7 +44,7 @@ export default class CompletionsUseCase {
let settings = await this.settingRepository.get();
let groups: CompletionGroup[] = [];
- let complete = settings.properties.complete || properties.defaults.complete;
+ let complete = settings.properties.complete;
for (let c of complete) {
if (c === 's') {
// eslint-disable-next-line no-await-in-loop
@@ -127,25 +127,25 @@ export default class CompletionsUseCase {
}
querySet(name: string, keywords: string): Promise<CompletionGroup[]> {
- let items = Object.keys(properties.docs).map((key) => {
- if (properties.types[key] === 'boolean') {
+ let items = PropertyDefs.defs.map((def) => {
+ if (def.type === 'boolean') {
return [
{
- caption: key,
- content: name + ' ' + key,
- url: 'Enable ' + properties.docs[key],
+ caption: def.name,
+ content: name + ' ' + def.name,
+ url: 'Enable ' + def.description,
}, {
- caption: 'no' + key,
- content: name + ' no' + key,
- url: 'Disable ' + properties.docs[key],
+ caption: 'no' + def.name,
+ content: name + ' no' + def.name,
+ url: 'Disable ' + def.description
}
];
}
return [
{
- caption: key,
- content: name + ' ' + key,
- url: 'Set ' + properties.docs[key],
+ caption: def.name,
+ content: name + ' ' + def.name,
+ url: 'Set ' + def.description,
}
];
});
@@ -195,8 +195,8 @@ export default class CompletionsUseCase {
.map(filters.filterByTailingSlash)
.map(pages => filters.filterByPathname(pages, COMPLETION_ITEM_LIMIT))
.map(pages => filters.filterByOrigin(pages, COMPLETION_ITEM_LIMIT))[0]
- .sort((x: HistoryItem, y: HistoryItem) => {
- return Number(x.visitCount) < Number(y.visitCount);
+ .sort((x: HistoryItem, y: HistoryItem): number => {
+ return Number(x.visitCount) - Number(y.visitCount);
})
.slice(0, COMPLETION_ITEM_LIMIT);
return histories.map(page => ({
diff --git a/src/background/usecases/SettingUseCase.ts b/src/background/usecases/SettingUseCase.ts
index b66ce02..aa3b534 100644
--- a/src/background/usecases/SettingUseCase.ts
+++ b/src/background/usecases/SettingUseCase.ts
@@ -1,7 +1,8 @@
-import Setting from '../domains/Setting';
// eslint-disable-next-line max-len
import PersistentSettingRepository from '../repositories/PersistentSettingRepository';
import SettingRepository from '../repositories/SettingRepository';
+import { DefaultSettingData } from '../../shared/SettingData';
+import Settings from '../../shared/Settings';
export default class SettingUseCase {
private persistentSettingRepository: PersistentSettingRepository;
@@ -13,20 +14,18 @@ export default class SettingUseCase {
this.settingRepository = new SettingRepository();
}
- get(): Promise<any> {
+ get(): Promise<Settings> {
return this.settingRepository.get();
}
- async reload(): Promise<any> {
- let settings = await this.persistentSettingRepository.load();
- if (!settings) {
- settings = Setting.defaultSettings();
+ async reload(): Promise<Settings> {
+ let data = await this.persistentSettingRepository.load();
+ if (!data) {
+ data = DefaultSettingData;
}
- let value = settings.value();
-
+ let value = data.toSettings();
this.settingRepository.update(value);
-
return value;
}
}
diff --git a/src/background/usecases/parsers.ts b/src/background/usecases/parsers.ts
index 3616ac3..6135fd8 100644
--- a/src/background/usecases/parsers.ts
+++ b/src/background/usecases/parsers.ts
@@ -1,3 +1,5 @@
+import * as PropertyDefs from '../../shared//property-defs';
+
const mustNumber = (v: any): number => {
let num = Number(v);
if (isNaN(num)) {
@@ -7,29 +9,28 @@ const mustNumber = (v: any): number => {
};
const parseSetOption = (
- word: string,
- types: { [key: string]: string },
+ args: string,
): any[] => {
- let [key, value]: any[] = word.split('=');
+ let [key, value]: any[] = args.split('=');
if (value === undefined) {
value = !key.startsWith('no');
key = value ? key : key.slice(2);
}
- let type = types[key];
- if (!type) {
+ let def = PropertyDefs.defs.find(d => d.name === key);
+ if (!def) {
throw new Error('Unknown property: ' + key);
}
- if (type === 'boolean' && typeof value !== 'boolean' ||
- type !== 'boolean' && typeof value === 'boolean') {
- throw new Error('Invalid argument: ' + word);
+ if (def.type === 'boolean' && typeof value !== 'boolean' ||
+ def.type !== 'boolean' && typeof value === 'boolean') {
+ throw new Error('Invalid argument: ' + args);
}
- switch (type) {
+ switch (def.type) {
case 'string': return [key, value];
case 'number': return [key, mustNumber(value)];
case 'boolean': return [key, value];
}
- throw new Error('Unknown property type: ' + type);
+ throw new Error('Unknown property type: ' + def.type);
};
export { parseSetOption };
diff --git a/src/content/actions/index.ts b/src/content/actions/index.ts
index 18d0a69..a259211 100644
--- a/src/content/actions/index.ts
+++ b/src/content/actions/index.ts
@@ -1,4 +1,5 @@
import Redux from 'redux';
+import Settings from '../../shared/Settings';
// Enable/disable
export const ADDON_SET_ENABLED = 'addon.set.enabled';
@@ -45,7 +46,7 @@ export interface FindSetKeywordAction extends Redux.Action {
export interface SettingSetAction extends Redux.Action {
type: typeof SETTING_SET;
- value: any;
+ settings: Settings,
}
export interface InputKeyPressAction extends Redux.Action {
diff --git a/src/content/actions/operation.ts b/src/content/actions/operation.ts
index 6acb407..41e080b 100644
--- a/src/content/actions/operation.ts
+++ b/src/content/actions/operation.ts
@@ -8,7 +8,6 @@ import * as urls from '../urls';
import * as consoleFrames from '../console-frames';
import * as addonActions from './addon';
import * as markActions from './mark';
-import * as properties from '../../shared/settings/properties';
// eslint-disable-next-line complexity, max-lines-per-function
const exec = (
@@ -16,8 +15,7 @@ const exec = (
settings: any,
addonEnabled: boolean,
): Promise<actions.Action> | actions.Action => {
- let smoothscroll = settings.properties.smoothscroll ||
- properties.defaults.smoothscroll;
+ let smoothscroll = settings.properties.smoothscroll;
switch (operation.type) {
case operations.ADDON_ENABLE:
return addonActions.enable();
diff --git a/src/content/actions/setting.ts b/src/content/actions/setting.ts
index a8f049a..92f8559 100644
--- a/src/content/actions/setting.ts
+++ b/src/content/actions/setting.ts
@@ -1,29 +1,20 @@
import * as actions from './index';
-import * as keyUtils from '../../shared/utils/keys';
import * as operations from '../../shared/operations';
import * as messages from '../../shared/messages';
+import Settings, { Keymaps } from '../../shared/Settings';
-const reservedKeymaps = {
+const reservedKeymaps: Keymaps = {
'<Esc>': { type: operations.CANCEL },
'<C-[>': { type: operations.CANCEL },
};
-const set = (value: any): actions.SettingAction => {
- let entries: any[] = [];
- if (value.keymaps) {
- let keymaps = { ...value.keymaps, ...reservedKeymaps };
- entries = Object.entries(keymaps).map((entry) => {
- return [
- keyUtils.fromMapKeys(entry[0]),
- entry[1],
- ];
- });
- }
-
+const set = (settings: Settings): actions.SettingAction => {
return {
type: actions.SETTING_SET,
- value: { ...value,
- keymaps: entries, }
+ settings: {
+ ...settings,
+ keymaps: { ...settings.keymaps, ...reservedKeymaps },
+ }
};
};
diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts
index 9b5164e..8bd697f 100644
--- a/src/content/components/common/index.ts
+++ b/src/content/components/common/index.ts
@@ -8,6 +8,7 @@ import MessageListener from '../../MessageListener';
import * as addonActions from '../../actions/addon';
import * as blacklists from '../../../shared/blacklists';
import * as keys from '../../../shared/utils/keys';
+import * as actions from '../../actions';
export default class Common {
private win: Window;
@@ -45,9 +46,9 @@ export default class Common {
reloadSettings() {
try {
this.store.dispatch(settingActions.load())
- .then(({ value: settings }: any) => {
+ .then((action: actions.SettingAction) => {
let enabled = !blacklists.includes(
- settings.blacklist, this.win.location.href
+ action.settings.blacklist, this.win.location.href
);
this.store.dispatch(addonActions.setEnabled(enabled));
});
diff --git a/src/content/components/common/input.ts b/src/content/components/common/input.ts
index 64eb5f3..1fe34c9 100644
--- a/src/content/components/common/input.ts
+++ b/src/content/components/common/input.ts
@@ -61,7 +61,6 @@ export default class InputComponent {
}
let key = keys.fromKeyboardEvent(e);
-
for (let listener of this.onKeyListeners) {
let stop = listener(key);
if (stop) {
diff --git a/src/content/components/common/keymapper.ts b/src/content/components/common/keymapper.ts
index d9c9834..c94bae0 100644
--- a/src/content/components/common/keymapper.ts
+++ b/src/content/components/common/keymapper.ts
@@ -3,7 +3,10 @@ import * as operationActions from '../../actions/operation';
import * as operations from '../../../shared/operations';
import * as keyUtils from '../../../shared/utils/keys';
-const mapStartsWith = (mapping, keys) => {
+const mapStartsWith = (
+ mapping: keyUtils.Key[],
+ keys: keyUtils.Key[],
+): boolean => {
if (mapping.length < keys.length) {
return false;
}
@@ -16,26 +19,33 @@ const mapStartsWith = (mapping, keys) => {
};
export default class KeymapperComponent {
- constructor(store) {
+ private store: any;
+
+ constructor(store: any) {
this.store = store;
}
// eslint-disable-next-line max-statements
- key(key) {
+ key(key: keyUtils.Key): boolean {
this.store.dispatch(inputActions.keyPress(key));
let state = this.store.getState();
let input = state.input;
- let keymaps = new Map(state.setting.keymaps);
+ let keymaps = new Map<keyUtils.Key[], operations.Operation>(
+ state.setting.keymaps.map(
+ (e: {key: keyUtils.Key[], op: operations.Operation}) => [e.key, e.op],
+ )
+ );
- let matched = Array.from(keymaps.keys()).filter((mapping) => {
- return mapStartsWith(mapping, input.keys);
- });
+ let matched = Array.from(keymaps.keys()).filter(
+ (mapping: keyUtils.Key[]) => {
+ return mapStartsWith(mapping, input.keys);
+ });
if (!state.addon.enabled) {
// available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if
// the addon disabled
matched = matched.filter((keys) => {
- let type = keymaps.get(keys).type;
+ let type = (keymaps.get(keys) as operations.Operation).type;
return type === operations.ADDON_ENABLE ||
type === operations.ADDON_TOGGLE_ENABLED;
});
@@ -47,7 +57,7 @@ export default class KeymapperComponent {
matched.length === 1 && input.keys.length < matched[0].length) {
return true;
}
- let operation = keymaps.get(matched[0]);
+ let operation = keymaps.get(matched[0]) as operations.Operation;
let act = operationActions.exec(
operation, state.setting, state.addon.enabled
);
diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts
index 500d03b..686116c 100644
--- a/src/content/components/common/mark.ts
+++ b/src/content/components/common/mark.ts
@@ -1,7 +1,6 @@
-import * as markActions from 'content/actions/mark';
-import * as scrolls from 'content/scrolls';
-import * as consoleFrames from 'content/console-frames';
-import * as properties from 'shared/settings/properties';
+import * as markActions from '../../actions/mark';
+import * as scrolls from '../..//scrolls';
+import * as consoleFrames from '../..//console-frames';
const cancelKey = (key): boolean => {
return key.key === 'Esc' || key.key === '[' && key.ctrlKey;
@@ -20,8 +19,7 @@ export default class MarkComponent {
// eslint-disable-next-line max-statements
key(key) {
let { mark: markStage, setting } = this.store.getState();
- let smoothscroll = setting.properties.smoothscroll ||
- properties.defaults.smoothscroll;
+ let smoothscroll = setting.properties.smoothscroll;
if (!markStage.setMode && !markStage.jumpMode) {
return false;
diff --git a/src/content/components/top-content/follow-controller.ts b/src/content/components/top-content/follow-controller.ts
index be71f6e..d49b22a 100644
--- a/src/content/components/top-content/follow-controller.ts
+++ b/src/content/components/top-content/follow-controller.ts
@@ -2,7 +2,6 @@ import * as followControllerActions from '../../actions/follow-controller';
import * as messages from '../../../shared/messages';
import MessageListener, { WebMessageSender } from '../../MessageListener';
import HintKeyProducer from '../../hint-key-producer';
-import * as properties from '../../../shared/settings/properties';
const broadcastMessage = (win: Window, message: messages.Message): void => {
let json = JSON.stringify(message);
@@ -162,7 +161,6 @@ export default class FollowController {
}
hintchars() {
- return this.store.getState().setting.properties.hintchars ||
- properties.defaults.hintchars;
+ return this.store.getState().setting.properties.hintchars;
}
}
diff --git a/src/content/reducers/setting.ts b/src/content/reducers/setting.ts
index fa8e8ee..a3dc3aa 100644
--- a/src/content/reducers/setting.ts
+++ b/src/content/reducers/setting.ts
@@ -1,12 +1,20 @@
import * as actions from '../actions';
+import * as keyUtils from '../../shared/utils/keys';
+import * as operations from '../../shared/operations';
+import { Properties } from '../../shared/Settings';
export interface State {
- keymaps: any[];
+ keymaps: { key: keyUtils.Key[], op: operations.Operation }[];
+ properties: Properties;
}
-const defaultState = {
- // keymaps is and arrays of key-binding pairs, which is entries of Map
+const defaultState: State = {
keymaps: [],
+ properties: {
+ complete: '',
+ smoothscroll: false,
+ hintchars: '',
+ },
};
export default function reducer(
@@ -15,7 +23,15 @@ export default function reducer(
): State {
switch (action.type) {
case actions.SETTING_SET:
- return { ...action.value };
+ return {
+ keymaps: Object.entries(action.settings.keymaps).map((entry) => {
+ return {
+ key: keyUtils.fromMapKeys(entry[0]),
+ op: entry[1],
+ };
+ }),
+ properties: action.settings.properties,
+ };
default:
return state;
}
diff --git a/src/settings/actions/index.ts b/src/settings/actions/index.ts
index 75c6bb5..b1e996e 100644
--- a/src/settings/actions/index.ts
+++ b/src/settings/actions/index.ts
@@ -1,3 +1,7 @@
+import {
+ JSONSettings, FormSettings, SettingSource,
+} from '../../shared/SettingData';
+
// Settings
export const SETTING_SET_SETTINGS = 'setting.set.settings';
export const SETTING_SHOW_ERROR = 'setting.show.error';
@@ -6,25 +10,25 @@ export const SETTING_SWITCH_TO_JSON = 'setting.switch.to.json';
interface SettingSetSettingsAcion {
type: typeof SETTING_SET_SETTINGS;
- source: string;
- json: string;
- form: any;
+ source: SettingSource;
+ json?: JSONSettings;
+ form?: FormSettings;
}
interface SettingShowErrorAction {
type: typeof SETTING_SHOW_ERROR;
error: string;
- json: string;
+ json: JSONSettings;
}
interface SettingSwitchToFormAction {
type: typeof SETTING_SWITCH_TO_FORM;
- form: any;
+ form: FormSettings,
}
interface SettingSwitchToJsonAction {
type: typeof SETTING_SWITCH_TO_JSON;
- json: string;
+ json: JSONSettings,
}
export type SettingAction =
diff --git a/src/settings/actions/setting.ts b/src/settings/actions/setting.ts
index b03cd80..9eb416e 100644
--- a/src/settings/actions/setting.ts
+++ b/src/settings/actions/setting.ts
@@ -1,35 +1,35 @@
import * as actions from './index';
-import * as validator from '../../shared/settings/validator';
-import * as settingsValues from '../../shared/settings/values';
-import * as settingsStorage from '../../shared/settings/storage';
-import keymaps from '../keymaps';
+import * as storages from '../storage';
+import SettingData, {
+ JSONSettings, FormSettings, SettingSource,
+} from '../../shared/SettingData';
const load = async(): Promise<actions.SettingAction> => {
- let settings = await settingsStorage.loadRaw();
- return set(settings);
+ let data = await storages.load();
+ return set(data);
};
-const save = async(settings: any): Promise<actions.SettingAction> => {
+const save = async(data: SettingData): Promise<actions.SettingAction> => {
try {
- if (settings.source === 'json') {
- let value = JSON.parse(settings.json);
- validator.validate(value);
+ if (data.getSource() === SettingSource.JSON) {
+ // toSettings exercise validation
+ data.toSettings();
}
} catch (e) {
return {
type: actions.SETTING_SHOW_ERROR,
error: e.toString(),
- json: settings.json,
+ json: data.getJSON(),
};
}
- await settingsStorage.save(settings);
- return set(settings);
+ await storages.save(data);
+ return set(data);
};
-const switchToForm = (json: string): actions.SettingAction => {
+const switchToForm = (json: JSONSettings): actions.SettingAction => {
try {
- validator.validate(JSON.parse(json));
- let form = settingsValues.formFromJson(json, keymaps.allowedOps);
+ // toSettings exercise validation
+ let form = FormSettings.fromSettings(json.toSettings());
return {
type: actions.SETTING_SWITCH_TO_FORM,
form,
@@ -43,21 +43,31 @@ const switchToForm = (json: string): actions.SettingAction => {
}
};
-const switchToJson = (form: any): actions.SettingAction => {
- let json = settingsValues.jsonFromForm(form);
+const switchToJson = (form: FormSettings): actions.SettingAction => {
+ let json = JSONSettings.fromSettings(form.toSettings());
return {
type: actions.SETTING_SWITCH_TO_JSON,
json,
};
};
-const set = (settings: any): actions.SettingAction => {
- return {
- type: actions.SETTING_SET_SETTINGS,
- source: settings.source,
- json: settings.json,
- form: settings.form,
- };
+const set = (data: SettingData): actions.SettingAction => {
+ let source = data.getSource();
+ switch (source) {
+ case SettingSource.JSON:
+ return {
+ type: actions.SETTING_SET_SETTINGS,
+ source: source,
+ json: data.getJSON(),
+ };
+ case SettingSource.Form:
+ return {
+ type: actions.SETTING_SET_SETTINGS,
+ source: source,
+ form: data.getForm(),
+ };
+ }
+ throw new Error(`unknown source: ${source}`);
};
export { load, save, set, switchToForm, switchToJson };
diff --git a/src/settings/components/form/KeymapsForm.tsx b/src/settings/components/form/KeymapsForm.tsx
index ab44464..ad4d0e7 100644
--- a/src/settings/components/form/KeymapsForm.tsx
+++ b/src/settings/components/form/KeymapsForm.tsx
@@ -2,32 +2,30 @@ import './KeymapsForm.scss';
import React from 'react';
import Input from '../ui/Input';
import keymaps from '../../keymaps';
+import { FormKeymaps } from '../../../shared/SettingData';
-type Value = {[key: string]: string};
-
-interface Props{
- value: Value;
- onChange: (e: Value) => void;
+interface Props {
+ value: FormKeymaps;
+ onChange: (e: FormKeymaps) => void;
onBlur: () => void;
}
class KeymapsForm extends React.Component<Props> {
public static defaultProps: Props = {
- value: {},
+ value: FormKeymaps.valueOf({}),
onChange: () => {},
onBlur: () => {},
}
render() {
+ let values = this.props.value.toJSON();
return <div className='form-keymaps-form'>
{
keymaps.fields.map((group, index) => {
return <div key={index} className='form-keymaps-form-field-group'>
{
- group.map((field) => {
- let name = field[0];
- let label = field[1];
- let value = this.props.value[name] || '';
+ group.map(([name, label]) => {
+ let value = values[name] || '';
return <Input
type='text' id={name} name={name} key={name}
label={label} value={value}
@@ -43,10 +41,7 @@ class KeymapsForm extends React.Component<Props> {
}
bindValue(name: string, value: string) {
- let next = { ...this.props.value };
- next[name] = value;
-
- this.props.onChange(next);
+ this.props.onChange(this.props.value.buildWithOverride(name, value));
}
}
diff --git a/src/settings/components/form/SearchForm.tsx b/src/settings/components/form/SearchForm.tsx
index 737e291..67dbeba 100644
--- a/src/settings/components/form/SearchForm.tsx
+++ b/src/settings/components/form/SearchForm.tsx
@@ -2,31 +2,23 @@ import './SearchForm.scss';
import React from 'react';
import AddButton from '../ui/AddButton';
import DeleteButton from '../ui/DeleteButton';
-
-interface Value {
- default: string;
- engines: string[][];
-}
+import { FormSearch } from '../../../shared/SettingData';
interface Props {
- value: Value;
- onChange: (value: Value) => void;
+ value: FormSearch;
+ onChange: (value: FormSearch) => void;
onBlur: () => void;
}
class SearchForm extends React.Component<Props> {
public static defaultProps: Props = {
- value: { default: '', engines: []},
+ value: FormSearch.valueOf({ default: '', engines: []}),
onChange: () => {},
onBlur: () => {},
}
render() {
- let value = this.props.value;
- if (!value.engines) {
- value.engines = [];
- }
-
+ let value = this.props.value.toJSON();
return <div className='form-search-form'>
<div className='form-search-form-header'>
<div className='column-name'>Name</div>
@@ -63,28 +55,28 @@ class SearchForm extends React.Component<Props> {
}
bindValue(e: any) {
- let value = this.props.value;
+ let value = this.props.value.toJSON();
let name = e.target.name;
let index = Number(e.target.getAttribute('data-index'));
- let next: Value = {
+ let next: typeof value = {
default: value.default,
- engines: value.engines ? value.engines.slice() : [],
+ engines: value.engines.slice(),
};
if (name === 'name') {
next.engines[index][0] = e.target.value;
- next.default = this.props.value.engines[index][0];
+ next.default = value.engines[index][0];
} else if (name === 'url') {
next.engines[index][1] = e.target.value;
} else if (name === 'default') {
- next.default = this.props.value.engines[index][0];
+ next.default = value.engines[index][0];
} else if (name === 'add') {
next.engines.push(['', '']);
} else if (name === 'delete') {
next.engines.splice(index, 1);
}
- this.props.onChange(next);
+ this.props.onChange(FormSearch.valueOf(next));
if (name === 'delete' || name === 'default') {
this.props.onBlur();
}
diff --git a/src/settings/components/index.tsx b/src/settings/components/index.tsx
index f56e93f..b4a0866 100644
--- a/src/settings/components/index.tsx
+++ b/src/settings/components/index.tsx
@@ -6,22 +6,26 @@ import SearchForm from './form/SearchForm';
import KeymapsForm from './form/KeymapsForm';
import BlacklistForm from './form/BlacklistForm';
import PropertiesForm from './form/PropertiesForm';
-import * as properties from '../../shared/settings/properties';
import * as settingActions from '../../settings/actions/setting';
+import SettingData, {
+ JSONSettings, FormKeymaps, FormSearch, FormSettings,
+} from '../../shared/SettingData';
+import { State as AppState } from '../reducers/setting';
+import * as settings from '../../shared/Settings';
+import * as PropertyDefs from '../../shared/property-defs';
const DO_YOU_WANT_TO_CONTINUE =
'Some settings in JSON can be lost when migrating. ' +
'Do you want to continue?';
-interface Props {
- source: string;
- json: string;
- form: any;
- error: string;
-
+type StateProps = ReturnType<typeof mapStateToProps>;
+interface DispatchProps {
+ dispatch: (action: any) => void,
+}
+type Props = StateProps & DispatchProps & {
// FIXME
store: any;
-}
+};
class SettingsComponent extends React.Component<Props> {
componentDidMount() {
@@ -29,12 +33,17 @@ class SettingsComponent extends React.Component<Props> {
}
renderFormFields(form: any) {
+ let types = PropertyDefs.defs.reduce(
+ (o: {[key: string]: string}, def) => {
+ o[def.name] = def.type;
+ return o;
+ }, {});
return <div>
<fieldset>
<legend>Keybindings</legend>
<KeymapsForm
value={form.keymaps}
- onChange={value => this.bindForm('keymaps', value)}
+ onChange={this.bindKeymapsForm.bind(this)}
onBlur={this.save.bind(this)}
/>
</fieldset>
@@ -42,7 +51,7 @@ class SettingsComponent extends React.Component<Props> {
<legend>Search Engines</legend>
<SearchForm
value={form.search}
- onChange={value => this.bindForm('search', value)}
+ onChange={this.bindSearchForm.bind(this)}
onBlur={this.save.bind(this)}
/>
</fieldset>
@@ -50,23 +59,23 @@ class SettingsComponent extends React.Component<Props> {
<legend>Blacklist</legend>
<BlacklistForm
value={form.blacklist}
- onChange={value => this.bindForm('blacklist', value)}
+ onChange={this.bindBlacklistForm.bind(this)}
onBlur={this.save.bind(this)}
/>
</fieldset>
<fieldset>
<legend>Properties</legend>
<PropertiesForm
- types={properties.types}
+ types={types}
value={form.properties}
- onChange={value => this.bindForm('properties', value)}
+ onChange={this.bindPropertiesForm.bind(this)}
onBlur={this.save.bind(this)}
/>
</fieldset>
</div>;
}
- renderJsonFields(json: string, error: string) {
+ renderJsonFields(json: JSONSettings, error: string) {
return <div>
<Input
type='textarea'
@@ -76,7 +85,7 @@ class SettingsComponent extends React.Component<Props> {
error={error}
onValueChange={this.bindJson.bind(this)}
onBlur={this.save.bind(this)}
- value={json}
+ value={json.toJSON()}
/>
</div>;
}
@@ -87,7 +96,8 @@ class SettingsComponent extends React.Component<Props> {
if (this.props.source === 'form') {
fields = this.renderFormFields(this.props.form);
} else if (this.props.source === 'json') {
- fields = this.renderJsonFields(this.props.json, this.props.error);
+ fields = this.renderJsonFields(
+ this.props.json as JSONSettings, this.props.error);
}
return (
<div>
@@ -117,45 +127,73 @@ class SettingsComponent extends React.Component<Props> {
);
}
- bindForm(name: string, value: any) {
- let settings = {
+ bindKeymapsForm(value: FormKeymaps) {
+ let data = new SettingData({
+ source: this.props.source,
+ form: (this.props.form as FormSettings).buildWithKeymaps(value),
+ });
+ this.props.dispatch(settingActions.set(data));
+ }
+
+ bindSearchForm(value: any) {
+ let data = new SettingData({
+ source: this.props.source,
+ form: (this.props.form as FormSettings).buildWithSearch(
+ FormSearch.valueOf(value)),
+ });
+ this.props.dispatch(settingActions.set(data));
+ }
+
+ bindBlacklistForm(value: any) {
+ let data = new SettingData({
+ source: this.props.source,
+ form: (this.props.form as FormSettings).buildWithBlacklist(
+ settings.blacklistValueOf(value)),
+ });
+ this.props.dispatch(settingActions.set(data));
+ }
+
+ bindPropertiesForm(value: any) {
+ let data = new SettingData({
source: this.props.source,
- json: this.props.json,
- form: { ...this.props.form },
- };
- settings.form[name] = value;
- this.props.dispatch(settingActions.set(settings));
+ form: (this.props.form as FormSettings).buildWithProperties(
+ settings.propertiesValueOf(value)),
+ });
+ this.props.dispatch(settingActions.set(data));
}
bindJson(_name: string, value: string) {
- let settings = {
+ let data = new SettingData({
source: this.props.source,
- json: value,
- form: this.props.form,
- };
- this.props.dispatch(settingActions.set(settings));
+ json: JSONSettings.valueOf(value),
+ });
+ this.props.dispatch(settingActions.set(data));
}
bindSource(_name: string, value: string) {
let from = this.props.source;
if (from === 'form' && value === 'json') {
- this.props.dispatch(settingActions.switchToJson(this.props.form));
+ this.props.dispatch(settingActions.switchToJson(
+ this.props.form as FormSettings));
} else if (from === 'json' && value === 'form') {
let b = window.confirm(DO_YOU_WANT_TO_CONTINUE);
if (!b) {
this.forceUpdate();
return;
}
- this.props.dispatch(settingActions.switchToForm(this.props.json));
+ this.props.dispatch(
+ settingActions.switchToForm(this.props.json as JSONSettings));
}
}
save() {
- let settings = this.props.store.getState();
- this.props.dispatch(settingActions.save(settings));
+ let { source, json, form } = this.props.store.getState();
+ this.props.dispatch(settingActions.save(
+ new SettingData({ source, json, form }),
+ ));
}
}
-const mapStateToProps = (state: any) => state;
+const mapStateToProps = (state: AppState) => ({ ...state });
export default connect(mapStateToProps)(SettingsComponent);
diff --git a/src/settings/keymaps.ts b/src/settings/keymaps.ts
index ccfc74c..38045ad 100644
--- a/src/settings/keymaps.ts
+++ b/src/settings/keymaps.ts
@@ -66,9 +66,6 @@ const fields = [
]
];
-const allowedOps = [].concat(...fields.map(group => group.map(e => e[0])));
-
export default {
fields,
- allowedOps,
};
diff --git a/src/settings/reducers/setting.ts b/src/settings/reducers/setting.ts
index 47c21bf..c4a21c7 100644
--- a/src/settings/reducers/setting.ts
+++ b/src/settings/reducers/setting.ts
@@ -1,23 +1,25 @@
import * as actions from '../actions';
+import {
+ JSONSettings, FormSettings, SettingSource,
+} from '../../shared/SettingData';
-interface State {
- source: string;
- json: string;
- form: any;
+export interface State {
+ source: SettingSource;
+ json?: JSONSettings;
+ form?: FormSettings;
error: string;
}
const defaultState: State = {
- source: '',
- json: '',
- form: null,
+ source: SettingSource.JSON,
+ json: JSONSettings.valueOf(''),
error: '',
};
export default function reducer(
state = defaultState,
action: actions.SettingAction,
-) {
+): State {
switch (action.type) {
case actions.SETTING_SET_SETTINGS:
return { ...state,
@@ -32,12 +34,12 @@ export default function reducer(
case actions.SETTING_SWITCH_TO_FORM:
return { ...state,
error: '',
- source: 'form',
+ source: SettingSource.Form,
form: action.form, };
case actions.SETTING_SWITCH_TO_JSON:
return { ...state,
error: '',
- source: 'json',
+ source: SettingSource.JSON,
json: action.json, };
default:
return state;
diff --git a/src/settings/storage.ts b/src/settings/storage.ts
new file mode 100644
index 0000000..748b9ab
--- /dev/null
+++ b/src/settings/storage.ts
@@ -0,0 +1,15 @@
+import SettingData, { DefaultSettingData } from '../shared/SettingData';
+
+export const load = async(): Promise<SettingData> => {
+ let { settings } = await browser.storage.local.get('settings');
+ if (!settings) {
+ return DefaultSettingData;
+ }
+ return SettingData.valueOf(settings);
+};
+
+export const save = (data: SettingData) => {
+ return browser.storage.local.set({
+ settings: data.toJSON(),
+ });
+};
diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts
new file mode 100644
index 0000000..05e21fa
--- /dev/null
+++ b/src/shared/SettingData.ts
@@ -0,0 +1,414 @@
+import * as operations from './operations';
+import Settings, * as settings from './Settings';
+
+export class FormKeymaps {
+ private data: {[op: string]: string};
+
+ constructor(data: {[op: string]: string}) {
+ this.data = data;
+ }
+
+ toKeymaps(): settings.Keymaps {
+ let keymaps: settings.Keymaps = {};
+ for (let name of Object.keys(this.data)) {
+ let [type, argStr] = name.split('?');
+ let args = {};
+ if (argStr) {
+ args = JSON.parse(argStr);
+ }
+ let key = this.data[name];
+ keymaps[key] = operations.valueOf({ type, ...args });
+ }
+ return keymaps;
+ }
+
+ toJSON(): {[op: string]: string} {
+ return this.data;
+ }
+
+ buildWithOverride(op: string, keys: string): FormKeymaps {
+ let newData = {
+ ...this.data,
+ [op]: keys,
+ };
+ return new FormKeymaps(newData);
+ }
+
+ static valueOf(o: ReturnType<FormKeymaps['toJSON']>): FormKeymaps {
+ let data: {[op: string]: string} = {};
+ for (let op of Object.keys(o)) {
+ data[op] = o[op] as string;
+ }
+ return new FormKeymaps(data);
+ }
+
+ static fromKeymaps(keymaps: settings.Keymaps): FormKeymaps {
+ let data: {[op: string]: string} = {};
+ for (let key of Object.keys(keymaps)) {
+ let op = keymaps[key];
+ let args = { ...op };
+ delete args.type;
+
+ let name = op.type;
+ if (Object.keys(args).length > 0) {
+ name += '?' + JSON.stringify(args);
+ }
+ data[name] = key;
+ }
+ return new FormKeymaps(data);
+ }
+}
+
+export class FormSearch {
+ private default: string;
+
+ private engines: string[][];
+
+ constructor(defaultEngine: string, engines: string[][]) {
+ this.default = defaultEngine;
+ this.engines = engines;
+ }
+
+ toSearchSettings(): settings.Search {
+ return {
+ default: this.default,
+ engines: this.engines.reduce(
+ (o: {[key: string]: string}, [name, url]) => {
+ o[name] = url;
+ return o;
+ }, {}),
+ };
+ }
+
+ toJSON(): {
+ default: string;
+ engines: string[][];
+ } {
+ return {
+ default: this.default,
+ engines: this.engines,
+ };
+ }
+
+ static valueOf(o: ReturnType<FormSearch['toJSON']>): FormSearch {
+ if (!Object.prototype.hasOwnProperty.call(o, 'default')) {
+ throw new TypeError(`"default" field not set`);
+ }
+ if (!Object.prototype.hasOwnProperty.call(o, 'engines')) {
+ throw new TypeError(`"engines" field not set`);
+ }
+ return new FormSearch(o.default, o.engines);
+ }
+
+ static fromSearch(search: settings.Search): FormSearch {
+ let engines = Object.entries(search.engines).reduce(
+ (o: string[][], [name, url]) => {
+ return o.concat([[name, url]]);
+ }, []);
+ return new FormSearch(search.default, engines);
+ }
+}
+
+export class JSONSettings {
+ private json: string;
+
+ constructor(json: any) {
+ this.json = json;
+ }
+
+ toSettings(): Settings {
+ return settings.valueOf(JSON.parse(this.json));
+ }
+
+ toJSON(): string {
+ return this.json;
+ }
+
+ static valueOf(o: ReturnType<JSONSettings['toJSON']>): JSONSettings {
+ return new JSONSettings(o);
+ }
+
+ static fromSettings(data: Settings): JSONSettings {
+ return new JSONSettings(JSON.stringify(data, undefined, 2));
+ }
+}
+
+export class FormSettings {
+ private keymaps: FormKeymaps;
+
+ private search: FormSearch;
+
+ private properties: settings.Properties;
+
+ private blacklist: string[];
+
+ constructor(
+ keymaps: FormKeymaps,
+ search: FormSearch,
+ properties: settings.Properties,
+ blacklist: string[],
+ ) {
+ this.keymaps = keymaps;
+ this.search = search;
+ this.properties = properties;
+ this.blacklist = blacklist;
+ }
+
+ buildWithKeymaps(keymaps: FormKeymaps): FormSettings {
+ return new FormSettings(
+ keymaps,
+ this.search,
+ this.properties,
+ this.blacklist,
+ );
+ }
+
+ buildWithSearch(search: FormSearch): FormSettings {
+ return new FormSettings(
+ this.keymaps,
+ search,
+ this.properties,
+ this.blacklist,
+ );
+ }
+
+ buildWithProperties(props: settings.Properties): FormSettings {
+ return new FormSettings(
+ this.keymaps,
+ this.search,
+ props,
+ this.blacklist,
+ );
+ }
+
+ buildWithBlacklist(blacklist: string[]): FormSettings {
+ return new FormSettings(
+ this.keymaps,
+ this.search,
+ this.properties,
+ blacklist,
+ );
+ }
+
+ toSettings(): Settings {
+ return settings.valueOf({
+ keymaps: this.keymaps.toKeymaps(),
+ search: this.search.toSearchSettings(),
+ properties: this.properties,
+ blacklist: this.blacklist,
+ });
+ }
+
+ toJSON(): {
+ keymaps: ReturnType<FormKeymaps['toJSON']>;
+ search: ReturnType<FormSearch['toJSON']>;
+ properties: settings.Properties;
+ blacklist: string[];
+ } {
+ return {
+ keymaps: this.keymaps.toJSON(),
+ search: this.search.toJSON(),
+ properties: this.properties,
+ blacklist: this.blacklist,
+ };
+ }
+
+ static valueOf(o: ReturnType<FormSettings['toJSON']>): FormSettings {
+ for (let name of ['keymaps', 'search', 'properties', 'blacklist']) {
+ if (!Object.prototype.hasOwnProperty.call(o, name)) {
+ throw new Error(`"${name}" field not set`);
+ }
+ }
+ return new FormSettings(
+ FormKeymaps.valueOf(o.keymaps),
+ FormSearch.valueOf(o.search),
+ settings.propertiesValueOf(o.properties),
+ settings.blacklistValueOf(o.blacklist),
+ );
+ }
+
+ static fromSettings(data: Settings): FormSettings {
+ return new FormSettings(
+ FormKeymaps.fromKeymaps(data.keymaps),
+ FormSearch.fromSearch(data.search),
+ data.properties,
+ data.blacklist);
+ }
+}
+
+export enum SettingSource {
+ JSON = 'json',
+ Form = 'form',
+}
+
+export default class SettingData {
+ private source: SettingSource;
+
+ private json?: JSONSettings;
+
+ private form?: FormSettings;
+
+ constructor({
+ source, json, form
+ }: {
+ source: SettingSource,
+ json?: JSONSettings,
+ form?: FormSettings,
+ }) {
+ this.source = source;
+ this.json = json;
+ this.form = form;
+ }
+
+ getSource(): SettingSource {
+ return this.source;
+ }
+
+ getJSON(): JSONSettings {
+ if (!this.json) {
+ throw new TypeError('json settings not set');
+ }
+ return this.json;
+ }
+
+ getForm(): FormSettings {
+ if (!this.form) {
+ throw new TypeError('form settings not set');
+ }
+ return this.form;
+ }
+
+ toJSON(): any {
+ switch (this.source) {
+ case SettingSource.JSON:
+ return {
+ source: this.source,
+ json: (this.json as JSONSettings).toJSON(),
+ };
+ case SettingSource.Form:
+ return {
+ source: this.source,
+ form: (this.form as FormSettings).toJSON(),
+ };
+ }
+ throw new Error(`unknown settings source: ${this.source}`);
+ }
+
+ toSettings(): Settings {
+ switch (this.source) {
+ case SettingSource.JSON:
+ return this.getJSON().toSettings();
+ case SettingSource.Form:
+ return this.getForm().toSettings();
+ }
+ throw new Error(`unknown settings source: ${this.source}`);
+ }
+
+ static valueOf(o: {
+ source: string;
+ json?: string;
+ form?: ReturnType<FormSettings['toJSON']>;
+ }): SettingData {
+ switch (o.source) {
+ case SettingSource.JSON:
+ return new SettingData({
+ source: o.source,
+ json: JSONSettings.valueOf(
+ o.json as ReturnType<JSONSettings['toJSON']>),
+ });
+ case SettingSource.Form:
+ return new SettingData({
+ source: o.source,
+ form: FormSettings.valueOf(
+ o.form as ReturnType<FormSettings['toJSON']>),
+ });
+ }
+ throw new Error(`unknown settings source: ${o.source}`);
+ }
+}
+
+export const DefaultSettingData: SettingData = SettingData.valueOf({
+ source: 'json',
+ json: `{
+ "keymaps": {
+ "0": { "type": "scroll.home" },
+ ":": { "type": "command.show" },
+ "o": { "type": "command.show.open", "alter": false },
+ "O": { "type": "command.show.open", "alter": true },
+ "t": { "type": "command.show.tabopen", "alter": false },
+ "T": { "type": "command.show.tabopen", "alter": true },
+ "w": { "type": "command.show.winopen", "alter": false },
+ "W": { "type": "command.show.winopen", "alter": true },
+ "b": { "type": "command.show.buffer" },
+ "a": { "type": "command.show.addbookmark", "alter": true },
+ "k": { "type": "scroll.vertically", "count": -1 },
+ "j": { "type": "scroll.vertically", "count": 1 },
+ "h": { "type": "scroll.horizonally", "count": -1 },
+ "l": { "type": "scroll.horizonally", "count": 1 },
+ "<C-U>": { "type": "scroll.pages", "count": -0.5 },
+ "<C-D>": { "type": "scroll.pages", "count": 0.5 },
+ "<C-B>": { "type": "scroll.pages", "count": -1 },
+ "<C-F>": { "type": "scroll.pages", "count": 1 },
+ "gg": { "type": "scroll.top" },
+ "G": { "type": "scroll.bottom" },
+ "$": { "type": "scroll.end" },
+ "d": { "type": "tabs.close" },
+ "D": { "type": "tabs.close.right" },
+ "!d": { "type": "tabs.close.force" },
+ "u": { "type": "tabs.reopen" },
+ "K": { "type": "tabs.prev" },
+ "J": { "type": "tabs.next" },
+ "gT": { "type": "tabs.prev" },
+ "gt": { "type": "tabs.next" },
+ "g0": { "type": "tabs.first" },
+ "g$": { "type": "tabs.last" },
+ "<C-6>": { "type": "tabs.prevsel" },
+ "r": { "type": "tabs.reload", "cache": false },
+ "R": { "type": "tabs.reload", "cache": true },
+ "zp": { "type": "tabs.pin.toggle" },
+ "zd": { "type": "tabs.duplicate" },
+ "zi": { "type": "zoom.in" },
+ "zo": { "type": "zoom.out" },
+ "zz": { "type": "zoom.neutral" },
+ "f": { "type": "follow.start", "newTab": false },
+ "F": { "type": "follow.start", "newTab": true, "background": false },
+ "m": { "type": "mark.set.prefix" },
+ "'": { "type": "mark.jump.prefix" },
+ "H": { "type": "navigate.history.prev" },
+ "L": { "type": "navigate.history.next" },
+ "[[": { "type": "navigate.link.prev" },
+ "]]": { "type": "navigate.link.next" },
+ "gu": { "type": "navigate.parent" },
+ "gU": { "type": "navigate.root" },
+ "gi": { "type": "focus.input" },
+ "gf": { "type": "page.source" },
+ "gh": { "type": "page.home" },
+ "gH": { "type": "page.home", "newTab": true },
+ "y": { "type": "urls.yank" },
+ "p": { "type": "urls.paste", "newTab": false },
+ "P": { "type": "urls.paste", "newTab": true },
+ "/": { "type": "find.start" },
+ "n": { "type": "find.next" },
+ "N": { "type": "find.prev" },
+ "<S-Esc>": { "type": "addon.toggle.enabled" }
+ },
+ "search": {
+ "default": "google",
+ "engines": {
+ "google": "https://google.com/search?q={}",
+ "yahoo": "https://search.yahoo.com/search?p={}",
+ "bing": "https://www.bing.com/search?q={}",
+ "duckduckgo": "https://duckduckgo.com/?q={}",
+ "twitter": "https://twitter.com/search?q={}",
+ "wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
+ }
+ },
+ "properties": {
+ "hintchars": "abcdefghijklmnopqrstuvwxyz",
+ "smoothscroll": false,
+ "complete": "sbh"
+ },
+ "blacklist": [
+ ]
+}`,
+});
diff --git a/src/shared/Settings.ts b/src/shared/Settings.ts
new file mode 100644
index 0000000..ce6b9ee
--- /dev/null
+++ b/src/shared/Settings.ts
@@ -0,0 +1,200 @@
+import * as operations from './operations';
+import * as PropertyDefs from './property-defs';
+
+export type Keymaps = {[key: string]: operations.Operation};
+
+export interface Search {
+ default: string;
+ engines: { [key: string]: string };
+}
+
+export interface Properties {
+ hintchars: string;
+ smoothscroll: boolean;
+ complete: string;
+}
+
+export default interface Settings {
+ keymaps: Keymaps;
+ search: Search;
+ properties: Properties;
+ blacklist: string[];
+ // eslint-disable-next-line semi
+}
+
+const DefaultProperties: Properties = PropertyDefs.defs.reduce(
+ (o: {[name: string]: PropertyDefs.Type}, def) => {
+ o[def.name] = def.defaultValue;
+ return o;
+ }, {}) as Properties;
+
+
+export const keymapsValueOf = (o: any): Keymaps => {
+ return Object.keys(o).reduce((keymaps: Keymaps, key: string): Keymaps => {
+ let op = operations.valueOf(o[key]);
+ keymaps[key] = op;
+ return keymaps;
+ }, {});
+};
+
+export const searchValueOf = (o: any): Search => {
+ if (typeof o.default !== 'string') {
+ throw new TypeError('string field "default" not set"');
+ }
+ for (let name of Object.keys(o.engines)) {
+ if ((/\s/).test(name)) {
+ throw new TypeError(
+ `While space in the search engine not allowed: "${name}"`);
+ }
+ let url = o.engines[name];
+ if (typeof url !== 'string') {
+ throw new TypeError('"engines" not an object of string');
+ }
+ let matches = url.match(/{}/g);
+ if (matches === null) {
+ throw new TypeError(`No {}-placeholders in URL of "${name}"`);
+ } else if (matches.length > 1) {
+ throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`);
+ }
+
+ }
+ if (!Object.prototype.hasOwnProperty.call(o.engines, o.default)) {
+ throw new TypeError(`Default engine "${o.default}" not found`);
+ }
+ return {
+ default: o.default as string,
+ engines: { ...o.engines },
+ };
+};
+
+export const propertiesValueOf = (o: any): Properties => {
+ let defNames = new Set(PropertyDefs.defs.map(def => def.name));
+ let unknownName = Object.keys(o).find(name => !defNames.has(name));
+ if (unknownName) {
+ throw new TypeError(`Unknown property name: "${unknownName}"`);
+ }
+
+ for (let def of PropertyDefs.defs) {
+ if (!Object.prototype.hasOwnProperty.call(o, def.name)) {
+ continue;
+ }
+ if (typeof o[def.name] !== def.type) {
+ throw new TypeError(`property "${def.name}" is not ${def.type}`);
+ }
+ }
+ return {
+ ...DefaultProperties,
+ ...o,
+ };
+};
+
+export const blacklistValueOf = (o: any): string[] => {
+ if (!Array.isArray(o)) {
+ throw new TypeError(`"blacklist" is not an array of string`);
+ }
+ for (let x of o) {
+ if (typeof x !== 'string') {
+ throw new TypeError(`"blacklist" is not an array of string`);
+ }
+ }
+ return o as string[];
+};
+
+export const valueOf = (o: any): Settings => {
+ let settings = { ...DefaultSetting };
+ if (Object.prototype.hasOwnProperty.call(o, 'keymaps')) {
+ settings.keymaps = keymapsValueOf(o.keymaps);
+ }
+ if (Object.prototype.hasOwnProperty.call(o, 'search')) {
+ settings.search = searchValueOf(o.search);
+ }
+ if (Object.prototype.hasOwnProperty.call(o, 'properties')) {
+ settings.properties = propertiesValueOf(o.properties);
+ }
+ if (Object.prototype.hasOwnProperty.call(o, 'blacklist')) {
+ settings.blacklist = blacklistValueOf(o.blacklist);
+ }
+ return settings;
+};
+
+const DefaultSetting: Settings = {
+ keymaps: {
+ '0': { 'type': 'scroll.home' },
+ ':': { 'type': 'command.show' },
+ 'o': { 'type': 'command.show.open', 'alter': false },
+ 'O': { 'type': 'command.show.open', 'alter': true },
+ 't': { 'type': 'command.show.tabopen', 'alter': false },
+ 'T': { 'type': 'command.show.tabopen', 'alter': true },
+ 'w': { 'type': 'command.show.winopen', 'alter': false },
+ 'W': { 'type': 'command.show.winopen', 'alter': true },
+ 'b': { 'type': 'command.show.buffer' },
+ 'a': { 'type': 'command.show.addbookmark', 'alter': true },
+ 'k': { 'type': 'scroll.vertically', 'count': -1 },
+ 'j': { 'type': 'scroll.vertically', 'count': 1 },
+ 'h': { 'type': 'scroll.horizonally', 'count': -1 },
+ 'l': { 'type': 'scroll.horizonally', 'count': 1 },
+ '<C-U>': { 'type': 'scroll.pages', 'count': -0.5 },
+ '<C-D>': { 'type': 'scroll.pages', 'count': 0.5 },
+ '<C-B>': { 'type': 'scroll.pages', 'count': -1 },
+ '<C-F>': { 'type': 'scroll.pages', 'count': 1 },
+ 'gg': { 'type': 'scroll.top' },
+ 'G': { 'type': 'scroll.bottom' },
+ '$': { 'type': 'scroll.end' },
+ 'd': { 'type': 'tabs.close' },
+ 'D': { 'type': 'tabs.close.right' },
+ '!d': { 'type': 'tabs.close.force' },
+ 'u': { 'type': 'tabs.reopen' },
+ 'K': { 'type': 'tabs.prev' },
+ 'J': { 'type': 'tabs.next' },
+ 'gT': { 'type': 'tabs.prev' },
+ 'gt': { 'type': 'tabs.next' },
+ 'g0': { 'type': 'tabs.first' },
+ 'g$': { 'type': 'tabs.last' },
+ '<C-6>': { 'type': 'tabs.prevsel' },
+ 'r': { 'type': 'tabs.reload', 'cache': false },
+ 'R': { 'type': 'tabs.reload', 'cache': true },
+ 'zp': { 'type': 'tabs.pin.toggle' },
+ 'zd': { 'type': 'tabs.duplicate' },
+ 'zi': { 'type': 'zoom.in' },
+ 'zo': { 'type': 'zoom.out' },
+ 'zz': { 'type': 'zoom.neutral' },
+ 'f': { 'type': 'follow.start', 'newTab': false, 'background': false },
+ 'F': { 'type': 'follow.start', 'newTab': true, 'background': false },
+ 'm': { 'type': 'mark.set.prefix' },
+ '\'': { 'type': 'mark.jump.prefix' },
+ 'H': { 'type': 'navigate.history.prev' },
+ 'L': { 'type': 'navigate.history.next' },
+ '[[': { 'type': 'navigate.link.prev' },
+ ']]': { 'type': 'navigate.link.next' },
+ 'gu': { 'type': 'navigate.parent' },
+ 'gU': { 'type': 'navigate.root' },
+ 'gi': { 'type': 'focus.input' },
+ 'gf': { 'type': 'page.source' },
+ 'gh': { 'type': 'page.home', 'newTab': false },
+ 'gH': { 'type': 'page.home', 'newTab': true },
+ 'y': { 'type': 'urls.yank' },
+ 'p': { 'type': 'urls.paste', 'newTab': false },
+ 'P': { 'type': 'urls.paste', 'newTab': true },
+ '/': { 'type': 'find.start' },
+ 'n': { 'type': 'find.next' },
+ 'N': { 'type': 'find.prev' },
+ '<S-Esc>': { 'type': 'addon.toggle.enabled' }
+ },
+ search: {
+ default: 'google',
+ engines: {
+ 'google': 'https://google.com/search?q={}',
+ 'yahoo': 'https://search.yahoo.com/search?p={}',
+ 'bing': 'https://www.bing.com/search?q={}',
+ 'duckduckgo': 'https://duckduckgo.com/?q={}',
+ 'twitter': 'https://twitter.com/search?q={}',
+ 'wikipedia': 'https://en.wikipedia.org/w/index.php?search={}'
+ }
+ },
+ properties: {
+ hintchars: 'abcdefghijklmnopqrstuvwxyz',
+ smoothscroll: false,
+ complete: 'sbh'
+ },
+ blacklist: []
+};
diff --git a/src/shared/operations.ts b/src/shared/operations.ts
index cc22f75..688c240 100644
--- a/src/shared/operations.ts
+++ b/src/shared/operations.ts
@@ -443,5 +443,5 @@ export const valueOf = (o: any): Operation => {
case MARK_JUMP_PREFIX:
return { type: o.type };
}
- throw new Error('unknown operation type: ' + o.type);
+ throw new TypeError('unknown operation type: ' + o.type);
};
diff --git a/src/shared/properties.ts b/src/shared/properties.ts
new file mode 100644
index 0000000..6315030
--- /dev/null
+++ b/src/shared/properties.ts
@@ -0,0 +1,50 @@
+export type Type = string | number | boolean;
+
+export class Def {
+ private name0: string;
+
+ private description0: string;
+
+ private defaultValue0: Type;
+
+ constructor(
+ name: string,
+ description: string,
+ defaultValue: Type,
+ ) {
+ this.name0 = name;
+ this.description0 = description;
+ this.defaultValue0 = defaultValue;
+ }
+
+ public get name(): string {
+ return this.name0;
+ }
+
+ public get defaultValue(): Type {
+ return this.defaultValue0;
+ }
+
+ public get description(): Type {
+ return this.description0;
+ }
+
+ public get type(): string {
+ return typeof this.defaultValue;
+ }
+}
+
+export const defs: Def[] = [
+ new Def(
+ 'hintchars',
+ 'hint characters on follow mode',
+ 'abcdefghijklmnopqrstuvwxyz'),
+ new Def(
+ 'smoothscroll',
+ 'smooth scroll',
+ false),
+ new Def(
+ 'complete',
+ 'which are completed at the open page',
+ 'sbh'),
+];
diff --git a/src/shared/property-defs.ts b/src/shared/property-defs.ts
new file mode 100644
index 0000000..6315030
--- /dev/null
+++ b/src/shared/property-defs.ts
@@ -0,0 +1,50 @@
+export type Type = string | number | boolean;
+
+export class Def {
+ private name0: string;
+
+ private description0: string;
+
+ private defaultValue0: Type;
+
+ constructor(
+ name: string,
+ description: string,
+ defaultValue: Type,
+ ) {
+ this.name0 = name;
+ this.description0 = description;
+ this.defaultValue0 = defaultValue;
+ }
+
+ public get name(): string {
+ return this.name0;
+ }
+
+ public get defaultValue(): Type {
+ return this.defaultValue0;
+ }
+
+ public get description(): Type {
+ return this.description0;
+ }
+
+ public get type(): string {
+ return typeof this.defaultValue;
+ }
+}
+
+export const defs: Def[] = [
+ new Def(
+ 'hintchars',
+ 'hint characters on follow mode',
+ 'abcdefghijklmnopqrstuvwxyz'),
+ new Def(
+ 'smoothscroll',
+ 'smooth scroll',
+ false),
+ new Def(
+ 'complete',
+ 'which are completed at the open page',
+ 'sbh'),
+];
diff --git a/src/shared/settings/default.ts b/src/shared/settings/default.ts
deleted file mode 100644
index 6523a74..0000000
--- a/src/shared/settings/default.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-export default {
- source: 'json',
- json: `{
- "keymaps": {
- "0": { "type": "scroll.home" },
- ":": { "type": "command.show" },
- "o": { "type": "command.show.open", "alter": false },
- "O": { "type": "command.show.open", "alter": true },
- "t": { "type": "command.show.tabopen", "alter": false },
- "T": { "type": "command.show.tabopen", "alter": true },
- "w": { "type": "command.show.winopen", "alter": false },
- "W": { "type": "command.show.winopen", "alter": true },
- "b": { "type": "command.show.buffer" },
- "a": { "type": "command.show.addbookmark", "alter": true },
- "k": { "type": "scroll.vertically", "count": -1 },
- "j": { "type": "scroll.vertically", "count": 1 },
- "h": { "type": "scroll.horizonally", "count": -1 },
- "l": { "type": "scroll.horizonally", "count": 1 },
- "<C-U>": { "type": "scroll.pages", "count": -0.5 },
- "<C-D>": { "type": "scroll.pages", "count": 0.5 },
- "<C-B>": { "type": "scroll.pages", "count": -1 },
- "<C-F>": { "type": "scroll.pages", "count": 1 },
- "gg": { "type": "scroll.top" },
- "G": { "type": "scroll.bottom" },
- "$": { "type": "scroll.end" },
- "d": { "type": "tabs.close" },
- "D": { "type": "tabs.close.right" },
- "!d": { "type": "tabs.close.force" },
- "u": { "type": "tabs.reopen" },
- "K": { "type": "tabs.prev", "count": 1 },
- "J": { "type": "tabs.next", "count": 1 },
- "gT": { "type": "tabs.prev", "count": 1 },
- "gt": { "type": "tabs.next", "count": 1 },
- "g0": { "type": "tabs.first" },
- "g$": { "type": "tabs.last" },
- "<C-6>": { "type": "tabs.prevsel" },
- "r": { "type": "tabs.reload", "cache": false },
- "R": { "type": "tabs.reload", "cache": true },
- "zp": { "type": "tabs.pin.toggle" },
- "zd": { "type": "tabs.duplicate" },
- "zi": { "type": "zoom.in" },
- "zo": { "type": "zoom.out" },
- "zz": { "type": "zoom.neutral" },
- "f": { "type": "follow.start", "newTab": false },
- "F": { "type": "follow.start", "newTab": true, "background": false },
- "m": { "type": "mark.set.prefix" },
- "'": { "type": "mark.jump.prefix" },
- "H": { "type": "navigate.history.prev" },
- "L": { "type": "navigate.history.next" },
- "[[": { "type": "navigate.link.prev" },
- "]]": { "type": "navigate.link.next" },
- "gu": { "type": "navigate.parent" },
- "gU": { "type": "navigate.root" },
- "gi": { "type": "focus.input" },
- "gf": { "type": "page.source" },
- "gh": { "type": "page.home" },
- "gH": { "type": "page.home", "newTab": true },
- "y": { "type": "urls.yank" },
- "p": { "type": "urls.paste", "newTab": false },
- "P": { "type": "urls.paste", "newTab": true },
- "/": { "type": "find.start" },
- "n": { "type": "find.next" },
- "N": { "type": "find.prev" },
- "<S-Esc>": { "type": "addon.toggle.enabled" }
- },
- "search": {
- "default": "google",
- "engines": {
- "google": "https://google.com/search?q={}",
- "yahoo": "https://search.yahoo.com/search?p={}",
- "bing": "https://www.bing.com/search?q={}",
- "duckduckgo": "https://duckduckgo.com/?q={}",
- "twitter": "https://twitter.com/search?q={}",
- "wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
- }
- },
- "properties": {
- "hintchars": "abcdefghijklmnopqrstuvwxyz",
- "smoothscroll": false,
- "complete": "sbh"
- },
- "blacklist": [
- ]
-}`,
-};
diff --git a/src/shared/settings/properties.ts b/src/shared/settings/properties.ts
deleted file mode 100644
index 7d037df..0000000
--- a/src/shared/settings/properties.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-// describe types of a propety as:
-// mystr: 'string',
-// mynum: 'number',
-// mybool: 'boolean',
-const types: { [key: string]: string } = {
- hintchars: 'string',
- smoothscroll: 'boolean',
- complete: 'string',
-};
-
-// describe default values of a property
-const defaults: { [key: string]: string | number | boolean } = {
- hintchars: 'abcdefghijklmnopqrstuvwxyz',
- smoothscroll: false,
- complete: 'sbh',
-};
-
-const docs: { [key: string]: string } = {
- hintchars: 'hint characters on follow mode',
- smoothscroll: 'smooth scroll',
- complete: 'which are completed at the open page',
-};
-
-export { types, defaults, docs };
diff --git a/src/shared/settings/storage.ts b/src/shared/settings/storage.ts
deleted file mode 100644
index 90a3a66..0000000
--- a/src/shared/settings/storage.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import DefaultSettings from './default';
-import * as settingsValues from './values';
-
-const loadRaw = async(): Promise<any> => {
- let { settings } = await browser.storage.local.get('settings');
- if (!settings) {
- return DefaultSettings;
- }
- return { ...DefaultSettings, ...settings as object };
-};
-
-const loadValue = async() => {
- let settings = await loadRaw();
- let value = JSON.parse(DefaultSettings.json);
- if (settings.source === 'json') {
- value = settingsValues.valueFromJson(settings.json);
- } else if (settings.source === 'form') {
- value = settingsValues.valueFromForm(settings.form);
- }
- if (!value.properties) {
- value.properties = {};
- }
- return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value };
-};
-
-const save = (settings: any): Promise<any> => {
- return browser.storage.local.set({
- settings,
- });
-};
-
-export { loadRaw, loadValue, save };
diff --git a/src/shared/settings/validator.ts b/src/shared/settings/validator.ts
deleted file mode 100644
index 71cc466..0000000
--- a/src/shared/settings/validator.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import * as operations from '../operations';
-import * as properties from './properties';
-
-const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties'];
-const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => {
- return operations[key];
-});
-
-const validateInvalidTopKeys = (settings: any): void => {
- let invalidKey = Object.keys(settings).find((key) => {
- return !VALID_TOP_KEYS.includes(key);
- });
- if (invalidKey) {
- throw Error(`Unknown key: "${invalidKey}"`);
- }
-};
-
-const validateKeymaps = (keymaps: any): void => {
- for (let key of Object.keys(keymaps)) {
- let value = keymaps[key];
- if (!VALID_OPERATION_VALUES.includes(value.type)) {
- throw Error(`Unknown operation: "${value.type}"`);
- }
- }
-};
-
-const validateSearch = (search: any): void => {
- let engines = search.engines;
- for (let key of Object.keys(engines)) {
- if ((/\s/).test(key)) {
- throw new Error(
- `While space in search engine name is not allowed: "${key}"`
- );
- }
- let url = engines[key];
- if (!url.match(/{}/)) {
- throw new Error(`No {}-placeholders in URL of "${key}"`);
- }
- if (url.match(/{}/g).length > 1) {
- throw new Error(`Multiple {}-placeholders in URL of "${key}"`);
- }
- }
-
- if (!search.default) {
- throw new Error(`Default engine is not set`);
- }
- if (!Object.keys(engines).includes(search.default)) {
- throw new Error(`Default engine "${search.default}" not found`);
- }
-};
-
-const validateProperties = (props: any): void => {
- for (let name of Object.keys(props)) {
- if (!properties.types[name]) {
- throw new Error(`Unknown property name: "${name}"`);
- }
- if (typeof props[name] !== properties.types[name]) {
- throw new Error(`Invalid type for property: "${name}"`);
- }
- }
-};
-
-const validate = (settings: any): void => {
- validateInvalidTopKeys(settings);
- if (settings.keymaps) {
- validateKeymaps(settings.keymaps);
- }
- if (settings.search) {
- validateSearch(settings.search);
- }
- if (settings.properties) {
- validateProperties(settings.properties);
- }
-};
-
-export { validate };
diff --git a/src/shared/settings/values.ts b/src/shared/settings/values.ts
deleted file mode 100644
index cb6a668..0000000
--- a/src/shared/settings/values.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import * as properties from './properties';
-
-const operationFromFormName = (name: string): any => {
- let [type, argStr] = name.split('?');
- let args = {};
- if (argStr) {
- args = JSON.parse(argStr);
- }
- return { type, ...args };
-};
-
-const operationToFormName = (op: any): string => {
- let type = op.type;
- let args = { ...op };
- delete args.type;
-
- if (Object.keys(args).length === 0) {
- return type;
- }
- return op.type + '?' + JSON.stringify(args);
-};
-
-const valueFromJson = (json: string): object => {
- return JSON.parse(json);
-};
-
-const valueFromForm = (form: any): object => {
- let keymaps: any = undefined;
- if (form.keymaps) {
- keymaps = {};
- for (let name of Object.keys(form.keymaps)) {
- let keys = form.keymaps[name];
- keymaps[keys] = operationFromFormName(name);
- }
- }
-
- let search: any = undefined;
- if (form.search) {
- search = { default: form.search.default };
-
- if (form.search.engines) {
- search.engines = {};
- for (let [name, url] of form.search.engines) {
- search.engines[name] = url;
- }
- }
- }
-
- return {
- keymaps,
- search,
- blacklist: form.blacklist,
- properties: form.properties
- };
-};
-
-const jsonFromValue = (value: any): string => {
- return JSON.stringify(value, undefined, 2);
-};
-
-const formFromValue = (value: any, allowedOps: any[]): any => {
- let keymaps: any = undefined;
-
- if (value.keymaps) {
- let allowedSet = new Set(allowedOps);
-
- keymaps = {};
- for (let keys of Object.keys(value.keymaps)) {
- let op = operationToFormName(value.keymaps[keys]);
- if (allowedSet.has(op)) {
- keymaps[op] = keys;
- }
- }
- }
-
- let search: any = undefined;
- if (value.search) {
- search = { default: value.search.default };
- if (value.search.engines) {
- search.engines = Object.keys(value.search.engines).map((name) => {
- return [name, value.search.engines[name]];
- });
- }
- }
-
- let formProperties = { ...properties.defaults, ...value.properties };
-
- return {
- keymaps,
- search,
- blacklist: value.blacklist,
- properties: formProperties,
- };
-};
-
-const jsonFromForm = (form: any): string => {
- return jsonFromValue(valueFromForm(form));
-};
-
-const formFromJson = (json: string, allowedOps: any[]): any => {
- let value = valueFromJson(json);
- return formFromValue(value, allowedOps);
-};
-
-export {
- valueFromJson, valueFromForm, jsonFromValue, formFromValue,
- jsonFromForm, formFromJson
-};