aboutsummaryrefslogtreecommitdiff
path: root/src/shared
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2019-10-07 12:54:32 +0000
committerGitHub <noreply@github.com>2019-10-07 12:54:32 +0000
commit8eddcc1785a85bbe74be254d1055ebe5125dad10 (patch)
treef3f51320d12a90a1b421ed8b1f811c576996ea8e /src/shared
parent7fc2bb615f530fc6adfade54b9553568f5d50ceb (diff)
parentb77a4734985722e96066e713f3b1b9e81a6e1811 (diff)
Merge pull request #654 from ueokande/settings-as-a-class
Refactor settings on shared logics
Diffstat (limited to 'src/shared')
-rw-r--r--src/shared/SettingData.ts224
-rw-r--r--src/shared/Settings.ts200
-rw-r--r--src/shared/blacklists.ts13
-rw-r--r--src/shared/properties.ts50
-rw-r--r--src/shared/property-defs.ts56
-rw-r--r--src/shared/settings/Blacklist.ts39
-rw-r--r--src/shared/settings/Key.ts61
-rw-r--r--src/shared/settings/KeySequence.ts54
-rw-r--r--src/shared/settings/Keymaps.ts37
-rw-r--r--src/shared/settings/Properties.ts110
-rw-r--r--src/shared/settings/Search.ts76
-rw-r--r--src/shared/settings/Settings.ts158
-rw-r--r--src/shared/urls.ts4
-rw-r--r--src/shared/utils/re.ts6
14 files changed, 611 insertions, 477 deletions
diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts
index 14a7d35..532570e 100644
--- a/src/shared/SettingData.ts
+++ b/src/shared/SettingData.ts
@@ -1,15 +1,19 @@
import * as operations from './operations';
-import Settings, * as settings from './Settings';
+import Settings, { DefaultSettingJSONText } from './settings/Settings';
+import Keymaps from './settings/Keymaps';
+import Search from './settings/Search';
+import Properties from './settings/Properties';
+import Blacklist from './settings/Blacklist';
export class FormKeymaps {
- private data: {[op: string]: string};
+ private readonly data: {[op: string]: string};
- constructor(data: {[op: string]: string}) {
+ private constructor(data: {[op: string]: string}) {
this.data = data;
}
- toKeymaps(): settings.Keymaps {
- let keymaps: settings.Keymaps = {};
+ toKeymaps(): Keymaps {
+ let keymaps: { [key: string]: operations.Operation } = {};
for (let name of Object.keys(this.data)) {
let [type, argStr] = name.split('?');
let args = {};
@@ -19,7 +23,7 @@ export class FormKeymaps {
let key = this.data[name];
keymaps[key] = operations.valueOf({ type, ...args });
}
- return keymaps;
+ return Keymaps.fromJSON(keymaps);
}
toJSON(): {[op: string]: string} {
@@ -34,7 +38,7 @@ export class FormKeymaps {
return new FormKeymaps(newData);
}
- static valueOf(o: ReturnType<FormKeymaps['toJSON']>): FormKeymaps {
+ static fromJSON(o: ReturnType<FormKeymaps['toJSON']>): FormKeymaps {
let data: {[op: string]: string} = {};
for (let op of Object.keys(o)) {
data[op] = o[op] as string;
@@ -42,10 +46,11 @@ export class FormKeymaps {
return new FormKeymaps(data);
}
- static fromKeymaps(keymaps: settings.Keymaps): FormKeymaps {
+ static fromKeymaps(keymaps: Keymaps): FormKeymaps {
+ let json = keymaps.toJSON();
let data: {[op: string]: string} = {};
- for (let key of Object.keys(keymaps)) {
- let op = keymaps[key];
+ for (let key of Object.keys(json)) {
+ let op = json[key];
let args = { ...op };
delete args.type;
@@ -60,24 +65,21 @@ export class FormKeymaps {
}
export class FormSearch {
- private default: string;
+ private readonly default: string;
- private engines: string[][];
+ private readonly 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;
- }, {}),
- };
+ toSearchSettings(): Search {
+ let engines: { [name: string]: string } = {};
+ for (let entry of this.engines) {
+ engines[entry[0]] = entry[1];
+ }
+ return new Search(this.default, engines);
}
toJSON(): {
@@ -90,7 +92,7 @@ export class FormSearch {
};
}
- static valueOf(o: ReturnType<FormSearch['toJSON']>): FormSearch {
+ static fromJSON(o: ReturnType<FormSearch['toJSON']>): FormSearch {
if (!Object.prototype.hasOwnProperty.call(o, 'default')) {
throw new TypeError(`"default" field not set`);
}
@@ -100,53 +102,58 @@ export class FormSearch {
return new FormSearch(o.default, o.engines);
}
- static fromSearch(search: settings.Search): FormSearch {
+ static fromSearch(search: Search): FormSearch {
let engines = Object.entries(search.engines).reduce(
(o: string[][], [name, url]) => {
return o.concat([[name, url]]);
}, []);
- return new FormSearch(search.default, engines);
+ return new FormSearch(search.defaultEngine, engines);
}
}
-export class JSONSettings {
- private json: string;
-
- constructor(json: any) {
- this.json = json;
+export class JSONTextSettings {
+ constructor(
+ private json: string,
+ ) {
}
toSettings(): Settings {
- return settings.valueOf(JSON.parse(this.json));
+ return Settings.fromJSON(JSON.parse(this.json));
}
- toJSON(): string {
+ toJSONText(): string {
return this.json;
}
- static valueOf(o: ReturnType<JSONSettings['toJSON']>): JSONSettings {
- return new JSONSettings(o);
+ static fromText(o: string): JSONTextSettings {
+ return new JSONTextSettings(o);
}
- static fromSettings(data: Settings): JSONSettings {
- return new JSONSettings(JSON.stringify(data, undefined, 2));
+ static fromSettings(data: Settings): JSONTextSettings {
+ let json = {
+ keymaps: data.keymaps.toJSON(),
+ search: data.search,
+ properties: data.properties,
+ blacklist: data.blacklist,
+ };
+ return new JSONTextSettings(JSON.stringify(json, undefined, 2));
}
}
export class FormSettings {
- private keymaps: FormKeymaps;
+ public readonly keymaps: FormKeymaps;
- private search: FormSearch;
+ public readonly search: FormSearch;
- private properties: settings.Properties;
+ public readonly properties: Properties;
- private blacklist: string[];
+ public readonly blacklist: Blacklist;
constructor(
keymaps: FormKeymaps,
search: FormSearch,
- properties: settings.Properties,
- blacklist: string[],
+ properties: Properties,
+ blacklist: Blacklist,
) {
this.keymaps = keymaps;
this.search = search;
@@ -172,7 +179,7 @@ export class FormSettings {
);
}
- buildWithProperties(props: settings.Properties): FormSettings {
+ buildWithProperties(props: Properties): FormSettings {
return new FormSettings(
this.keymaps,
this.search,
@@ -181,7 +188,7 @@ export class FormSettings {
);
}
- buildWithBlacklist(blacklist: string[]): FormSettings {
+ buildWithBlacklist(blacklist: Blacklist): FormSettings {
return new FormSettings(
this.keymaps,
this.search,
@@ -191,39 +198,39 @@ export class FormSettings {
}
toSettings(): Settings {
- return settings.valueOf({
- keymaps: this.keymaps.toKeymaps(),
- search: this.search.toSearchSettings(),
- properties: this.properties,
- blacklist: this.blacklist,
+ return Settings.fromJSON({
+ keymaps: this.keymaps.toKeymaps().toJSON(),
+ search: this.search.toSearchSettings().toJSON(),
+ properties: this.properties.toJSON(),
+ blacklist: this.blacklist.toJSON(),
});
}
toJSON(): {
keymaps: ReturnType<FormKeymaps['toJSON']>;
search: ReturnType<FormSearch['toJSON']>;
- properties: settings.Properties;
- blacklist: string[];
+ properties: ReturnType<Properties['toJSON']>;
+ blacklist: ReturnType<Blacklist['toJSON']>;
} {
return {
keymaps: this.keymaps.toJSON(),
search: this.search.toJSON(),
- properties: this.properties,
- blacklist: this.blacklist,
+ properties: this.properties.toJSON(),
+ blacklist: this.blacklist.toJSON(),
};
}
- static valueOf(o: ReturnType<FormSettings['toJSON']>): FormSettings {
+ static fromJSON(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),
+ FormKeymaps.fromJSON(o.keymaps),
+ FormSearch.fromJSON(o.search),
+ Properties.fromJSON(o.properties),
+ Blacklist.fromJSON(o.blacklist),
);
}
@@ -244,7 +251,7 @@ export enum SettingSource {
export default class SettingData {
private source: SettingSource;
- private json?: JSONSettings;
+ private json?: JSONTextSettings;
private form?: FormSettings;
@@ -252,7 +259,7 @@ export default class SettingData {
source, json, form
}: {
source: SettingSource,
- json?: JSONSettings,
+ json?: JSONTextSettings,
form?: FormSettings,
}) {
this.source = source;
@@ -264,7 +271,7 @@ export default class SettingData {
return this.source;
}
- getJSON(): JSONSettings {
+ getJSON(): JSONTextSettings {
if (!this.json) {
throw new TypeError('json settings not set');
}
@@ -283,7 +290,7 @@ export default class SettingData {
case SettingSource.JSON:
return {
source: this.source,
- json: (this.json as JSONSettings).toJSON(),
+ json: (this.json as JSONTextSettings).toJSONText(),
};
case SettingSource.Form:
return {
@@ -304,7 +311,7 @@ export default class SettingData {
throw new Error(`unknown settings source: ${this.source}`);
}
- static valueOf(o: {
+ static fromJSON(o: {
source: string;
json?: string;
form?: ReturnType<FormSettings['toJSON']>;
@@ -313,13 +320,13 @@ export default class SettingData {
case SettingSource.JSON:
return new SettingData({
source: o.source,
- json: JSONSettings.valueOf(
- o.json as ReturnType<JSONSettings['toJSON']>),
+ json: JSONTextSettings.fromText(
+ o.json as ReturnType<JSONTextSettings['toJSONText']>),
});
case SettingSource.Form:
return new SettingData({
source: o.source,
- form: FormSettings.valueOf(
+ form: FormSettings.fromJSON(
o.form as ReturnType<FormSettings['toJSON']>),
});
}
@@ -327,90 +334,7 @@ export default class SettingData {
}
}
-export const DefaultSettingData: SettingData = SettingData.valueOf({
+export const DefaultSettingData: SettingData = SettingData.fromJSON({
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", "select": "left" },
- "x$": { "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" },
- ".": { "type": "repeat.last" },
- "<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": [
- ]
-}`,
+ json: DefaultSettingJSONText,
});
diff --git a/src/shared/Settings.ts b/src/shared/Settings.ts
deleted file mode 100644
index d338e2a..0000000
--- a/src/shared/Settings.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-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[];
-}
-
-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 {
- ...PropertyDefs.defaultValues,
- ...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 };
- for (let key of Object.keys(o)) {
- switch (key) {
- case 'keymaps':
- settings.keymaps = keymapsValueOf(o.keymaps);
- break;
- case 'search':
- settings.search = searchValueOf(o.search);
- break;
- case 'properties':
- settings.properties = propertiesValueOf(o.properties);
- break;
- case 'blacklist':
- settings.blacklist = blacklistValueOf(o.blacklist);
- break;
- default:
- throw new TypeError('unknown setting: ' + key);
- }
- }
- return settings;
-};
-
-export 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', 'select': 'left' },
- 'x$': { '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' },
- '.': { 'type': 'repeat.last' },
- '<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/blacklists.ts b/src/shared/blacklists.ts
deleted file mode 100644
index 61ee4de..0000000
--- a/src/shared/blacklists.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as re from './utils/re';
-
-const includes = (blacklist: string[], url: string): boolean => {
- let u = new URL(url);
- return blacklist.some((item) => {
- if (!item.includes('/')) {
- return re.fromWildcard(item).test(u.host);
- }
- return re.fromWildcard(item).test(u.host + u.pathname);
- });
-};
-
-export { includes };
diff --git a/src/shared/properties.ts b/src/shared/properties.ts
deleted file mode 100644
index 6315030..0000000
--- a/src/shared/properties.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-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
deleted file mode 100644
index fec9f80..0000000
--- a/src/shared/property-defs.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-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'),
-];
-
-export const defaultValues = {
- hintchars: 'abcdefghijklmnopqrstuvwxyz',
- smoothscroll: false,
- complete: 'sbh',
-};
diff --git a/src/shared/settings/Blacklist.ts b/src/shared/settings/Blacklist.ts
new file mode 100644
index 0000000..a95b606
--- /dev/null
+++ b/src/shared/settings/Blacklist.ts
@@ -0,0 +1,39 @@
+export type BlacklistJSON = string[];
+
+const fromWildcard = (pattern: string): RegExp => {
+ let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
+ return new RegExp(regexStr);
+};
+
+export default class Blacklist {
+ constructor(
+ private blacklist: string[],
+ ) {
+ }
+
+ static fromJSON(json: any): Blacklist {
+ if (!Array.isArray(json)) {
+ throw new TypeError(`"blacklist" is not an array of string`);
+ }
+ for (let x of json) {
+ if (typeof x !== 'string') {
+ throw new TypeError(`"blacklist" is not an array of string`);
+ }
+ }
+ return new Blacklist(json);
+ }
+
+ toJSON(): BlacklistJSON {
+ return this.blacklist;
+ }
+
+ includes(url: string): boolean {
+ let u = new URL(url);
+ return this.blacklist.some((item) => {
+ if (!item.includes('/')) {
+ return fromWildcard(item).test(u.host);
+ }
+ return fromWildcard(item).test(u.host + u.pathname);
+ });
+ }
+}
diff --git a/src/shared/settings/Key.ts b/src/shared/settings/Key.ts
new file mode 100644
index 0000000..b11eeb2
--- /dev/null
+++ b/src/shared/settings/Key.ts
@@ -0,0 +1,61 @@
+export default class Key {
+ public readonly key: string;
+
+ public readonly shift: boolean;
+
+ public readonly ctrl: boolean;
+
+ public readonly alt: boolean;
+
+ public readonly meta: boolean;
+
+ constructor({ key, shift, ctrl, alt, meta }: {
+ key: string;
+ shift: boolean;
+ ctrl: boolean;
+ alt: boolean;
+ meta: boolean;
+ }) {
+ this.key = key;
+ this.shift = shift;
+ this.ctrl = ctrl;
+ this.alt = alt;
+ this.meta = meta;
+ }
+
+ static fromMapKey(str: string): Key {
+ if (str.startsWith('<') && str.endsWith('>')) {
+ let inner = str.slice(1, -1);
+ let shift = inner.includes('S-');
+ let base = inner.slice(inner.lastIndexOf('-') + 1);
+ if (shift && base.length === 1) {
+ base = base.toUpperCase();
+ } else if (!shift && base.length === 1) {
+ base = base.toLowerCase();
+ }
+ return new Key({
+ key: base,
+ shift: shift,
+ ctrl: inner.includes('C-'),
+ alt: inner.includes('A-'),
+ meta: inner.includes('M-'),
+ });
+ }
+
+ return new Key({
+ key: str,
+ shift: str.toLowerCase() !== str,
+ ctrl: false,
+ alt: false,
+ meta: false,
+ });
+ }
+
+ equals(key: Key) {
+ return this.key === key.key &&
+ this.ctrl === key.ctrl &&
+ this.meta === key.meta &&
+ this.alt === key.alt &&
+ this.shift === key.shift;
+ }
+}
diff --git a/src/shared/settings/KeySequence.ts b/src/shared/settings/KeySequence.ts
new file mode 100644
index 0000000..4955583
--- /dev/null
+++ b/src/shared/settings/KeySequence.ts
@@ -0,0 +1,54 @@
+import Key from '../../shared/settings/Key';
+
+export default class KeySequence {
+ constructor(
+ public readonly keys: Key[],
+ ) {
+ }
+
+ push(key: Key): number {
+ return this.keys.push(key);
+ }
+
+ length(): number {
+ return this.keys.length;
+ }
+
+ startsWith(o: KeySequence): boolean {
+ if (this.keys.length < o.keys.length) {
+ return false;
+ }
+ for (let i = 0; i < o.keys.length; ++i) {
+ if (!this.keys[i].equals(o.keys[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static fromMapKeys(keys: string): KeySequence {
+ const fromMapKeysRecursive = (
+ remaining: string, mappedKeys: Key[],
+ ): Key[] => {
+ if (remaining.length === 0) {
+ return mappedKeys;
+ }
+
+ let nextPos = 1;
+ if (remaining.startsWith('<')) {
+ let ltPos = remaining.indexOf('>');
+ if (ltPos > 0) {
+ nextPos = ltPos + 1;
+ }
+ }
+
+ return fromMapKeysRecursive(
+ remaining.slice(nextPos),
+ mappedKeys.concat([Key.fromMapKey(remaining.slice(0, nextPos))])
+ );
+ };
+
+ let data = fromMapKeysRecursive(keys, []);
+ return new KeySequence(data);
+ }
+}
diff --git a/src/shared/settings/Keymaps.ts b/src/shared/settings/Keymaps.ts
new file mode 100644
index 0000000..a5558b0
--- /dev/null
+++ b/src/shared/settings/Keymaps.ts
@@ -0,0 +1,37 @@
+import * as operations from '../operations';
+
+export type KeymapsJSON = { [key: string]: operations.Operation };
+
+export default class Keymaps {
+ constructor(
+ private readonly data: KeymapsJSON,
+ ) {
+ }
+
+ static fromJSON(json: any): Keymaps {
+ if (typeof json !== 'object' || json === null) {
+ throw new TypeError('invalid keymaps type: ' + JSON.stringify(json));
+ }
+
+ let data: KeymapsJSON = {};
+ for (let key of Object.keys(json)) {
+ data[key] = operations.valueOf(json[key]);
+ }
+ return new Keymaps(data);
+ }
+
+ combine(other: Keymaps): Keymaps {
+ return new Keymaps({
+ ...this.data,
+ ...other.data,
+ });
+ }
+
+ toJSON(): KeymapsJSON {
+ return this.data;
+ }
+
+ entries(): [string, operations.Operation][] {
+ return Object.entries(this.data);
+ }
+}
diff --git a/src/shared/settings/Properties.ts b/src/shared/settings/Properties.ts
new file mode 100644
index 0000000..63ff991
--- /dev/null
+++ b/src/shared/settings/Properties.ts
@@ -0,0 +1,110 @@
+export type PropertiesJSON = {
+ hintchars?: string;
+ smoothscroll?: boolean;
+ complete?: string;
+};
+
+export type PropertyTypes = {
+ hintchars: string;
+ smoothscroll: string;
+ complete: string;
+};
+
+type PropertyName = 'hintchars' | 'smoothscroll' | 'complete';
+
+type PropertyDef = {
+ name: PropertyName;
+ description: string;
+ defaultValue: string | number | boolean;
+ type: 'string' | 'number' | 'boolean';
+};
+
+const defs: PropertyDef[] = [
+ {
+ name: 'hintchars',
+ description: 'hint characters on follow mode',
+ defaultValue: 'abcdefghijklmnopqrstuvwxyz',
+ type: 'string',
+ }, {
+ name: 'smoothscroll',
+ description: 'smooth scroll',
+ defaultValue: false,
+ type: 'boolean',
+ }, {
+ name: 'complete',
+ description: 'which are completed at the open page',
+ defaultValue: 'sbh',
+ type: 'string',
+ }
+];
+
+const defaultValues = {
+ hintchars: 'abcdefghijklmnopqrstuvwxyz',
+ smoothscroll: false,
+ complete: 'sbh',
+};
+
+export default class Properties {
+ public hintchars: string;
+
+ public smoothscroll: boolean;
+
+ public complete: string;
+
+ constructor({
+ hintchars,
+ smoothscroll,
+ complete,
+ }: {
+ hintchars?: string;
+ smoothscroll?: boolean;
+ complete?: string;
+ } = {}) {
+ this.hintchars = hintchars || defaultValues.hintchars;
+ this.smoothscroll = smoothscroll || defaultValues.smoothscroll;
+ this.complete = complete || defaultValues.complete;
+ }
+
+ static fromJSON(json: any): Properties {
+ let defNames: Set<string> = new Set(defs.map(def => def.name));
+ let unknownName = Object.keys(json).find(name => !defNames.has(name));
+ if (unknownName) {
+ throw new TypeError(`Unknown property name: "${unknownName}"`);
+ }
+
+ for (let def of defs) {
+ if (!Object.prototype.hasOwnProperty.call(json, def.name)) {
+ continue;
+ }
+ if (typeof json[def.name] !== def.type) {
+ throw new TypeError(
+ `property "${def.name}" is not ${def.type}`);
+ }
+ }
+ return new Properties(json);
+ }
+
+ static types(): PropertyTypes {
+ return {
+ hintchars: 'string',
+ smoothscroll: 'boolean',
+ complete: 'string',
+ };
+ }
+
+ static def(name: string): PropertyDef | undefined {
+ return defs.find(p => p.name === name);
+ }
+
+ static defs(): PropertyDef[] {
+ return defs;
+ }
+
+ toJSON(): PropertiesJSON {
+ return {
+ hintchars: this.hintchars,
+ smoothscroll: this.smoothscroll,
+ complete: this.complete,
+ };
+ }
+}
diff --git a/src/shared/settings/Search.ts b/src/shared/settings/Search.ts
new file mode 100644
index 0000000..4580236
--- /dev/null
+++ b/src/shared/settings/Search.ts
@@ -0,0 +1,76 @@
+type Entries = { [name: string]: string };
+
+export type SearchJSON = {
+ default: string;
+ engines: { [key: string]: string };
+};
+
+export default class Search {
+ constructor(
+ public defaultEngine: string,
+ public engines: Entries,
+ ) {
+ }
+
+ static fromJSON(json: any): Search {
+ let defaultEngine = Search.getStringField(json, 'default');
+ let engines = Search.getObjectField(json, 'engines');
+
+ for (let [name, url] of Object.entries(engines)) {
+ if ((/\s/).test(name)) {
+ throw new TypeError(
+ `While space in the search engine not allowed: "${name}"`);
+ }
+ if (typeof url !== 'string') {
+ throw new TypeError(
+ `Invalid type of value in filed "engines": ${JSON.stringify(json)}`);
+ }
+ 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.keys(engines).includes(defaultEngine)) {
+ throw new TypeError(`Default engine "${defaultEngine}" not found`);
+ }
+
+ return new Search(
+ json.default as string,
+ json.engines,
+ );
+ }
+
+ toJSON(): SearchJSON {
+ return {
+ default: this.defaultEngine,
+ engines: this.engines,
+ };
+ }
+
+ private static getStringField(json: any, name: string): string {
+ if (!Object.prototype.hasOwnProperty.call(json, name)) {
+ throw new TypeError(
+ `missing field "${name}" on search: ${JSON.stringify(json)}`);
+ }
+ if (typeof json[name] !== 'string') {
+ throw new TypeError(
+ `invalid type of filed "${name}" on search: ${JSON.stringify(json)}`);
+ }
+ return json[name];
+ }
+
+ private static getObjectField(json: any, name: string): Object {
+ if (!Object.prototype.hasOwnProperty.call(json, name)) {
+ throw new TypeError(
+ `missing field "${name}" on search: ${JSON.stringify(json)}`);
+ }
+ if (typeof json[name] !== 'object' || json[name] === null) {
+ throw new TypeError(
+ `invalid type of filed "${name}" on search: ${JSON.stringify(json)}`);
+ }
+ return json[name];
+ }
+}
diff --git a/src/shared/settings/Settings.ts b/src/shared/settings/Settings.ts
new file mode 100644
index 0000000..2c9e37f
--- /dev/null
+++ b/src/shared/settings/Settings.ts
@@ -0,0 +1,158 @@
+import Keymaps, { KeymapsJSON } from './Keymaps';
+import Search, { SearchJSON } from './Search';
+import Properties, { PropertiesJSON } from './Properties';
+import Blacklist, { BlacklistJSON } from './Blacklist';
+
+export type SettingsJSON = {
+ keymaps: KeymapsJSON,
+ search: SearchJSON,
+ properties: PropertiesJSON,
+ blacklist: BlacklistJSON,
+};
+
+export default class Settings {
+ public keymaps: Keymaps;
+
+ public search: Search;
+
+ public properties: Properties;
+
+ public blacklist: Blacklist;
+
+ constructor({
+ keymaps,
+ search,
+ properties,
+ blacklist,
+ }: {
+ keymaps: Keymaps;
+ search: Search;
+ properties: Properties;
+ blacklist: Blacklist;
+ }) {
+ this.keymaps = keymaps;
+ this.search = search;
+ this.properties = properties;
+ this.blacklist = blacklist;
+ }
+
+ static fromJSON(json: any): Settings {
+ let settings = { ...DefaultSetting };
+ for (let key of Object.keys(json)) {
+ switch (key) {
+ case 'keymaps':
+ settings.keymaps = Keymaps.fromJSON(json.keymaps);
+ break;
+ case 'search':
+ settings.search = Search.fromJSON(json.search);
+ break;
+ case 'properties':
+ settings.properties = Properties.fromJSON(json.properties);
+ break;
+ case 'blacklist':
+ settings.blacklist = Blacklist.fromJSON(json.blacklist);
+ break;
+ default:
+ throw new TypeError('unknown setting: ' + key);
+ }
+ }
+ return new Settings(settings);
+ }
+
+ toJSON(): SettingsJSON {
+ return {
+ keymaps: this.keymaps.toJSON(),
+ search: this.search.toJSON(),
+ properties: this.properties.toJSON(),
+ blacklist: this.blacklist.toJSON(),
+ };
+ }
+}
+
+export const DefaultSettingJSONText = `{
+ "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", "select": "left" },
+ "x$": { "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" },
+ ".": { "type": "repeat.last" },
+ "<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": [
+ ]
+}`;
+
+export const DefaultSetting: Settings =
+ Settings.fromJSON(JSON.parse(DefaultSettingJSONText));
diff --git a/src/shared/urls.ts b/src/shared/urls.ts
index bbdb1ea..64ea4f2 100644
--- a/src/shared/urls.ts
+++ b/src/shared/urls.ts
@@ -1,4 +1,4 @@
-import { Search } from './Settings';
+import Search from './settings/Search';
const trimStart = (str: string): string => {
// NOTE String.trimStart is available on Firefox 61
@@ -19,7 +19,7 @@ const searchUrl = (keywords: string, search: Search): string => {
if (keywords.includes('.') && !keywords.includes(' ')) {
return 'http://' + keywords;
}
- let template = search.engines[search.default];
+ let template = search.engines[search.defaultEngine];
let query = keywords;
let first = trimStart(keywords).split(' ')[0];
diff --git a/src/shared/utils/re.ts b/src/shared/utils/re.ts
deleted file mode 100644
index 34f4fa6..0000000
--- a/src/shared/utils/re.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-const fromWildcard = (pattern: string): RegExp => {
- let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
- return new RegExp(regexStr);
-};
-
-export { fromWildcard };