Declare setting types

This commit is contained in:
Shin'ya Ueoka 2019-05-05 08:03:29 +09:00
parent d01db82c0d
commit a0882bbceb
48 changed files with 1618 additions and 903 deletions

414
src/shared/SettingData.ts Normal file
View file

@ -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": [
]
}`,
});

200
src/shared/Settings.ts Normal file
View file

@ -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: []
};

View file

@ -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);
};

50
src/shared/properties.ts Normal file
View file

@ -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'),
];

View file

@ -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'),
];

View file

@ -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": [
]
}`,
};

View file

@ -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 };

View file

@ -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 };

View file

@ -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 };

View file

@ -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
};