diff options
Diffstat (limited to 'src/shared')
-rw-r--r-- | src/shared/SettingData.ts | 34 | ||||
-rw-r--r-- | src/shared/messages.ts | 1 | ||||
-rw-r--r-- | src/shared/operations.ts | 28 | ||||
-rw-r--r-- | src/shared/settings/Blacklist.ts | 49 | ||||
-rw-r--r-- | src/shared/settings/Key.ts | 26 | ||||
-rw-r--r-- | src/shared/settings/KeySequence.ts | 54 | ||||
-rw-r--r-- | src/shared/settings/Keymaps.ts | 24 | ||||
-rw-r--r-- | src/shared/settings/Properties.ts | 18 | ||||
-rw-r--r-- | src/shared/settings/Search.ts | 52 | ||||
-rw-r--r-- | src/shared/settings/Settings.ts | 54 | ||||
-rw-r--r-- | src/shared/settings/schema.json | 84 | ||||
-rw-r--r-- | src/shared/settings/validate.js | 567 | ||||
-rw-r--r-- | src/shared/urls.ts | 33 | ||||
-rw-r--r-- | src/shared/utils/dom.ts | 26 |
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; } |