diff options
Diffstat (limited to 'src/shared')
-rw-r--r-- | src/shared/SettingData.ts | 414 | ||||
-rw-r--r-- | src/shared/Settings.ts | 200 | ||||
-rw-r--r-- | src/shared/blacklists.ts (renamed from src/shared/blacklists.js) | 4 | ||||
-rw-r--r-- | src/shared/messages.js | 71 | ||||
-rw-r--r-- | src/shared/messages.ts | 276 | ||||
-rw-r--r-- | src/shared/operations.js | 78 | ||||
-rw-r--r-- | src/shared/operations.ts | 447 | ||||
-rw-r--r-- | src/shared/properties.ts | 50 | ||||
-rw-r--r-- | src/shared/property-defs.ts | 50 | ||||
-rw-r--r-- | src/shared/settings/default.js | 85 | ||||
-rw-r--r-- | src/shared/settings/properties.js | 24 | ||||
-rw-r--r-- | src/shared/settings/storage.js | 32 | ||||
-rw-r--r-- | src/shared/settings/validator.js | 76 | ||||
-rw-r--r-- | src/shared/settings/values.js | 108 | ||||
-rw-r--r-- | src/shared/urls.ts (renamed from src/shared/urls.js) | 6 | ||||
-rw-r--r-- | src/shared/utils/dom.ts (renamed from src/shared/utils/dom.js) | 41 | ||||
-rw-r--r-- | src/shared/utils/keys.ts (renamed from src/shared/utils/keys.js) | 22 | ||||
-rw-r--r-- | src/shared/utils/re.ts (renamed from src/shared/utils/re.js) | 2 |
18 files changed, 1487 insertions, 499 deletions
diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts new file mode 100644 index 0000000..05e21fa --- /dev/null +++ b/src/shared/SettingData.ts @@ -0,0 +1,414 @@ +import * as operations from './operations'; +import Settings, * as settings from './Settings'; + +export class FormKeymaps { + private data: {[op: string]: string}; + + constructor(data: {[op: string]: string}) { + this.data = data; + } + + toKeymaps(): settings.Keymaps { + let keymaps: settings.Keymaps = {}; + for (let name of Object.keys(this.data)) { + let [type, argStr] = name.split('?'); + let args = {}; + if (argStr) { + args = JSON.parse(argStr); + } + let key = this.data[name]; + keymaps[key] = operations.valueOf({ type, ...args }); + } + return keymaps; + } + + toJSON(): {[op: string]: string} { + return this.data; + } + + buildWithOverride(op: string, keys: string): FormKeymaps { + let newData = { + ...this.data, + [op]: keys, + }; + return new FormKeymaps(newData); + } + + static valueOf(o: ReturnType<FormKeymaps['toJSON']>): FormKeymaps { + let data: {[op: string]: string} = {}; + for (let op of Object.keys(o)) { + data[op] = o[op] as string; + } + return new FormKeymaps(data); + } + + static fromKeymaps(keymaps: settings.Keymaps): FormKeymaps { + let data: {[op: string]: string} = {}; + for (let key of Object.keys(keymaps)) { + let op = keymaps[key]; + let args = { ...op }; + delete args.type; + + let name = op.type; + if (Object.keys(args).length > 0) { + name += '?' + JSON.stringify(args); + } + data[name] = key; + } + return new FormKeymaps(data); + } +} + +export class FormSearch { + private default: string; + + private engines: string[][]; + + constructor(defaultEngine: string, engines: string[][]) { + this.default = defaultEngine; + this.engines = engines; + } + + toSearchSettings(): settings.Search { + return { + default: this.default, + engines: this.engines.reduce( + (o: {[key: string]: string}, [name, url]) => { + o[name] = url; + return o; + }, {}), + }; + } + + toJSON(): { + default: string; + engines: string[][]; + } { + return { + default: this.default, + engines: this.engines, + }; + } + + static valueOf(o: ReturnType<FormSearch['toJSON']>): FormSearch { + if (!Object.prototype.hasOwnProperty.call(o, 'default')) { + throw new TypeError(`"default" field not set`); + } + if (!Object.prototype.hasOwnProperty.call(o, 'engines')) { + throw new TypeError(`"engines" field not set`); + } + return new FormSearch(o.default, o.engines); + } + + static fromSearch(search: settings.Search): FormSearch { + let engines = Object.entries(search.engines).reduce( + (o: string[][], [name, url]) => { + return o.concat([[name, url]]); + }, []); + return new FormSearch(search.default, engines); + } +} + +export class JSONSettings { + private json: string; + + constructor(json: any) { + this.json = json; + } + + toSettings(): Settings { + return settings.valueOf(JSON.parse(this.json)); + } + + toJSON(): string { + return this.json; + } + + static valueOf(o: ReturnType<JSONSettings['toJSON']>): JSONSettings { + return new JSONSettings(o); + } + + static fromSettings(data: Settings): JSONSettings { + return new JSONSettings(JSON.stringify(data, undefined, 2)); + } +} + +export class FormSettings { + private keymaps: FormKeymaps; + + private search: FormSearch; + + private properties: settings.Properties; + + private blacklist: string[]; + + constructor( + keymaps: FormKeymaps, + search: FormSearch, + properties: settings.Properties, + blacklist: string[], + ) { + this.keymaps = keymaps; + this.search = search; + this.properties = properties; + this.blacklist = blacklist; + } + + buildWithKeymaps(keymaps: FormKeymaps): FormSettings { + return new FormSettings( + keymaps, + this.search, + this.properties, + this.blacklist, + ); + } + + buildWithSearch(search: FormSearch): FormSettings { + return new FormSettings( + this.keymaps, + search, + this.properties, + this.blacklist, + ); + } + + buildWithProperties(props: settings.Properties): FormSettings { + return new FormSettings( + this.keymaps, + this.search, + props, + this.blacklist, + ); + } + + buildWithBlacklist(blacklist: string[]): FormSettings { + return new FormSettings( + this.keymaps, + this.search, + this.properties, + blacklist, + ); + } + + toSettings(): Settings { + return settings.valueOf({ + keymaps: this.keymaps.toKeymaps(), + search: this.search.toSearchSettings(), + properties: this.properties, + blacklist: this.blacklist, + }); + } + + toJSON(): { + keymaps: ReturnType<FormKeymaps['toJSON']>; + search: ReturnType<FormSearch['toJSON']>; + properties: settings.Properties; + blacklist: string[]; + } { + return { + keymaps: this.keymaps.toJSON(), + search: this.search.toJSON(), + properties: this.properties, + blacklist: this.blacklist, + }; + } + + static valueOf(o: ReturnType<FormSettings['toJSON']>): FormSettings { + for (let name of ['keymaps', 'search', 'properties', 'blacklist']) { + if (!Object.prototype.hasOwnProperty.call(o, name)) { + throw new Error(`"${name}" field not set`); + } + } + return new FormSettings( + FormKeymaps.valueOf(o.keymaps), + FormSearch.valueOf(o.search), + settings.propertiesValueOf(o.properties), + settings.blacklistValueOf(o.blacklist), + ); + } + + static fromSettings(data: Settings): FormSettings { + return new FormSettings( + FormKeymaps.fromKeymaps(data.keymaps), + FormSearch.fromSearch(data.search), + data.properties, + data.blacklist); + } +} + +export enum SettingSource { + JSON = 'json', + Form = 'form', +} + +export default class SettingData { + private source: SettingSource; + + private json?: JSONSettings; + + private form?: FormSettings; + + constructor({ + source, json, form + }: { + source: SettingSource, + json?: JSONSettings, + form?: FormSettings, + }) { + this.source = source; + this.json = json; + this.form = form; + } + + getSource(): SettingSource { + return this.source; + } + + getJSON(): JSONSettings { + if (!this.json) { + throw new TypeError('json settings not set'); + } + return this.json; + } + + getForm(): FormSettings { + if (!this.form) { + throw new TypeError('form settings not set'); + } + return this.form; + } + + toJSON(): any { + switch (this.source) { + case SettingSource.JSON: + return { + source: this.source, + json: (this.json as JSONSettings).toJSON(), + }; + case SettingSource.Form: + return { + source: this.source, + form: (this.form as FormSettings).toJSON(), + }; + } + throw new Error(`unknown settings source: ${this.source}`); + } + + toSettings(): Settings { + switch (this.source) { + case SettingSource.JSON: + return this.getJSON().toSettings(); + case SettingSource.Form: + return this.getForm().toSettings(); + } + throw new Error(`unknown settings source: ${this.source}`); + } + + static valueOf(o: { + source: string; + json?: string; + form?: ReturnType<FormSettings['toJSON']>; + }): SettingData { + switch (o.source) { + case SettingSource.JSON: + return new SettingData({ + source: o.source, + json: JSONSettings.valueOf( + o.json as ReturnType<JSONSettings['toJSON']>), + }); + case SettingSource.Form: + return new SettingData({ + source: o.source, + form: FormSettings.valueOf( + o.form as ReturnType<FormSettings['toJSON']>), + }); + } + throw new Error(`unknown settings source: ${o.source}`); + } +} + +export const DefaultSettingData: SettingData = SettingData.valueOf({ + source: 'json', + json: `{ + "keymaps": { + "0": { "type": "scroll.home" }, + ":": { "type": "command.show" }, + "o": { "type": "command.show.open", "alter": false }, + "O": { "type": "command.show.open", "alter": true }, + "t": { "type": "command.show.tabopen", "alter": false }, + "T": { "type": "command.show.tabopen", "alter": true }, + "w": { "type": "command.show.winopen", "alter": false }, + "W": { "type": "command.show.winopen", "alter": true }, + "b": { "type": "command.show.buffer" }, + "a": { "type": "command.show.addbookmark", "alter": true }, + "k": { "type": "scroll.vertically", "count": -1 }, + "j": { "type": "scroll.vertically", "count": 1 }, + "h": { "type": "scroll.horizonally", "count": -1 }, + "l": { "type": "scroll.horizonally", "count": 1 }, + "<C-U>": { "type": "scroll.pages", "count": -0.5 }, + "<C-D>": { "type": "scroll.pages", "count": 0.5 }, + "<C-B>": { "type": "scroll.pages", "count": -1 }, + "<C-F>": { "type": "scroll.pages", "count": 1 }, + "gg": { "type": "scroll.top" }, + "G": { "type": "scroll.bottom" }, + "$": { "type": "scroll.end" }, + "d": { "type": "tabs.close" }, + "D": { "type": "tabs.close.right" }, + "!d": { "type": "tabs.close.force" }, + "u": { "type": "tabs.reopen" }, + "K": { "type": "tabs.prev" }, + "J": { "type": "tabs.next" }, + "gT": { "type": "tabs.prev" }, + "gt": { "type": "tabs.next" }, + "g0": { "type": "tabs.first" }, + "g$": { "type": "tabs.last" }, + "<C-6>": { "type": "tabs.prevsel" }, + "r": { "type": "tabs.reload", "cache": false }, + "R": { "type": "tabs.reload", "cache": true }, + "zp": { "type": "tabs.pin.toggle" }, + "zd": { "type": "tabs.duplicate" }, + "zi": { "type": "zoom.in" }, + "zo": { "type": "zoom.out" }, + "zz": { "type": "zoom.neutral" }, + "f": { "type": "follow.start", "newTab": false }, + "F": { "type": "follow.start", "newTab": true, "background": false }, + "m": { "type": "mark.set.prefix" }, + "'": { "type": "mark.jump.prefix" }, + "H": { "type": "navigate.history.prev" }, + "L": { "type": "navigate.history.next" }, + "[[": { "type": "navigate.link.prev" }, + "]]": { "type": "navigate.link.next" }, + "gu": { "type": "navigate.parent" }, + "gU": { "type": "navigate.root" }, + "gi": { "type": "focus.input" }, + "gf": { "type": "page.source" }, + "gh": { "type": "page.home" }, + "gH": { "type": "page.home", "newTab": true }, + "y": { "type": "urls.yank" }, + "p": { "type": "urls.paste", "newTab": false }, + "P": { "type": "urls.paste", "newTab": true }, + "/": { "type": "find.start" }, + "n": { "type": "find.next" }, + "N": { "type": "find.prev" }, + "<S-Esc>": { "type": "addon.toggle.enabled" } + }, + "search": { + "default": "google", + "engines": { + "google": "https://google.com/search?q={}", + "yahoo": "https://search.yahoo.com/search?p={}", + "bing": "https://www.bing.com/search?q={}", + "duckduckgo": "https://duckduckgo.com/?q={}", + "twitter": "https://twitter.com/search?q={}", + "wikipedia": "https://en.wikipedia.org/w/index.php?search={}" + } + }, + "properties": { + "hintchars": "abcdefghijklmnopqrstuvwxyz", + "smoothscroll": false, + "complete": "sbh" + }, + "blacklist": [ + ] +}`, +}); diff --git a/src/shared/Settings.ts b/src/shared/Settings.ts new file mode 100644 index 0000000..e35094b --- /dev/null +++ b/src/shared/Settings.ts @@ -0,0 +1,200 @@ +import * as operations from './operations'; +import * as PropertyDefs from './property-defs'; + +export type Keymaps = {[key: string]: operations.Operation}; + +export interface Search { + default: string; + engines: { [key: string]: string }; +} + +export interface Properties { + hintchars: string; + smoothscroll: boolean; + complete: string; +} + +export default interface Settings { + keymaps: Keymaps; + search: Search; + properties: Properties; + blacklist: string[]; + // eslint-disable-next-line semi +} + +const DefaultProperties: Properties = PropertyDefs.defs.reduce( + (o: {[name: string]: PropertyDefs.Type}, def) => { + o[def.name] = def.defaultValue; + return o; + }, {}) as Properties; + + +export const keymapsValueOf = (o: any): Keymaps => { + return Object.keys(o).reduce((keymaps: Keymaps, key: string): Keymaps => { + let op = operations.valueOf(o[key]); + keymaps[key] = op; + return keymaps; + }, {}); +}; + +export const searchValueOf = (o: any): Search => { + if (typeof o.default !== 'string') { + throw new TypeError('string field "default" not set"'); + } + for (let name of Object.keys(o.engines)) { + if ((/\s/).test(name)) { + throw new TypeError( + `While space in the search engine not allowed: "${name}"`); + } + let url = o.engines[name]; + if (typeof url !== 'string') { + throw new TypeError('"engines" not an object of string'); + } + let matches = url.match(/{}/g); + if (matches === null) { + throw new TypeError(`No {}-placeholders in URL of "${name}"`); + } else if (matches.length > 1) { + throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`); + } + + } + if (!Object.prototype.hasOwnProperty.call(o.engines, o.default)) { + throw new TypeError(`Default engine "${o.default}" not found`); + } + return { + default: o.default as string, + engines: { ...o.engines }, + }; +}; + +export const propertiesValueOf = (o: any): Properties => { + let defNames = new Set(PropertyDefs.defs.map(def => def.name)); + let unknownName = Object.keys(o).find(name => !defNames.has(name)); + if (unknownName) { + throw new TypeError(`Unknown property name: "${unknownName}"`); + } + + for (let def of PropertyDefs.defs) { + if (!Object.prototype.hasOwnProperty.call(o, def.name)) { + continue; + } + if (typeof o[def.name] !== def.type) { + throw new TypeError(`property "${def.name}" is not ${def.type}`); + } + } + return { + ...DefaultProperties, + ...o, + }; +}; + +export const blacklistValueOf = (o: any): string[] => { + if (!Array.isArray(o)) { + throw new TypeError(`"blacklist" is not an array of string`); + } + for (let x of o) { + if (typeof x !== 'string') { + throw new TypeError(`"blacklist" is not an array of string`); + } + } + return o as string[]; +}; + +export const valueOf = (o: any): Settings => { + let settings = { ...DefaultSetting }; + if (Object.prototype.hasOwnProperty.call(o, 'keymaps')) { + settings.keymaps = keymapsValueOf(o.keymaps); + } + if (Object.prototype.hasOwnProperty.call(o, 'search')) { + settings.search = searchValueOf(o.search); + } + if (Object.prototype.hasOwnProperty.call(o, 'properties')) { + settings.properties = propertiesValueOf(o.properties); + } + if (Object.prototype.hasOwnProperty.call(o, 'blacklist')) { + settings.blacklist = blacklistValueOf(o.blacklist); + } + return settings; +}; + +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.right' }, + '!d': { 'type': 'tabs.close.force' }, + 'u': { 'type': 'tabs.reopen' }, + 'K': { 'type': 'tabs.prev' }, + 'J': { 'type': 'tabs.next' }, + 'gT': { 'type': 'tabs.prev' }, + 'gt': { 'type': 'tabs.next' }, + 'g0': { 'type': 'tabs.first' }, + 'g$': { 'type': 'tabs.last' }, + '<C-6>': { 'type': 'tabs.prevsel' }, + 'r': { 'type': 'tabs.reload', 'cache': false }, + 'R': { 'type': 'tabs.reload', 'cache': true }, + 'zp': { 'type': 'tabs.pin.toggle' }, + 'zd': { 'type': 'tabs.duplicate' }, + 'zi': { 'type': 'zoom.in' }, + 'zo': { 'type': 'zoom.out' }, + 'zz': { 'type': 'zoom.neutral' }, + 'f': { 'type': 'follow.start', 'newTab': false, 'background': false }, + 'F': { 'type': 'follow.start', 'newTab': true, 'background': false }, + 'm': { 'type': 'mark.set.prefix' }, + '\'': { 'type': 'mark.jump.prefix' }, + 'H': { 'type': 'navigate.history.prev' }, + 'L': { 'type': 'navigate.history.next' }, + '[[': { 'type': 'navigate.link.prev' }, + ']]': { 'type': 'navigate.link.next' }, + 'gu': { 'type': 'navigate.parent' }, + 'gU': { 'type': 'navigate.root' }, + 'gi': { 'type': 'focus.input' }, + 'gf': { 'type': 'page.source' }, + 'gh': { 'type': 'page.home', 'newTab': false }, + 'gH': { 'type': 'page.home', 'newTab': true }, + 'y': { 'type': 'urls.yank' }, + 'p': { 'type': 'urls.paste', 'newTab': false }, + 'P': { 'type': 'urls.paste', 'newTab': true }, + '/': { 'type': 'find.start' }, + 'n': { 'type': 'find.next' }, + 'N': { 'type': 'find.prev' }, + '<S-Esc>': { 'type': 'addon.toggle.enabled' } + }, + search: { + default: 'google', + engines: { + 'google': 'https://google.com/search?q={}', + 'yahoo': 'https://search.yahoo.com/search?p={}', + 'bing': 'https://www.bing.com/search?q={}', + 'duckduckgo': 'https://duckduckgo.com/?q={}', + 'twitter': 'https://twitter.com/search?q={}', + 'wikipedia': 'https://en.wikipedia.org/w/index.php?search={}' + } + }, + properties: { + hintchars: 'abcdefghijklmnopqrstuvwxyz', + smoothscroll: false, + complete: 'sbh' + }, + blacklist: [] +}; diff --git a/src/shared/blacklists.js b/src/shared/blacklists.ts index 61720c3..61ee4de 100644 --- a/src/shared/blacklists.js +++ b/src/shared/blacklists.ts @@ -1,6 +1,6 @@ -import * as re from 'shared/utils/re'; +import * as re from './utils/re'; -const includes = (blacklist, url) => { +const includes = (blacklist: string[], url: string): boolean => { let u = new URL(url); return blacklist.some((item) => { if (!item.includes('/')) { diff --git a/src/shared/messages.js b/src/shared/messages.js deleted file mode 100644 index ddf3368..0000000 --- a/src/shared/messages.js +++ /dev/null @@ -1,71 +0,0 @@ -const onWebMessage = (listener) => { - window.addEventListener('message', (event) => { - let sender = event.source; - let message = null; - try { - message = JSON.parse(event.data); - } catch (e) { - // ignore unexpected message - return; - } - listener(message, sender); - }); -}; - -const onBackgroundMessage = (listener) => { - browser.runtime.onMessage.addListener(listener); -}; - -const onMessage = (listener) => { - onWebMessage(listener); - onBackgroundMessage(listener); -}; - -export default { - BACKGROUND_OPERATION: 'background.operation', - - CONSOLE_UNFOCUS: 'console.unfocus', - CONSOLE_ENTER_COMMAND: 'console.enter.command', - CONSOLE_ENTER_FIND: 'console.enter.find', - CONSOLE_QUERY_COMPLETIONS: 'console.query.completions', - CONSOLE_SHOW_COMMAND: 'console.show.command', - CONSOLE_SHOW_ERROR: 'console.show.error', - CONSOLE_SHOW_INFO: 'console.show.info', - CONSOLE_SHOW_FIND: 'console.show.find', - CONSOLE_HIDE: 'console.hide', - - FOLLOW_START: 'follow.start', - FOLLOW_REQUEST_COUNT_TARGETS: 'follow.request.count.targets', - FOLLOW_RESPONSE_COUNT_TARGETS: 'follow.response.count.targets', - FOLLOW_CREATE_HINTS: 'follow.create.hints', - FOLLOW_SHOW_HINTS: 'follow.update.hints', - FOLLOW_REMOVE_HINTS: 'follow.remove.hints', - FOLLOW_ACTIVATE: 'follow.activate', - FOLLOW_KEY_PRESS: 'follow.key.press', - - MARK_SET_GLOBAL: 'mark.set.global', - MARK_JUMP_GLOBAL: 'mark.jump.global', - - TAB_SCROLL_TO: 'tab.scroll.to', - - FIND_NEXT: 'find.next', - FIND_PREV: 'find.prev', - FIND_GET_KEYWORD: 'find.get.keyword', - FIND_SET_KEYWORD: 'find.set.keyword', - - ADDON_ENABLED_QUERY: 'addon.enabled.query', - ADDON_ENABLED_RESPONSE: 'addon.enabled.response', - ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled', - - OPEN_URL: 'open.url', - - SETTINGS_CHANGED: 'settings.changed', - SETTINGS_QUERY: 'settings.query', - - WINDOW_TOP_MESSAGE: 'window.top.message', - CONSOLE_FRAME_MESSAGE: 'console.frame.message', - - onWebMessage, - onBackgroundMessage, - onMessage, -}; diff --git a/src/shared/messages.ts b/src/shared/messages.ts new file mode 100644 index 0000000..41b0f0b --- /dev/null +++ b/src/shared/messages.ts @@ -0,0 +1,276 @@ +import * as operations from './operations'; + +export const BACKGROUND_OPERATION = 'background.operation'; + +export const CONSOLE_UNFOCUS = 'console.unfocus'; +export const CONSOLE_ENTER_COMMAND = 'console.enter.command'; +export const CONSOLE_ENTER_FIND = 'console.enter.find'; +export const CONSOLE_QUERY_COMPLETIONS = 'console.query.completions'; +export const CONSOLE_SHOW_COMMAND = 'console.show.command'; +export const CONSOLE_SHOW_ERROR = 'console.show.error'; +export const CONSOLE_SHOW_INFO = 'console.show.info'; +export const CONSOLE_SHOW_FIND = 'console.show.find'; +export const CONSOLE_HIDE = 'console.hide'; + +export const FOLLOW_START = 'follow.start'; +export const FOLLOW_REQUEST_COUNT_TARGETS = 'follow.request.count.targets'; +export const FOLLOW_RESPONSE_COUNT_TARGETS = 'follow.response.count.targets'; +export const FOLLOW_CREATE_HINTS = 'follow.create.hints'; +export const FOLLOW_SHOW_HINTS = 'follow.update.hints'; +export const FOLLOW_REMOVE_HINTS = 'follow.remove.hints'; +export const FOLLOW_ACTIVATE = 'follow.activate'; +export const FOLLOW_KEY_PRESS = 'follow.key.press'; + +export const MARK_SET_GLOBAL = 'mark.set.global'; +export const MARK_JUMP_GLOBAL = 'mark.jump.global'; + +export const TAB_SCROLL_TO = 'tab.scroll.to'; + +export const FIND_NEXT = 'find.next'; +export const FIND_PREV = 'find.prev'; +export const FIND_GET_KEYWORD = 'find.get.keyword'; +export const FIND_SET_KEYWORD = 'find.set.keyword'; + +export const ADDON_ENABLED_QUERY = 'addon.enabled.query'; +export const ADDON_ENABLED_RESPONSE = 'addon.enabled.response'; +export const ADDON_TOGGLE_ENABLED = 'addon.toggle.enabled'; + +export const OPEN_URL = 'open.url'; + +export const SETTINGS_CHANGED = 'settings.changed'; +export const SETTINGS_QUERY = 'settings.query'; + +export const CONSOLE_FRAME_MESSAGE = 'console.frame.message'; + +interface BackgroundOperationMessage { + type: typeof BACKGROUND_OPERATION; + operation: operations.Operation; +} + +interface ConsoleUnfocusMessage { + type: typeof CONSOLE_UNFOCUS; +} + +interface ConsoleEnterCommandMessage { + type: typeof CONSOLE_ENTER_COMMAND; + text: string; +} + +interface ConsoleEnterFindMessage { + type: typeof CONSOLE_ENTER_FIND; + text: string; +} + +interface ConsoleQueryCompletionsMessage { + type: typeof CONSOLE_QUERY_COMPLETIONS; + text: string; +} + +interface ConsoleShowCommandMessage { + type: typeof CONSOLE_SHOW_COMMAND; + command: string; +} + +interface ConsoleShowErrorMessage { + type: typeof CONSOLE_SHOW_ERROR; + text: string; +} + +interface ConsoleShowInfoMessage { + type: typeof CONSOLE_SHOW_INFO; + text: string; +} + +interface ConsoleShowFindMessage { + type: typeof CONSOLE_SHOW_FIND; +} + +interface ConsoleHideMessage { + type: typeof CONSOLE_HIDE; +} + +interface FollowStartMessage { + type: typeof FOLLOW_START; + newTab: boolean; + background: boolean; +} + +interface FollowRequestCountTargetsMessage { + type: typeof FOLLOW_REQUEST_COUNT_TARGETS; + viewSize: { width: number, height: number }; + framePosition: { x: number, y: number }; +} + +interface FollowResponseCountTargetsMessage { + type: typeof FOLLOW_RESPONSE_COUNT_TARGETS; + count: number; +} + +interface FollowCreateHintsMessage { + type: typeof FOLLOW_CREATE_HINTS; + keysArray: string[]; + newTab: boolean; + background: boolean; +} + +interface FollowShowHintsMessage { + type: typeof FOLLOW_SHOW_HINTS; + keys: string; +} + +interface FollowRemoveHintsMessage { + type: typeof FOLLOW_REMOVE_HINTS; +} + +interface FollowActivateMessage { + type: typeof FOLLOW_ACTIVATE; + keys: string; +} + +interface FollowKeyPressMessage { + type: typeof FOLLOW_KEY_PRESS; + key: string; + ctrlKey: boolean; +} + +interface MarkSetGlobalMessage { + type: typeof MARK_SET_GLOBAL; + key: string; + x: number; + y: number; +} + +interface MarkJumpGlobalMessage { + type: typeof MARK_JUMP_GLOBAL; + key: string; +} + +interface TabScrollToMessage { + type: typeof TAB_SCROLL_TO; + x: number; + y: number; +} + +interface FindNextMessage { + type: typeof FIND_NEXT; +} + +interface FindPrevMessage { + type: typeof FIND_PREV; +} + +interface FindGetKeywordMessage { + type: typeof FIND_GET_KEYWORD; +} + +interface FindSetKeywordMessage { + type: typeof FIND_SET_KEYWORD; + keyword: string; + found: boolean; +} + +interface AddonEnabledQueryMessage { + type: typeof ADDON_ENABLED_QUERY; +} + +interface AddonEnabledResponseMessage { + type: typeof ADDON_ENABLED_RESPONSE; + enabled: boolean; +} + +interface AddonToggleEnabledMessage { + type: typeof ADDON_TOGGLE_ENABLED; +} + +interface OpenUrlMessage { + type: typeof OPEN_URL; + url: string; + newTab: boolean; + background: boolean; +} + +interface SettingsChangedMessage { + type: typeof SETTINGS_CHANGED; +} + +interface SettingsQueryMessage { + type: typeof SETTINGS_QUERY; +} + +interface ConsoleFrameMessageMessage { + type: typeof CONSOLE_FRAME_MESSAGE; + message: any; +} + +export type Message = + BackgroundOperationMessage | + ConsoleUnfocusMessage | + ConsoleEnterCommandMessage | + ConsoleEnterFindMessage | + ConsoleQueryCompletionsMessage | + ConsoleShowCommandMessage | + ConsoleShowErrorMessage | + ConsoleShowInfoMessage | + ConsoleShowFindMessage | + ConsoleHideMessage | + FollowStartMessage | + FollowRequestCountTargetsMessage | + FollowResponseCountTargetsMessage | + FollowCreateHintsMessage | + FollowShowHintsMessage | + FollowRemoveHintsMessage | + FollowActivateMessage | + FollowKeyPressMessage | + MarkSetGlobalMessage | + MarkJumpGlobalMessage | + TabScrollToMessage | + FindNextMessage | + FindPrevMessage | + FindGetKeywordMessage | + FindSetKeywordMessage | + AddonEnabledQueryMessage | + AddonEnabledResponseMessage | + AddonToggleEnabledMessage | + OpenUrlMessage | + SettingsChangedMessage | + SettingsQueryMessage | + ConsoleFrameMessageMessage; + +// eslint-disable-next-line complexity +export const valueOf = (o: any): Message => { + switch (o.type) { + case CONSOLE_UNFOCUS: + case CONSOLE_ENTER_COMMAND: + case CONSOLE_ENTER_FIND: + case CONSOLE_QUERY_COMPLETIONS: + case CONSOLE_SHOW_COMMAND: + case CONSOLE_SHOW_ERROR: + case CONSOLE_SHOW_INFO: + case CONSOLE_SHOW_FIND: + case CONSOLE_HIDE: + case FOLLOW_START: + case FOLLOW_REQUEST_COUNT_TARGETS: + case FOLLOW_RESPONSE_COUNT_TARGETS: + case FOLLOW_CREATE_HINTS: + case FOLLOW_SHOW_HINTS: + case FOLLOW_REMOVE_HINTS: + case FOLLOW_ACTIVATE: + case FOLLOW_KEY_PRESS: + case MARK_SET_GLOBAL: + case MARK_JUMP_GLOBAL: + case TAB_SCROLL_TO: + case FIND_NEXT: + case FIND_PREV: + case FIND_GET_KEYWORD: + case FIND_SET_KEYWORD: + case ADDON_ENABLED_QUERY: + case ADDON_ENABLED_RESPONSE: + case ADDON_TOGGLE_ENABLED: + case OPEN_URL: + case SETTINGS_CHANGED: + case SETTINGS_QUERY: + case CONSOLE_FRAME_MESSAGE: + return o; + } + throw new Error('unknown operation type: ' + o.type); +}; diff --git a/src/shared/operations.js b/src/shared/operations.js deleted file mode 100644 index 8674f4d..0000000 --- a/src/shared/operations.js +++ /dev/null @@ -1,78 +0,0 @@ -export default { - // Hide console, or cancel some user actions - CANCEL: 'cancel', - - // Addons - ADDON_ENABLE: 'addon.enable', - ADDON_DISABLE: 'addon.disable', - ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled', - - // Command - COMMAND_SHOW: 'command.show', - COMMAND_SHOW_OPEN: 'command.show.open', - COMMAND_SHOW_TABOPEN: 'command.show.tabopen', - COMMAND_SHOW_WINOPEN: 'command.show.winopen', - COMMAND_SHOW_BUFFER: 'command.show.buffer', - COMMAND_SHOW_ADDBOOKMARK: 'command.show.addbookmark', - - // Scrolls - SCROLL_VERTICALLY: 'scroll.vertically', - SCROLL_HORIZONALLY: 'scroll.horizonally', - SCROLL_PAGES: 'scroll.pages', - SCROLL_TOP: 'scroll.top', - SCROLL_BOTTOM: 'scroll.bottom', - SCROLL_HOME: 'scroll.home', - SCROLL_END: 'scroll.end', - - // Follows - FOLLOW_START: 'follow.start', - - // Navigations - NAVIGATE_HISTORY_PREV: 'navigate.history.prev', - NAVIGATE_HISTORY_NEXT: 'navigate.history.next', - NAVIGATE_LINK_PREV: 'navigate.link.prev', - NAVIGATE_LINK_NEXT: 'navigate.link.next', - NAVIGATE_PARENT: 'navigate.parent', - NAVIGATE_ROOT: 'navigate.root', - - // Focus - FOCUS_INPUT: 'focus.input', - - // Page - PAGE_SOURCE: 'page.source', - PAGE_HOME: 'page.home', - - // Tabs - TAB_CLOSE: 'tabs.close', - TAB_CLOSE_FORCE: 'tabs.close.force', - TAB_CLOSE_RIGHT: 'tabs.close.right', - TAB_REOPEN: 'tabs.reopen', - TAB_PREV: 'tabs.prev', - TAB_NEXT: 'tabs.next', - TAB_FIRST: 'tabs.first', - TAB_LAST: 'tabs.last', - TAB_PREV_SEL: 'tabs.prevsel', - TAB_RELOAD: 'tabs.reload', - TAB_PIN: 'tabs.pin', - TAB_UNPIN: 'tabs.unpin', - TAB_TOGGLE_PINNED: 'tabs.pin.toggle', - TAB_DUPLICATE: 'tabs.duplicate', - - // Zooms - ZOOM_IN: 'zoom.in', - ZOOM_OUT: 'zoom.out', - ZOOM_NEUTRAL: 'zoom.neutral', - - // Url yank/paste - URLS_YANK: 'urls.yank', - URLS_PASTE: 'urls.paste', - - // Find - FIND_START: 'find.start', - FIND_NEXT: 'find.next', - FIND_PREV: 'find.prev', - - // Mark - MARK_SET_PREFIX: 'mark.set.prefix', - MARK_JUMP_PREFIX: 'mark.jump.prefix', -}; diff --git a/src/shared/operations.ts b/src/shared/operations.ts new file mode 100644 index 0000000..688c240 --- /dev/null +++ b/src/shared/operations.ts @@ -0,0 +1,447 @@ +// Hide console; or cancel some user actions +export const CANCEL = 'cancel'; + +// Addons +export const ADDON_ENABLE = 'addon.enable'; +export const ADDON_DISABLE = 'addon.disable'; +export const ADDON_TOGGLE_ENABLED = 'addon.toggle.enabled'; + +// Command +export const COMMAND_SHOW = 'command.show'; +export const COMMAND_SHOW_OPEN = 'command.show.open'; +export const COMMAND_SHOW_TABOPEN = 'command.show.tabopen'; +export const COMMAND_SHOW_WINOPEN = 'command.show.winopen'; +export const COMMAND_SHOW_BUFFER = 'command.show.buffer'; +export const COMMAND_SHOW_ADDBOOKMARK = 'command.show.addbookmark'; + +// Scrolls +export const SCROLL_VERTICALLY = 'scroll.vertically'; +export const SCROLL_HORIZONALLY = 'scroll.horizonally'; +export const SCROLL_PAGES = 'scroll.pages'; +export const SCROLL_TOP = 'scroll.top'; +export const SCROLL_BOTTOM = 'scroll.bottom'; +export const SCROLL_HOME = 'scroll.home'; +export const SCROLL_END = 'scroll.end'; + +// Follows +export const FOLLOW_START = 'follow.start'; + +// Navigations +export const NAVIGATE_HISTORY_PREV = 'navigate.history.prev'; +export const NAVIGATE_HISTORY_NEXT = 'navigate.history.next'; +export const NAVIGATE_LINK_PREV = 'navigate.link.prev'; +export const NAVIGATE_LINK_NEXT = 'navigate.link.next'; +export const NAVIGATE_PARENT = 'navigate.parent'; +export const NAVIGATE_ROOT = 'navigate.root'; + +// Focus +export const FOCUS_INPUT = 'focus.input'; + +// Page +export const PAGE_SOURCE = 'page.source'; +export const PAGE_HOME = 'page.home'; + +// Tabs +export const TAB_CLOSE = 'tabs.close'; +export const TAB_CLOSE_FORCE = 'tabs.close.force'; +export const TAB_CLOSE_RIGHT = 'tabs.close.right'; +export const TAB_REOPEN = 'tabs.reopen'; +export const TAB_PREV = 'tabs.prev'; +export const TAB_NEXT = 'tabs.next'; +export const TAB_FIRST = 'tabs.first'; +export const TAB_LAST = 'tabs.last'; +export const TAB_PREV_SEL = 'tabs.prevsel'; +export const TAB_RELOAD = 'tabs.reload'; +export const TAB_PIN = 'tabs.pin'; +export const TAB_UNPIN = 'tabs.unpin'; +export const TAB_TOGGLE_PINNED = 'tabs.pin.toggle'; +export const TAB_DUPLICATE = 'tabs.duplicate'; + +// Zooms +export const ZOOM_IN = 'zoom.in'; +export const ZOOM_OUT = 'zoom.out'; +export const ZOOM_NEUTRAL = 'zoom.neutral'; + +// Url yank/paste +export const URLS_YANK = 'urls.yank'; +export const URLS_PASTE = 'urls.paste'; + +// Find +export const FIND_START = 'find.start'; +export const FIND_NEXT = 'find.next'; +export const FIND_PREV = 'find.prev'; + +// Mark +export const MARK_SET_PREFIX = 'mark.set.prefix'; +export const MARK_JUMP_PREFIX = 'mark.jump.prefix'; + +export interface CancelOperation { + type: typeof CANCEL; +} + +export interface AddonEnableOperation { + type: typeof ADDON_ENABLE; +} + +export interface AddonDisableOperation { + type: typeof ADDON_DISABLE; +} + +export interface AddonToggleEnabledOperation { + type: typeof ADDON_TOGGLE_ENABLED; +} + +export interface CommandShowOperation { + type: typeof COMMAND_SHOW; +} + +export interface CommandShowOpenOperation { + type: typeof COMMAND_SHOW_OPEN; + alter: boolean; +} + +export interface CommandShowTabopenOperation { + type: typeof COMMAND_SHOW_TABOPEN; + alter: boolean; +} + +export interface CommandShowWinopenOperation { + type: typeof COMMAND_SHOW_WINOPEN; + alter: boolean; +} + +export interface CommandShowBufferOperation { + type: typeof COMMAND_SHOW_BUFFER; +} + +export interface CommandShowAddbookmarkOperation { + type: typeof COMMAND_SHOW_ADDBOOKMARK; + alter: boolean; +} + +export interface ScrollVerticallyOperation { + type: typeof SCROLL_VERTICALLY; + count: number; +} + +export interface ScrollHorizonallyOperation { + type: typeof SCROLL_HORIZONALLY; + count: number; +} + +export interface ScrollPagesOperation { + type: typeof SCROLL_PAGES; + count: number; +} + +export interface ScrollTopOperation { + type: typeof SCROLL_TOP; +} + +export interface ScrollBottomOperation { + type: typeof SCROLL_BOTTOM; +} + +export interface ScrollHomeOperation { + type: typeof SCROLL_HOME; +} + +export interface ScrollEndOperation { + type: typeof SCROLL_END; +} + +export interface FollowStartOperation { + type: typeof FOLLOW_START; + newTab: boolean; + background: boolean; +} + +export interface NavigateHistoryPrevOperation { + type: typeof NAVIGATE_HISTORY_PREV; +} + +export interface NavigateHistoryNextOperation { + type: typeof NAVIGATE_HISTORY_NEXT; +} + +export interface NavigateLinkPrevOperation { + type: typeof NAVIGATE_LINK_PREV; +} + +export interface NavigateLinkNextOperation { + type: typeof NAVIGATE_LINK_NEXT; +} + +export interface NavigateParentOperation { + type: typeof NAVIGATE_PARENT; +} + +export interface NavigateRootOperation { + type: typeof NAVIGATE_ROOT; +} + +export interface FocusInputOperation { + type: typeof FOCUS_INPUT; +} + +export interface PageSourceOperation { + type: typeof PAGE_SOURCE; +} + +export interface PageHomeOperation { + type: typeof PAGE_HOME; + newTab: boolean; +} + +export interface TabCloseOperation { + type: typeof TAB_CLOSE; +} + +export interface TabCloseForceOperation { + type: typeof TAB_CLOSE_FORCE; +} + +export interface TabCloseRightOperation { + type: typeof TAB_CLOSE_RIGHT; +} + +export interface TabReopenOperation { + type: typeof TAB_REOPEN; +} + +export interface TabPrevOperation { + type: typeof TAB_PREV; +} + +export interface TabNextOperation { + type: typeof TAB_NEXT; +} + +export interface TabFirstOperation { + type: typeof TAB_FIRST; +} + +export interface TabLastOperation { + type: typeof TAB_LAST; +} + +export interface TabPrevSelOperation { + type: typeof TAB_PREV_SEL; +} + +export interface TabReloadOperation { + type: typeof TAB_RELOAD; + cache: boolean; +} + +export interface TabPinOperation { + type: typeof TAB_PIN; +} + +export interface TabUnpinOperation { + type: typeof TAB_UNPIN; +} + +export interface TabTogglePinnedOperation { + type: typeof TAB_TOGGLE_PINNED; +} + +export interface TabDuplicateOperation { + type: typeof TAB_DUPLICATE; +} + +export interface ZoomInOperation { + type: typeof ZOOM_IN; +} + +export interface ZoomOutOperation { + type: typeof ZOOM_OUT; +} + +export interface ZoomNeutralOperation { + type: typeof ZOOM_NEUTRAL; +} + +export interface UrlsYankOperation { + type: typeof URLS_YANK; +} + +export interface UrlsPasteOperation { + type: typeof URLS_PASTE; + newTab: boolean; +} + +export interface FindStartOperation { + type: typeof FIND_START; +} + +export interface FindNextOperation { + type: typeof FIND_NEXT; +} + +export interface FindPrevOperation { + type: typeof FIND_PREV; +} + +export interface MarkSetPrefixOperation { + type: typeof MARK_SET_PREFIX; +} + +export interface MarkJumpPrefixOperation { + type: typeof MARK_JUMP_PREFIX; +} + +export type Operation = + CancelOperation | + AddonEnableOperation | + AddonDisableOperation | + AddonToggleEnabledOperation | + CommandShowOperation | + CommandShowOpenOperation | + CommandShowTabopenOperation | + CommandShowWinopenOperation | + CommandShowBufferOperation | + CommandShowAddbookmarkOperation | + ScrollVerticallyOperation | + ScrollHorizonallyOperation | + ScrollPagesOperation | + ScrollTopOperation | + ScrollBottomOperation | + ScrollHomeOperation | + ScrollEndOperation | + FollowStartOperation | + NavigateHistoryPrevOperation | + NavigateHistoryNextOperation | + NavigateLinkPrevOperation | + NavigateLinkNextOperation | + NavigateParentOperation | + NavigateRootOperation | + FocusInputOperation | + PageSourceOperation | + PageHomeOperation | + TabCloseOperation | + TabCloseForceOperation | + TabCloseRightOperation | + TabReopenOperation | + TabPrevOperation | + TabNextOperation | + TabFirstOperation | + TabLastOperation | + TabPrevSelOperation | + TabReloadOperation | + TabPinOperation | + TabUnpinOperation | + TabTogglePinnedOperation | + TabDuplicateOperation | + ZoomInOperation | + ZoomOutOperation | + ZoomNeutralOperation | + UrlsYankOperation | + UrlsPasteOperation | + FindStartOperation | + FindNextOperation | + FindPrevOperation | + MarkSetPrefixOperation | + MarkJumpPrefixOperation; + +const assertOptionalBoolean = (obj: any, name: string) => { + if (Object.prototype.hasOwnProperty.call(obj, name) && + typeof obj[name] !== 'boolean') { + throw new TypeError(`Not a boolean parameter '${name}'`); + } +}; + +const assertRequiredNumber = (obj: any, name: string) => { + if (!Object.prototype.hasOwnProperty.call(obj, name) || + typeof obj[name] !== 'number') { + throw new TypeError(`Missing number parameter '${name}`); + } +}; + +// eslint-disable-next-line complexity, max-lines-per-function +export const valueOf = (o: any): Operation => { + if (!Object.prototype.hasOwnProperty.call(o, 'type')) { + throw new TypeError(`missing 'type' field`); + } + switch (o.type) { + case COMMAND_SHOW_OPEN: + case COMMAND_SHOW_TABOPEN: + case COMMAND_SHOW_WINOPEN: + case COMMAND_SHOW_ADDBOOKMARK: + assertOptionalBoolean(o, 'alter'); + return { type: o.type, alter: Boolean(o.alter) }; + case SCROLL_VERTICALLY: + case SCROLL_HORIZONALLY: + case SCROLL_PAGES: + assertRequiredNumber(o, 'count'); + return { type: o.type, count: Number(o.count) }; + case FOLLOW_START: + assertOptionalBoolean(o, 'newTab'); + assertOptionalBoolean(o, 'background'); + return { + type: FOLLOW_START, + newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab), + background: Boolean(typeof o.background === undefined ? true : o.background), // eslint-disable-line max-len + }; + case PAGE_HOME: + assertOptionalBoolean(o, 'newTab'); + return { + type: PAGE_HOME, + newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab), + }; + case TAB_RELOAD: + assertOptionalBoolean(o, 'cache'); + return { + type: TAB_RELOAD, + cache: Boolean(typeof o.cache === undefined ? false : o.cache), + }; + case URLS_PASTE: + assertOptionalBoolean(o, 'newTab'); + return { + type: URLS_PASTE, + newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab), + }; + case CANCEL: + case ADDON_ENABLE: + case ADDON_DISABLE: + case ADDON_TOGGLE_ENABLED: + case COMMAND_SHOW: + case COMMAND_SHOW_BUFFER: + case SCROLL_TOP: + case SCROLL_BOTTOM: + case SCROLL_HOME: + case SCROLL_END: + case NAVIGATE_HISTORY_PREV: + case NAVIGATE_HISTORY_NEXT: + case NAVIGATE_LINK_PREV: + case NAVIGATE_LINK_NEXT: + case NAVIGATE_PARENT: + case NAVIGATE_ROOT: + case FOCUS_INPUT: + case PAGE_SOURCE: + case TAB_CLOSE: + case TAB_CLOSE_FORCE: + case TAB_CLOSE_RIGHT: + case TAB_REOPEN: + case TAB_PREV: + case TAB_NEXT: + case TAB_FIRST: + case TAB_LAST: + case TAB_PREV_SEL: + case TAB_PIN: + case TAB_UNPIN: + case TAB_TOGGLE_PINNED: + case TAB_DUPLICATE: + case ZOOM_IN: + case ZOOM_OUT: + case ZOOM_NEUTRAL: + case URLS_YANK: + case FIND_START: + case FIND_NEXT: + case FIND_PREV: + case MARK_SET_PREFIX: + case MARK_JUMP_PREFIX: + return { type: o.type }; + } + throw new TypeError('unknown operation type: ' + o.type); +}; diff --git a/src/shared/properties.ts b/src/shared/properties.ts new file mode 100644 index 0000000..6315030 --- /dev/null +++ b/src/shared/properties.ts @@ -0,0 +1,50 @@ +export type Type = string | number | boolean; + +export class Def { + private name0: string; + + private description0: string; + + private defaultValue0: Type; + + constructor( + name: string, + description: string, + defaultValue: Type, + ) { + this.name0 = name; + this.description0 = description; + this.defaultValue0 = defaultValue; + } + + public get name(): string { + return this.name0; + } + + public get defaultValue(): Type { + return this.defaultValue0; + } + + public get description(): Type { + return this.description0; + } + + public get type(): string { + return typeof this.defaultValue; + } +} + +export const defs: Def[] = [ + new Def( + 'hintchars', + 'hint characters on follow mode', + 'abcdefghijklmnopqrstuvwxyz'), + new Def( + 'smoothscroll', + 'smooth scroll', + false), + new Def( + 'complete', + 'which are completed at the open page', + 'sbh'), +]; diff --git a/src/shared/property-defs.ts b/src/shared/property-defs.ts new file mode 100644 index 0000000..6315030 --- /dev/null +++ b/src/shared/property-defs.ts @@ -0,0 +1,50 @@ +export type Type = string | number | boolean; + +export class Def { + private name0: string; + + private description0: string; + + private defaultValue0: Type; + + constructor( + name: string, + description: string, + defaultValue: Type, + ) { + this.name0 = name; + this.description0 = description; + this.defaultValue0 = defaultValue; + } + + public get name(): string { + return this.name0; + } + + public get defaultValue(): Type { + return this.defaultValue0; + } + + public get description(): Type { + return this.description0; + } + + public get type(): string { + return typeof this.defaultValue; + } +} + +export const defs: Def[] = [ + new Def( + 'hintchars', + 'hint characters on follow mode', + 'abcdefghijklmnopqrstuvwxyz'), + new Def( + 'smoothscroll', + 'smooth scroll', + false), + new Def( + 'complete', + 'which are completed at the open page', + 'sbh'), +]; diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js deleted file mode 100644 index 6523a74..0000000 --- a/src/shared/settings/default.js +++ /dev/null @@ -1,85 +0,0 @@ -export default { - source: 'json', - json: `{ - "keymaps": { - "0": { "type": "scroll.home" }, - ":": { "type": "command.show" }, - "o": { "type": "command.show.open", "alter": false }, - "O": { "type": "command.show.open", "alter": true }, - "t": { "type": "command.show.tabopen", "alter": false }, - "T": { "type": "command.show.tabopen", "alter": true }, - "w": { "type": "command.show.winopen", "alter": false }, - "W": { "type": "command.show.winopen", "alter": true }, - "b": { "type": "command.show.buffer" }, - "a": { "type": "command.show.addbookmark", "alter": true }, - "k": { "type": "scroll.vertically", "count": -1 }, - "j": { "type": "scroll.vertically", "count": 1 }, - "h": { "type": "scroll.horizonally", "count": -1 }, - "l": { "type": "scroll.horizonally", "count": 1 }, - "<C-U>": { "type": "scroll.pages", "count": -0.5 }, - "<C-D>": { "type": "scroll.pages", "count": 0.5 }, - "<C-B>": { "type": "scroll.pages", "count": -1 }, - "<C-F>": { "type": "scroll.pages", "count": 1 }, - "gg": { "type": "scroll.top" }, - "G": { "type": "scroll.bottom" }, - "$": { "type": "scroll.end" }, - "d": { "type": "tabs.close" }, - "D": { "type": "tabs.close.right" }, - "!d": { "type": "tabs.close.force" }, - "u": { "type": "tabs.reopen" }, - "K": { "type": "tabs.prev", "count": 1 }, - "J": { "type": "tabs.next", "count": 1 }, - "gT": { "type": "tabs.prev", "count": 1 }, - "gt": { "type": "tabs.next", "count": 1 }, - "g0": { "type": "tabs.first" }, - "g$": { "type": "tabs.last" }, - "<C-6>": { "type": "tabs.prevsel" }, - "r": { "type": "tabs.reload", "cache": false }, - "R": { "type": "tabs.reload", "cache": true }, - "zp": { "type": "tabs.pin.toggle" }, - "zd": { "type": "tabs.duplicate" }, - "zi": { "type": "zoom.in" }, - "zo": { "type": "zoom.out" }, - "zz": { "type": "zoom.neutral" }, - "f": { "type": "follow.start", "newTab": false }, - "F": { "type": "follow.start", "newTab": true, "background": false }, - "m": { "type": "mark.set.prefix" }, - "'": { "type": "mark.jump.prefix" }, - "H": { "type": "navigate.history.prev" }, - "L": { "type": "navigate.history.next" }, - "[[": { "type": "navigate.link.prev" }, - "]]": { "type": "navigate.link.next" }, - "gu": { "type": "navigate.parent" }, - "gU": { "type": "navigate.root" }, - "gi": { "type": "focus.input" }, - "gf": { "type": "page.source" }, - "gh": { "type": "page.home" }, - "gH": { "type": "page.home", "newTab": true }, - "y": { "type": "urls.yank" }, - "p": { "type": "urls.paste", "newTab": false }, - "P": { "type": "urls.paste", "newTab": true }, - "/": { "type": "find.start" }, - "n": { "type": "find.next" }, - "N": { "type": "find.prev" }, - "<S-Esc>": { "type": "addon.toggle.enabled" } - }, - "search": { - "default": "google", - "engines": { - "google": "https://google.com/search?q={}", - "yahoo": "https://search.yahoo.com/search?p={}", - "bing": "https://www.bing.com/search?q={}", - "duckduckgo": "https://duckduckgo.com/?q={}", - "twitter": "https://twitter.com/search?q={}", - "wikipedia": "https://en.wikipedia.org/w/index.php?search={}" - } - }, - "properties": { - "hintchars": "abcdefghijklmnopqrstuvwxyz", - "smoothscroll": false, - "complete": "sbh" - }, - "blacklist": [ - ] -}`, -}; diff --git a/src/shared/settings/properties.js b/src/shared/settings/properties.js deleted file mode 100644 index f8e61a0..0000000 --- a/src/shared/settings/properties.js +++ /dev/null @@ -1,24 +0,0 @@ -// describe types of a propety as: -// mystr: 'string', -// mynum: 'number', -// mybool: 'boolean', -const types = { - hintchars: 'string', - smoothscroll: 'boolean', - complete: 'string', -}; - -// describe default values of a property -const defaults = { - hintchars: 'abcdefghijklmnopqrstuvwxyz', - smoothscroll: false, - complete: 'sbh', -}; - -const docs = { - hintchars: 'hint characters on follow mode', - smoothscroll: 'smooth scroll', - complete: 'which are completed at the open page', -}; - -export { types, defaults, docs }; diff --git a/src/shared/settings/storage.js b/src/shared/settings/storage.js deleted file mode 100644 index 5dce3b0..0000000 --- a/src/shared/settings/storage.js +++ /dev/null @@ -1,32 +0,0 @@ -import DefaultSettings from './default'; -import * as settingsValues from './values'; - -const loadRaw = async() => { - let { settings } = await browser.storage.local.get('settings'); - if (!settings) { - return DefaultSettings; - } - return { ...DefaultSettings, ...settings }; -}; - -const loadValue = async() => { - let settings = await loadRaw(); - let value = JSON.parse(DefaultSettings.json); - if (settings.source === 'json') { - value = settingsValues.valueFromJson(settings.json); - } else if (settings.source === 'form') { - value = settingsValues.valueFromForm(settings.form); - } - if (!value.properties) { - value.properties = {}; - } - return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value }; -}; - -const save = (settings) => { - return browser.storage.local.set({ - settings, - }); -}; - -export { loadRaw, loadValue, save }; diff --git a/src/shared/settings/validator.js b/src/shared/settings/validator.js deleted file mode 100644 index a800a52..0000000 --- a/src/shared/settings/validator.js +++ /dev/null @@ -1,76 +0,0 @@ -import operations from 'shared/operations'; -import * as properties from './properties'; - -const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties']; -const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => { - return operations[key]; -}); - -const validateInvalidTopKeys = (settings) => { - let invalidKey = Object.keys(settings).find((key) => { - return !VALID_TOP_KEYS.includes(key); - }); - if (invalidKey) { - throw Error(`Unknown key: "${invalidKey}"`); - } -}; - -const validateKeymaps = (keymaps) => { - for (let key of Object.keys(keymaps)) { - let value = keymaps[key]; - if (!VALID_OPERATION_VALUES.includes(value.type)) { - throw Error(`Unknown operation: "${value.type}"`); - } - } -}; - -const validateSearch = (search) => { - let engines = search.engines; - for (let key of Object.keys(engines)) { - if ((/\s/).test(key)) { - throw new Error( - `While space in search engine name is not allowed: "${key}"` - ); - } - let url = engines[key]; - if (!url.match(/{}/)) { - throw new Error(`No {}-placeholders in URL of "${key}"`); - } - if (url.match(/{}/g).length > 1) { - throw new Error(`Multiple {}-placeholders in URL of "${key}"`); - } - } - - if (!search.default) { - throw new Error(`Default engine is not set`); - } - if (!Object.keys(engines).includes(search.default)) { - throw new Error(`Default engine "${search.default}" not found`); - } -}; - -const validateProperties = (props) => { - for (let name of Object.keys(props)) { - if (!properties.types[name]) { - throw new Error(`Unknown property name: "${name}"`); - } - if (typeof props[name] !== properties.types[name]) { - throw new Error(`Invalid type for property: "${name}"`); - } - } -}; - -const validate = (settings) => { - validateInvalidTopKeys(settings); - if (settings.keymaps) { - validateKeymaps(settings.keymaps); - } - if (settings.search) { - validateSearch(settings.search); - } - if (settings.properties) { - validateProperties(settings.properties); - } -}; - -export { validate }; diff --git a/src/shared/settings/values.js b/src/shared/settings/values.js deleted file mode 100644 index 9828af6..0000000 --- a/src/shared/settings/values.js +++ /dev/null @@ -1,108 +0,0 @@ -import * as properties from './properties'; - -const operationFromFormName = (name) => { - let [type, argStr] = name.split('?'); - let args = {}; - if (argStr) { - args = JSON.parse(argStr); - } - return { type, ...args }; -}; - -const operationToFormName = (op) => { - let type = op.type; - let args = { ...op }; - delete args.type; - - if (Object.keys(args).length === 0) { - return type; - } - return op.type + '?' + JSON.stringify(args); -}; - -const valueFromJson = (json) => { - return JSON.parse(json); -}; - -const valueFromForm = (form) => { - let keymaps = undefined; - if (form.keymaps) { - keymaps = {}; - for (let name of Object.keys(form.keymaps)) { - let keys = form.keymaps[name]; - keymaps[keys] = operationFromFormName(name); - } - } - - let search = undefined; - if (form.search) { - search = { default: form.search.default }; - - if (form.search.engines) { - search.engines = {}; - for (let [name, url] of form.search.engines) { - search.engines[name] = url; - } - } - } - - return { - keymaps, - search, - blacklist: form.blacklist, - properties: form.properties - }; -}; - -const jsonFromValue = (value) => { - return JSON.stringify(value, undefined, 2); -}; - -const formFromValue = (value, allowedOps) => { - let keymaps = undefined; - - if (value.keymaps) { - let allowedSet = new Set(allowedOps); - - keymaps = {}; - for (let keys of Object.keys(value.keymaps)) { - let op = operationToFormName(value.keymaps[keys]); - if (allowedSet.has(op)) { - keymaps[op] = keys; - } - } - } - - let search = undefined; - if (value.search) { - search = { default: value.search.default }; - if (value.search.engines) { - search.engines = Object.keys(value.search.engines).map((name) => { - return [name, value.search.engines[name]]; - }); - } - } - - let formProperties = { ...properties.defaults, ...value.properties }; - - return { - keymaps, - search, - blacklist: value.blacklist, - properties: formProperties, - }; -}; - -const jsonFromForm = (form) => { - return jsonFromValue(valueFromForm(form)); -}; - -const formFromJson = (json, allowedOps) => { - let value = valueFromJson(json); - return formFromValue(value, allowedOps); -}; - -export { - valueFromJson, valueFromForm, jsonFromValue, formFromValue, - jsonFromForm, formFromJson -}; diff --git a/src/shared/urls.js b/src/shared/urls.ts index 94b1220..18349c8 100644 --- a/src/shared/urls.js +++ b/src/shared/urls.ts @@ -1,11 +1,11 @@ -const trimStart = (str) => { +const trimStart = (str: string): string => { // NOTE String.trimStart is available on Firefox 61 return str.replace(/^\s+/, ''); }; const SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ftp:', 'mailto:', 'about:']; -const searchUrl = (keywords, searchSettings) => { +const searchUrl = (keywords: string, searchSettings: any): string => { try { let u = new URL(keywords); if (SUPPORTED_PROTOCOLS.includes(u.protocol.toLowerCase())) { @@ -28,7 +28,7 @@ const searchUrl = (keywords, searchSettings) => { return template.replace('{}', encodeURIComponent(query)); }; -const normalizeUrl = (url) => { +const normalizeUrl = (url: string): string => { try { let u = new URL(url); if (SUPPORTED_PROTOCOLS.includes(u.protocol.toLowerCase())) { diff --git a/src/shared/utils/dom.js b/src/shared/utils/dom.ts index 974d534..c1f2190 100644 --- a/src/shared/utils/dom.js +++ b/src/shared/utils/dom.ts @@ -1,16 +1,24 @@ -const isContentEditable = (element) => { - return element.hasAttribute('contenteditable') && ( - element.getAttribute('contenteditable').toLowerCase() === 'true' || - element.getAttribute('contenteditable').toLowerCase() === '' - ); +const isContentEditable = (element: Element): boolean => { + let value = element.getAttribute('contenteditable'); + if (value === null) { + return false; + } + return value.toLowerCase() === 'true' || value.toLowerCase() === ''; }; -const rectangleCoordsRect = (coords) => { +interface Rect { + left: number; + top: number; + right: number; + bottom: number; +} + +const rectangleCoordsRect = (coords: string): Rect => { let [left, top, right, bottom] = coords.split(',').map(n => Number(n)); return { left, top, right, bottom }; }; -const circleCoordsRect = (coords) => { +const circleCoordsRect = (coords: string): Rect => { let [x, y, r] = coords.split(',').map(n => Number(n)); return { left: x - r, @@ -20,7 +28,7 @@ const circleCoordsRect = (coords) => { }; }; -const polygonCoordsRect = (coords) => { +const polygonCoordsRect = (coords: string): Rect => { let params = coords.split(','); let minx = Number(params[0]), maxx = Number(params[0]), @@ -46,18 +54,24 @@ const polygonCoordsRect = (coords) => { return { left: minx, top: miny, right: maxx, bottom: maxy }; }; -const viewportRect = (e) => { +const viewportRect = (e: Element): Rect => { if (e.tagName !== 'AREA') { return e.getBoundingClientRect(); } - let mapElement = e.parentNode; - let imgElement = document.querySelector(`img[usemap="#${mapElement.name}"]`); + let mapElement = e.parentNode as HTMLMapElement; + let imgElement = document.querySelector( + `img[usemap="#${mapElement.name}"]` + ) as HTMLImageElement; let { left: mapLeft, top: mapTop } = imgElement.getBoundingClientRect(); let coords = e.getAttribute('coords'); + if (!coords) { + return e.getBoundingClientRect(); + } + let rect = { left: 0, top: 0, right: 0, bottom: 0 }; switch (e.getAttribute('shape')) { case 'rect': @@ -81,7 +95,7 @@ const viewportRect = (e) => { }; }; -const isVisible = (element) => { +const isVisible = (element: Element): boolean => { let rect = element.getBoundingClientRect(); let style = window.getComputedStyle(element); @@ -94,7 +108,8 @@ const isVisible = (element) => { if (window.innerWidth < rect.left && window.innerHeight < rect.top) { return false; } - if (element.nodeName === 'INPUT' && element.type.toLowerCase() === 'hidden') { + if (element instanceof HTMLInputElement && + element.type.toLowerCase() === 'hidden') { return false; } diff --git a/src/shared/utils/keys.js b/src/shared/utils/keys.ts index f024069..e9b0365 100644 --- a/src/shared/utils/keys.js +++ b/src/shared/utils/keys.ts @@ -1,4 +1,12 @@ -const modifiedKeyName = (name) => { +export interface Key { + key: string; + shiftKey: boolean | undefined; + ctrlKey: boolean | undefined; + altKey: boolean | undefined; + metaKey: boolean | undefined; +} + +const modifiedKeyName = (name: string): string => { if (name === ' ') { return 'Space'; } @@ -10,7 +18,7 @@ const modifiedKeyName = (name) => { return name; }; -const fromKeyboardEvent = (e) => { +const fromKeyboardEvent = (e: KeyboardEvent): Key => { let key = modifiedKeyName(e.key); let shift = e.shiftKey; if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) { @@ -28,7 +36,7 @@ const fromKeyboardEvent = (e) => { }; }; -const fromMapKey = (key) => { +const fromMapKey = (key: string): Key => { if (key.startsWith('<') && key.endsWith('>')) { let inner = key.slice(1, -1); let shift = inner.includes('S-'); @@ -55,8 +63,10 @@ const fromMapKey = (key) => { }; }; -const fromMapKeys = (keys) => { - const fromMapKeysRecursive = (remainings, mappedKeys) => { +const fromMapKeys = (keys: string): Key[] => { + const fromMapKeysRecursive = ( + remainings: string, mappedKeys: Key[], + ): Key[] => { if (remainings.length === 0) { return mappedKeys; } @@ -78,7 +88,7 @@ const fromMapKeys = (keys) => { return fromMapKeysRecursive(keys, []); }; -const equals = (e1, e2) => { +const equals = (e1: Key, e2: Key): boolean => { return e1.key === e2.key && e1.ctrlKey === e2.ctrlKey && e1.metaKey === e2.metaKey && diff --git a/src/shared/utils/re.js b/src/shared/utils/re.ts index 7db9091..34f4fa6 100644 --- a/src/shared/utils/re.js +++ b/src/shared/utils/re.ts @@ -1,4 +1,4 @@ -const fromWildcard = (pattern) => { +const fromWildcard = (pattern: string): RegExp => { let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$'; return new RegExp(regexStr); }; |