aboutsummaryrefslogtreecommitdiff
path: root/src/shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared')
-rw-r--r--src/shared/SettingData.ts34
-rw-r--r--src/shared/messages.ts1
-rw-r--r--src/shared/operations.ts28
-rw-r--r--src/shared/settings/Blacklist.ts49
-rw-r--r--src/shared/settings/Key.ts26
-rw-r--r--src/shared/settings/KeySequence.ts54
-rw-r--r--src/shared/settings/Keymaps.ts24
-rw-r--r--src/shared/settings/Properties.ts18
-rw-r--r--src/shared/settings/Search.ts52
-rw-r--r--src/shared/settings/Settings.ts54
-rw-r--r--src/shared/settings/schema.json84
-rw-r--r--src/shared/settings/validate.js567
-rw-r--r--src/shared/urls.ts33
-rw-r--r--src/shared/utils/dom.ts26
14 files changed, 818 insertions, 232 deletions
diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts
index 532570e..5ad360e 100644
--- a/src/shared/SettingData.ts
+++ b/src/shared/SettingData.ts
@@ -13,14 +13,14 @@ export class FormKeymaps {
}
toKeymaps(): Keymaps {
- let keymaps: { [key: string]: operations.Operation } = {};
- for (let name of Object.keys(this.data)) {
- let [type, argStr] = name.split('?');
+ const keymaps: { [key: string]: operations.Operation } = {};
+ for (const name of Object.keys(this.data)) {
+ const [type, argStr] = name.split('?');
let args = {};
if (argStr) {
args = JSON.parse(argStr);
}
- let key = this.data[name];
+ const key = this.data[name];
keymaps[key] = operations.valueOf({ type, ...args });
}
return Keymaps.fromJSON(keymaps);
@@ -31,7 +31,7 @@ export class FormKeymaps {
}
buildWithOverride(op: string, keys: string): FormKeymaps {
- let newData = {
+ const newData = {
...this.data,
[op]: keys,
};
@@ -39,19 +39,19 @@ export class FormKeymaps {
}
static fromJSON(o: ReturnType<FormKeymaps['toJSON']>): FormKeymaps {
- let data: {[op: string]: string} = {};
- for (let op of Object.keys(o)) {
+ const data: {[op: string]: string} = {};
+ for (const op of Object.keys(o)) {
data[op] = o[op] as string;
}
return new FormKeymaps(data);
}
static fromKeymaps(keymaps: Keymaps): FormKeymaps {
- let json = keymaps.toJSON();
- let data: {[op: string]: string} = {};
- for (let key of Object.keys(json)) {
- let op = json[key];
- let args = { ...op };
+ const json = keymaps.toJSON();
+ const data: {[op: string]: string} = {};
+ for (const key of Object.keys(json)) {
+ const op = json[key];
+ const args = { ...op };
delete args.type;
let name = op.type;
@@ -75,8 +75,8 @@ export class FormSearch {
}
toSearchSettings(): Search {
- let engines: { [name: string]: string } = {};
- for (let entry of this.engines) {
+ const engines: { [name: string]: string } = {};
+ for (const entry of this.engines) {
engines[entry[0]] = entry[1];
}
return new Search(this.default, engines);
@@ -103,7 +103,7 @@ export class FormSearch {
}
static fromSearch(search: Search): FormSearch {
- let engines = Object.entries(search.engines).reduce(
+ const engines = Object.entries(search.engines).reduce(
(o: string[][], [name, url]) => {
return o.concat([[name, url]]);
}, []);
@@ -130,7 +130,7 @@ export class JSONTextSettings {
}
static fromSettings(data: Settings): JSONTextSettings {
- let json = {
+ const json = {
keymaps: data.keymaps.toJSON(),
search: data.search,
properties: data.properties,
@@ -221,7 +221,7 @@ export class FormSettings {
}
static fromJSON(o: ReturnType<FormSettings['toJSON']>): FormSettings {
- for (let name of ['keymaps', 'search', 'properties', 'blacklist']) {
+ for (const name of ['keymaps', 'search', 'properties', 'blacklist']) {
if (!Object.prototype.hasOwnProperty.call(o, name)) {
throw new Error(`"${name}" field not set`);
}
diff --git a/src/shared/messages.ts b/src/shared/messages.ts
index 36a23d8..7f8bd5b 100644
--- a/src/shared/messages.ts
+++ b/src/shared/messages.ts
@@ -49,6 +49,7 @@ export const NAVIGATE_LINK_PREV = 'navigate.link.prev';
export interface BackgroundOperationMessage {
type: typeof BACKGROUND_OPERATION;
+ repeat: number;
operation: operations.Operation;
}
diff --git a/src/shared/operations.ts b/src/shared/operations.ts
index 1ce5256..beca7b9 100644
--- a/src/shared/operations.ts
+++ b/src/shared/operations.ts
@@ -376,7 +376,7 @@ const assertOptionalBoolean = (obj: any, name: string) => {
const assertOptionalString = (obj: any, name: string, values?: string[]) => {
if (Object.prototype.hasOwnProperty.call(obj, name)) {
- let value = obj[name];
+ const value = obj[name];
if (typeof value !== 'string') {
throw new TypeError(
`Not a string parameter: '${name}' (${typeof value})`,
@@ -508,3 +508,29 @@ export const valueOf = (o: any): Operation => {
}
throw new TypeError('Unknown operation type: ' + o.type);
};
+
+export const isNRepeatable = (type: string): boolean => {
+ switch (type) {
+ case SCROLL_VERTICALLY:
+ case SCROLL_HORIZONALLY:
+ case SCROLL_PAGES:
+ case NAVIGATE_HISTORY_PREV:
+ case NAVIGATE_HISTORY_NEXT:
+ case NAVIGATE_PARENT:
+ case TAB_CLOSE:
+ case TAB_CLOSE_FORCE:
+ case TAB_CLOSE_RIGHT:
+ case TAB_REOPEN:
+ case TAB_PREV:
+ case TAB_NEXT:
+ case TAB_DUPLICATE:
+ case ZOOM_IN:
+ case ZOOM_OUT:
+ case URLS_PASTE:
+ case FIND_NEXT:
+ case FIND_PREV:
+ case REPEAT_LAST:
+ return true;
+ }
+ return false;
+};
diff --git a/src/shared/settings/Blacklist.ts b/src/shared/settings/Blacklist.ts
index 0cfbd71..6e6b51c 100644
--- a/src/shared/settings/Blacklist.ts
+++ b/src/shared/settings/Blacklist.ts
@@ -8,22 +8,10 @@ export type BlacklistItemJSON = string | {
export type BlacklistJSON = BlacklistItemJSON[];
const regexFromWildcard = (pattern: string): RegExp => {
- let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
+ const regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
return new RegExp(regexStr);
};
-const isArrayOfString = (raw: any): boolean => {
- if (!Array.isArray(raw)) {
- return false;
- }
- for (let x of Array.from(raw)) {
- if (typeof x !== 'string') {
- return false;
- }
- }
- return true;
-};
-
export class BlacklistItem {
public readonly pattern: string;
@@ -47,30 +35,10 @@ export class BlacklistItem {
this.keyEntities = this.keys.map(Key.fromMapKey);
}
- static fromJSON(raw: any): BlacklistItem {
- if (typeof raw === 'string') {
- return new BlacklistItem(raw, false, []);
- } else if (typeof raw === 'object' && raw !== null) {
- if (!('url' in raw)) {
- throw new TypeError(
- `missing field "url" of blacklist item: ${JSON.stringify(raw)}`);
- }
- if (typeof raw.url !== 'string') {
- throw new TypeError(
- `invalid field "url" of blacklist item: ${JSON.stringify(raw)}`);
- }
- if (!('keys' in raw)) {
- throw new TypeError(
- `missing field "keys" of blacklist item: ${JSON.stringify(raw)}`);
- }
- if (!isArrayOfString(raw.keys)) {
- throw new TypeError(
- `invalid field "keys" of blacklist item: ${JSON.stringify(raw)}`);
- }
- return new BlacklistItem(raw.url as string, true, raw.keys as string[]);
- }
- throw new TypeError(
- `invalid format of blacklist item: ${JSON.stringify(raw)}`);
+ static fromJSON(json: BlacklistItemJSON): BlacklistItem {
+ return typeof json === 'string'
+ ? new BlacklistItem(json, false, [])
+ : new BlacklistItem(json.url, true, json.keys);
}
toJSON(): BlacklistItemJSON {
@@ -103,11 +71,8 @@ export default class Blacklist {
) {
}
- static fromJSON(json: any): Blacklist {
- if (!Array.isArray(json)) {
- throw new TypeError('blacklist is not an array: ' + JSON.stringify(json));
- }
- let items = Array.from(json).map(item => BlacklistItem.fromJSON(item));
+ static fromJSON(json: BlacklistJSON): Blacklist {
+ const items = json.map(o => BlacklistItem.fromJSON(o));
return new Blacklist(items);
}
diff --git a/src/shared/settings/Key.ts b/src/shared/settings/Key.ts
index b11eeb2..cfe1e7e 100644
--- a/src/shared/settings/Key.ts
+++ b/src/shared/settings/Key.ts
@@ -1,3 +1,5 @@
+const digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+
export default class Key {
public readonly key: string;
@@ -9,12 +11,18 @@ export default class Key {
public readonly meta: boolean;
- constructor({ key, shift, ctrl, alt, meta }: {
+ constructor({
+ key,
+ shift = false,
+ ctrl = false,
+ alt = false,
+ meta = false,
+ }: {
key: string;
- shift: boolean;
- ctrl: boolean;
- alt: boolean;
- meta: boolean;
+ shift?: boolean;
+ ctrl?: boolean;
+ alt?: boolean;
+ meta?: boolean;
}) {
this.key = key;
this.shift = shift;
@@ -25,8 +33,8 @@ export default class Key {
static fromMapKey(str: string): Key {
if (str.startsWith('<') && str.endsWith('>')) {
- let inner = str.slice(1, -1);
- let shift = inner.includes('S-');
+ const inner = str.slice(1, -1);
+ const shift = inner.includes('S-');
let base = inner.slice(inner.lastIndexOf('-') + 1);
if (shift && base.length === 1) {
base = base.toUpperCase();
@@ -51,6 +59,10 @@ export default class Key {
});
}
+ isDigit(): boolean {
+ return digits.includes(this.key);
+ }
+
equals(key: Key) {
return this.key === key.key &&
this.ctrl === key.ctrl &&
diff --git a/src/shared/settings/KeySequence.ts b/src/shared/settings/KeySequence.ts
deleted file mode 100644
index abae61a..0000000
--- a/src/shared/settings/KeySequence.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import Key from './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
index a5558b0..3880654 100644
--- a/src/shared/settings/Keymaps.ts
+++ b/src/shared/settings/Keymaps.ts
@@ -1,23 +1,25 @@
import * as operations from '../operations';
-export type KeymapsJSON = { [key: string]: operations.Operation };
+type OperationJson = {
+ type: string
+} | {
+ type: string;
+ [prop: string]: string | number | boolean;
+};
+export type KeymapsJSON = { [key: string]: OperationJson };
export default class Keymaps {
constructor(
- private readonly data: KeymapsJSON,
+ private readonly data: { [key: string]: operations.Operation },
) {
}
- static fromJSON(json: any): Keymaps {
- if (typeof json !== 'object' || json === null) {
- throw new TypeError('invalid keymaps type: ' + JSON.stringify(json));
+ static fromJSON(json: KeymapsJSON): Keymaps {
+ const entries: { [key: string]: operations.Operation } = {};
+ for (const key of Object.keys(json)) {
+ entries[key] = operations.valueOf(json[key]);
}
-
- let data: KeymapsJSON = {};
- for (let key of Object.keys(json)) {
- data[key] = operations.valueOf(json[key]);
- }
- return new Keymaps(data);
+ return new Keymaps(entries);
}
combine(other: Keymaps): Keymaps {
diff --git a/src/shared/settings/Properties.ts b/src/shared/settings/Properties.ts
index 63ff991..27fb62e 100644
--- a/src/shared/settings/Properties.ts
+++ b/src/shared/settings/Properties.ts
@@ -1,3 +1,4 @@
+
export type PropertiesJSON = {
hintchars?: string;
smoothscroll?: boolean;
@@ -65,22 +66,7 @@ export default class Properties {
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}`);
- }
- }
+ static fromJSON(json: PropertiesJSON): Properties {
return new Properties(json);
}
diff --git a/src/shared/settings/Search.ts b/src/shared/settings/Search.ts
index 4580236..7de03de 100644
--- a/src/shared/settings/Search.ts
+++ b/src/shared/settings/Search.ts
@@ -12,35 +12,23 @@ export default class Search {
) {
}
- 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)}`);
+ static fromJSON(json: SearchJSON): Search {
+ for (const [name, url] of Object.entries(json.engines)) {
+ if (!(/^[a-zA-Z0-9]+$/).test(name)) {
+ throw new TypeError('Search engine\'s name must be [a-zA-Z0-9]+');
}
- let matches = url.match(/{}/g);
+ const 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`);
+ if (!Object.keys(json.engines).includes(json.default)) {
+ throw new TypeError(`Default engine "${json.default}" not found`);
}
- return new Search(
- json.default as string,
- json.engines,
- );
+ return new Search(json.default, json.engines);
}
toJSON(): SearchJSON {
@@ -49,28 +37,4 @@ export default class Search {
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
index 2c9e37f..add5389 100644
--- a/src/shared/settings/Settings.ts
+++ b/src/shared/settings/Settings.ts
@@ -1,13 +1,16 @@
+import Ajv from 'ajv';
+
import Keymaps, { KeymapsJSON } from './Keymaps';
import Search, { SearchJSON } from './Search';
import Properties, { PropertiesJSON } from './Properties';
import Blacklist, { BlacklistJSON } from './Blacklist';
+import validate from './validate';
export type SettingsJSON = {
- keymaps: KeymapsJSON,
- search: SearchJSON,
- properties: PropertiesJSON,
- blacklist: BlacklistJSON,
+ keymaps?: KeymapsJSON,
+ search?: SearchJSON,
+ properties?: PropertiesJSON,
+ blacklist?: BlacklistJSON,
};
export default class Settings {
@@ -36,25 +39,30 @@ export default class Settings {
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);
- }
+ static fromJSON(json: unknown): Settings {
+ const valid = validate(json);
+ if (!valid) {
+ const message = (validate as any).errors!!
+ .map((err: Ajv.ErrorObject) => {
+ return `'${err.dataPath}' ${err.message}`;
+ })
+ .join('; ');
+ throw new TypeError(message);
+ }
+
+ const obj = json as SettingsJSON;
+ const settings = { ...DefaultSetting };
+ if (obj.keymaps) {
+ settings.keymaps = Keymaps.fromJSON(obj.keymaps);
+ }
+ if (obj.search) {
+ settings.search = Search.fromJSON(obj.search);
+ }
+ if (obj.properties) {
+ settings.properties = Properties.fromJSON(obj.properties);
+ }
+ if (obj.blacklist) {
+ settings.blacklist = Blacklist.fromJSON(obj.blacklist);
}
return new Settings(settings);
}
diff --git a/src/shared/settings/schema.json b/src/shared/settings/schema.json
new file mode 100644
index 0000000..31d47f1
--- /dev/null
+++ b/src/shared/settings/schema.json
@@ -0,0 +1,84 @@
+{
+ "type": "object",
+ "properties": {
+ "keymaps": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ }
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "default": {
+ "type": "string"
+ },
+ "engines": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "required": [
+ "default",
+ "engines"
+ ]
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "hintchars": {
+ "type": "string"
+ },
+ "smoothscroll": {
+ "type": "boolean"
+ },
+ "complete": {
+ "type": "string"
+ }
+ }
+ },
+ "blacklist": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "keys": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "url",
+ "keys"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/src/shared/settings/validate.js b/src/shared/settings/validate.js
new file mode 100644
index 0000000..236488d
--- /dev/null
+++ b/src/shared/settings/validate.js
@@ -0,0 +1,567 @@
+'use strict';
+var validate = (function() {
+ var pattern0 = new RegExp('.*');
+ var refVal = [];
+ return function validate(data, dataPath, parentData, parentDataProperty, rootData) {
+ 'use strict';
+ var vErrors = null;
+ var errors = 0;
+ if ((data && typeof data === "object" && !Array.isArray(data))) {
+ var errs__0 = errors;
+ var valid1 = true;
+ for (var key0 in data) {
+ var isAdditional0 = !(false || key0 == 'keymaps' || key0 == 'search' || key0 == 'properties' || key0 == 'blacklist');
+ if (isAdditional0) {
+ valid1 = false;
+ validate.errors = [{
+ keyword: 'additionalProperties',
+ dataPath: (dataPath || '') + "",
+ schemaPath: '#/additionalProperties',
+ params: {
+ additionalProperty: '' + key0 + ''
+ },
+ message: 'should NOT have additional properties'
+ }];
+ return false;
+ break;
+ }
+ }
+ if (valid1) {
+ var data1 = data.keymaps;
+ if (data1 === undefined) {
+ valid1 = true;
+ } else {
+ var errs_1 = errors;
+ if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) {
+ var errs__1 = errors;
+ var valid2 = true;
+ for (var key1 in data1) {
+ if (pattern0.test(key1)) {
+ var data2 = data1[key1];
+ var errs_2 = errors;
+ if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) {
+ if (true) {
+ var errs__2 = errors;
+ var valid3 = true;
+ if (data2.type === undefined) {
+ valid3 = false;
+ validate.errors = [{
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\']',
+ schemaPath: '#/properties/keymaps/patternProperties/.*/required',
+ params: {
+ missingProperty: 'type'
+ },
+ message: 'should have required property \'type\''
+ }];
+ return false;
+ } else {
+ var errs_3 = errors;
+ if (typeof data2.type !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\'].type',
+ schemaPath: '#/properties/keymaps/patternProperties/.*/properties/type/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid3 = errors === errs_3;
+ }
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\']',
+ schemaPath: '#/properties/keymaps/patternProperties/.*/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ if (!valid2) break;
+ } else valid2 = true;
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.keymaps',
+ schemaPath: '#/properties/keymaps/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid1 = errors === errs_1;
+ }
+ if (valid1) {
+ var data1 = data.search;
+ if (data1 === undefined) {
+ valid1 = true;
+ } else {
+ var errs_1 = errors;
+ if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) {
+ if (true) {
+ var errs__1 = errors;
+ var valid2 = true;
+ if (data1.default === undefined) {
+ valid2 = false;
+ validate.errors = [{
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.search',
+ schemaPath: '#/properties/search/required',
+ params: {
+ missingProperty: 'default'
+ },
+ message: 'should have required property \'default\''
+ }];
+ return false;
+ } else {
+ var errs_2 = errors;
+ if (typeof data1.default !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.search.default',
+ schemaPath: '#/properties/search/properties/default/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ if (valid2) {
+ var data2 = data1.engines;
+ if (data2 === undefined) {
+ valid2 = false;
+ validate.errors = [{
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.search',
+ schemaPath: '#/properties/search/required',
+ params: {
+ missingProperty: 'engines'
+ },
+ message: 'should have required property \'engines\''
+ }];
+ return false;
+ } else {
+ var errs_2 = errors;
+ if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) {
+ var errs__2 = errors;
+ var valid3 = true;
+ for (var key2 in data2) {
+ if (pattern0.test(key2)) {
+ var errs_3 = errors;
+ if (typeof data2[key2] !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.search.engines[\'' + key2 + '\']',
+ schemaPath: '#/properties/search/properties/engines/patternProperties/.*/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid3 = errors === errs_3;
+ if (!valid3) break;
+ } else valid3 = true;
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.search.engines',
+ schemaPath: '#/properties/search/properties/engines/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ }
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.search',
+ schemaPath: '#/properties/search/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid1 = errors === errs_1;
+ }
+ if (valid1) {
+ var data1 = data.properties;
+ if (data1 === undefined) {
+ valid1 = true;
+ } else {
+ var errs_1 = errors;
+ if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) {
+ var errs__1 = errors;
+ var valid2 = true;
+ if (data1.hintchars === undefined) {
+ valid2 = true;
+ } else {
+ var errs_2 = errors;
+ if (typeof data1.hintchars !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.properties.hintchars',
+ schemaPath: '#/properties/properties/properties/hintchars/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ if (valid2) {
+ if (data1.smoothscroll === undefined) {
+ valid2 = true;
+ } else {
+ var errs_2 = errors;
+ if (typeof data1.smoothscroll !== "boolean") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.properties.smoothscroll',
+ schemaPath: '#/properties/properties/properties/smoothscroll/type',
+ params: {
+ type: 'boolean'
+ },
+ message: 'should be boolean'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ if (valid2) {
+ if (data1.complete === undefined) {
+ valid2 = true;
+ } else {
+ var errs_2 = errors;
+ if (typeof data1.complete !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.properties.complete',
+ schemaPath: '#/properties/properties/properties/complete/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ }
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.properties',
+ schemaPath: '#/properties/properties/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid1 = errors === errs_1;
+ }
+ if (valid1) {
+ var data1 = data.blacklist;
+ if (data1 === undefined) {
+ valid1 = true;
+ } else {
+ var errs_1 = errors;
+ if (Array.isArray(data1)) {
+ var errs__1 = errors;
+ var valid1;
+ for (var i1 = 0; i1 < data1.length; i1++) {
+ var data2 = data1[i1];
+ var errs_2 = errors;
+ var errs__2 = errors;
+ var valid2 = false;
+ var errs_3 = errors;
+ if (typeof data2 !== "string") {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/0/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid3 = errors === errs_3;
+ valid2 = valid2 || valid3;
+ if (!valid2) {
+ var errs_3 = errors;
+ if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) {
+ if (true) {
+ var errs__3 = errors;
+ var valid4 = true;
+ if (data2.url === undefined) {
+ valid4 = false;
+ var err = {
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/required',
+ params: {
+ missingProperty: 'url'
+ },
+ message: 'should have required property \'url\''
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ } else {
+ var errs_4 = errors;
+ if (typeof data2.url !== "string") {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + '].url',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/properties/url/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid4 = errors === errs_4;
+ }
+ if (valid4) {
+ var data3 = data2.keys;
+ if (data3 === undefined) {
+ valid4 = false;
+ var err = {
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/required',
+ params: {
+ missingProperty: 'keys'
+ },
+ message: 'should have required property \'keys\''
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ } else {
+ var errs_4 = errors;
+ if (Array.isArray(data3)) {
+ var errs__4 = errors;
+ var valid4;
+ for (var i4 = 0; i4 < data3.length; i4++) {
+ var errs_5 = errors;
+ if (typeof data3[i4] !== "string") {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + '].keys[' + i4 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/properties/keys/items/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid5 = errors === errs_5;
+ if (!valid5) break;
+ }
+ } else {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + '].keys',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/properties/keys/type',
+ params: {
+ type: 'array'
+ },
+ message: 'should be array'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid4 = errors === errs_4;
+ }
+ }
+ }
+ } else {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid3 = errors === errs_3;
+ valid2 = valid2 || valid3;
+ }
+ if (!valid2) {
+ var err = {
+ keyword: 'anyOf',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf',
+ params: {},
+ message: 'should match some schema in anyOf'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ validate.errors = vErrors;
+ return false;
+ } else {
+ errors = errs__2;
+ if (vErrors !== null) {
+ if (errs__2) vErrors.length = errs__2;
+ else vErrors = null;
+ }
+ }
+ var valid2 = errors === errs_2;
+ if (!valid2) break;
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist',
+ schemaPath: '#/properties/blacklist/type',
+ params: {
+ type: 'array'
+ },
+ message: 'should be array'
+ }];
+ return false;
+ }
+ var valid1 = errors === errs_1;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + "",
+ schemaPath: '#/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ validate.errors = vErrors;
+ return errors === 0;
+ };
+})();
+validate.schema = {
+ "type": "object",
+ "properties": {
+ "keymaps": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ }
+ },
+ "required": ["type"]
+ }
+ }
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "default": {
+ "type": "string"
+ },
+ "engines": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "required": ["default", "engines"]
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "hintchars": {
+ "type": "string"
+ },
+ "smoothscroll": {
+ "type": "boolean"
+ },
+ "complete": {
+ "type": "string"
+ }
+ }
+ },
+ "blacklist": {
+ "type": "array",
+ "items": {
+ "anyOf": [{
+ "type": "string"
+ }, {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "keys": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": ["url", "keys"]
+ }]
+ }
+ }
+ },
+ "additionalProperties": false
+};
+validate.errors = null;
+module.exports = validate; \ No newline at end of file
diff --git a/src/shared/urls.ts b/src/shared/urls.ts
index 64ea4f2..bac929e 100644
--- a/src/shared/urls.ts
+++ b/src/shared/urls.ts
@@ -7,22 +7,47 @@ const trimStart = (str: string): string => {
const SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ftp:', 'mailto:', 'about:'];
+const isLocalhost = (url: string): boolean => {
+ if (url === 'localhost') {
+ return true;
+ }
+
+ const [host, port] = url.split(':', 2);
+ return host === 'localhost' && !isNaN(Number(port));
+};
+
+const isMissingHttp = (keywords: string): boolean => {
+ if (keywords.includes('.') && !keywords.includes(' ')) {
+ return true;
+ }
+
+ try {
+ const u = new URL('http://' + keywords);
+ return isLocalhost(u.host);
+ } catch (e) {
+ // fallthrough
+ }
+ return false;
+};
+
const searchUrl = (keywords: string, search: Search): string => {
try {
- let u = new URL(keywords);
+ const u = new URL(keywords);
if (SUPPORTED_PROTOCOLS.includes(u.protocol.toLowerCase())) {
return u.href;
}
} catch (e) {
// fallthrough
}
- if (keywords.includes('.') && !keywords.includes(' ')) {
+
+ if (isMissingHttp(keywords)) {
return 'http://' + keywords;
}
+
let template = search.engines[search.defaultEngine];
let query = keywords;
- let first = trimStart(keywords).split(' ')[0];
+ const first = trimStart(keywords).split(' ')[0];
if (Object.keys(search.engines).includes(first)) {
template = search.engines[first];
query = trimStart(trimStart(keywords).slice(first.length));
@@ -32,7 +57,7 @@ const searchUrl = (keywords: string, search: Search): string => {
const normalizeUrl = (url: string): string => {
try {
- let u = new URL(url);
+ const u = new URL(url);
if (SUPPORTED_PROTOCOLS.includes(u.protocol.toLowerCase())) {
return u.href;
}
diff --git a/src/shared/utils/dom.ts b/src/shared/utils/dom.ts
index c1f2190..a6186cb 100644
--- a/src/shared/utils/dom.ts
+++ b/src/shared/utils/dom.ts
@@ -1,5 +1,5 @@
const isContentEditable = (element: Element): boolean => {
- let value = element.getAttribute('contenteditable');
+ const value = element.getAttribute('contenteditable');
if (value === null) {
return false;
}
@@ -14,12 +14,12 @@ interface Rect {
}
const rectangleCoordsRect = (coords: string): Rect => {
- let [left, top, right, bottom] = coords.split(',').map(n => Number(n));
+ const [left, top, right, bottom] = coords.split(',').map(n => Number(n));
return { left, top, right, bottom };
};
const circleCoordsRect = (coords: string): Rect => {
- let [x, y, r] = coords.split(',').map(n => Number(n));
+ const [x, y, r] = coords.split(',').map(n => Number(n));
return {
left: x - r,
top: y - r,
@@ -29,14 +29,14 @@ const circleCoordsRect = (coords: string): Rect => {
};
const polygonCoordsRect = (coords: string): Rect => {
- let params = coords.split(',');
+ const params = coords.split(',');
let minx = Number(params[0]),
maxx = Number(params[0]),
miny = Number(params[1]),
maxy = Number(params[1]);
- let len = Math.floor(params.length / 2);
+ const len = Math.floor(params.length / 2);
for (let i = 2; i < len; i += 2) {
- let x = Number(params[i]),
+ const x = Number(params[i]),
y = Number(params[i + 1]);
if (x < minx) {
minx = x;
@@ -59,15 +59,15 @@ const viewportRect = (e: Element): Rect => {
return e.getBoundingClientRect();
}
- let mapElement = e.parentNode as HTMLMapElement;
- let imgElement = document.querySelector(
+ const mapElement = e.parentNode as HTMLMapElement;
+ const imgElement = document.querySelector(
`img[usemap="#${mapElement.name}"]`
) as HTMLImageElement;
- let {
+ const {
left: mapLeft,
top: mapTop
} = imgElement.getBoundingClientRect();
- let coords = e.getAttribute('coords');
+ const coords = e.getAttribute('coords');
if (!coords) {
return e.getBoundingClientRect();
}
@@ -96,8 +96,8 @@ const viewportRect = (e: Element): Rect => {
};
const isVisible = (element: Element): boolean => {
- let rect = element.getBoundingClientRect();
- let style = window.getComputedStyle(element);
+ const rect = element.getBoundingClientRect();
+ const style = window.getComputedStyle(element);
if (style.overflow !== 'visible' && (rect.width === 0 || rect.height === 0)) {
return false;
@@ -113,7 +113,7 @@ const isVisible = (element: Element): boolean => {
return false;
}
- let { display, visibility } = window.getComputedStyle(element);
+ const { display, visibility } = window.getComputedStyle(element);
if (display === 'none' || visibility === 'hidden') {
return false;
}