parent
d01db82c0d
commit
a0882bbceb
48 changed files with 1617 additions and 902 deletions
@ -1,6 +1,7 @@ |
||||
export interface GlobalMark { |
||||
export default interface GlobalMark { |
||||
readonly tabId: number; |
||||
readonly url: string; |
||||
readonly x: number; |
||||
readonly y: number; |
||||
// eslint-disable-next-line semi
|
||||
} |
||||
|
@ -1,59 +0,0 @@ |
||||
import DefaultSettings from '../../shared/settings/default'; |
||||
import * as settingsValues from '../../shared/settings/values'; |
||||
|
||||
type SettingValue = { |
||||
source: string, |
||||
json: string, |
||||
form: any |
||||
} |
||||
|
||||
export default class Setting { |
||||
private obj: SettingValue; |
||||
|
||||
constructor({ source, json, form }: SettingValue) { |
||||
this.obj = { |
||||
source, json, form |
||||
}; |
||||
} |
||||
|
||||
get source(): string { |
||||
return this.obj.source; |
||||
} |
||||
|
||||
get json(): string { |
||||
return this.obj.json; |
||||
} |
||||
|
||||
get form(): any { |
||||
return this.obj.form; |
||||
} |
||||
|
||||
value() { |
||||
let value = JSON.parse(DefaultSettings.json); |
||||
if (this.obj.source === 'json') { |
||||
value = settingsValues.valueFromJson(this.obj.json); |
||||
} else if (this.obj.source === 'form') { |
||||
value = settingsValues.valueFromForm(this.obj.form); |
||||
} |
||||
if (!value.properties) { |
||||
value.properties = {}; |
||||
} |
||||
return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value }; |
||||
} |
||||
|
||||
serialize(): SettingValue { |
||||
return this.obj; |
||||
} |
||||
|
||||
static deserialize(obj: SettingValue): Setting { |
||||
return new Setting({ source: obj.source, json: obj.json, form: obj.form }); |
||||
} |
||||
|
||||
static defaultSettings() { |
||||
return new Setting({ |
||||
source: DefaultSettings.source, |
||||
json: DefaultSettings.json, |
||||
form: {}, |
||||
}); |
||||
} |
||||
} |
@ -1,12 +1,12 @@ |
||||
import Setting from '../domains/Setting'; |
||||
import SettingData from '../../shared/SettingData'; |
||||
|
||||
export default class SettingRepository { |
||||
async load(): Promise<any> { |
||||
async load(): Promise<SettingData | null> { |
||||
let { settings } = await browser.storage.local.get('settings'); |
||||
if (!settings) { |
||||
return null; |
||||
} |
||||
return Setting.deserialize(settings); |
||||
return SettingData.valueOf(settings); |
||||
} |
||||
} |
||||
|
||||
|
@ -0,0 +1,15 @@ |
||||
import SettingData, { DefaultSettingData } from '../shared/SettingData'; |
||||
|
||||
export const load = async(): Promise<SettingData> => { |
||||
let { settings } = await browser.storage.local.get('settings'); |
||||
if (!settings) { |
||||
return DefaultSettingData; |
||||
} |
||||
return SettingData.valueOf(settings); |
||||
}; |
||||
|
||||
export const save = (data: SettingData) => { |
||||
return browser.storage.local.set({ |
||||
settings: data.toJSON(), |
||||
}); |
||||
}; |
@ -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": [ |
||||
] |
||||
}`,
|
||||
}); |
@ -0,0 +1,200 @@ |
||||
import * as operations from './operations'; |
||||
import * as PropertyDefs from './property-defs'; |
||||
|
||||
export type Keymaps = {[key: string]: operations.Operation}; |
||||
|
||||
export interface Search { |
||||
default: string; |
||||
engines: { [key: string]: string }; |
||||
} |
||||
|
||||
export interface Properties { |
||||
hintchars: string; |
||||
smoothscroll: boolean; |
||||
complete: string; |
||||
} |
||||
|
||||
export default interface Settings { |
||||
keymaps: Keymaps; |
||||
search: Search; |
||||
properties: Properties; |
||||
blacklist: string[]; |
||||
// eslint-disable-next-line semi
|
||||
} |
||||
|
||||
const DefaultProperties: Properties = PropertyDefs.defs.reduce( |
||||
(o: {[name: string]: PropertyDefs.Type}, def) => { |
||||
o[def.name] = def.defaultValue; |
||||
return o; |
||||
}, {}) as Properties; |
||||
|
||||
|
||||
export const keymapsValueOf = (o: any): Keymaps => { |
||||
return Object.keys(o).reduce((keymaps: Keymaps, key: string): Keymaps => { |
||||
let op = operations.valueOf(o[key]); |
||||
keymaps[key] = op; |
||||
return keymaps; |
||||
}, {}); |
||||
}; |
||||
|
||||
export const searchValueOf = (o: any): Search => { |
||||
if (typeof o.default !== 'string') { |
||||
throw new TypeError('string field "default" not set"'); |
||||
} |
||||
for (let name of Object.keys(o.engines)) { |
||||
if ((/\s/).test(name)) { |
||||
throw new TypeError( |
||||
`While space in the search engine not allowed: "${name}"`); |
||||
} |
||||
let url = o.engines[name]; |
||||
if (typeof url !== 'string') { |
||||
throw new TypeError('"engines" not an object of string'); |
||||
} |
||||
let matches = url.match(/{}/g); |
||||
if (matches === null) { |
||||
throw new TypeError(`No {}-placeholders in URL of "${name}"`); |
||||
} else if (matches.length > 1) { |
||||
throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`); |
||||
} |
||||
|
||||
} |
||||
if (!Object.prototype.hasOwnProperty.call(o.engines, o.default)) { |
||||
throw new TypeError(`Default engine "${o.default}" not found`); |
||||
} |
||||
return { |
||||
default: o.default as string, |
||||
engines: { ...o.engines }, |
||||
}; |
||||
}; |
||||
|
||||
export const propertiesValueOf = (o: any): Properties => { |
||||
let defNames = new Set(PropertyDefs.defs.map(def => def.name)); |
||||
let unknownName = Object.keys(o).find(name => !defNames.has(name)); |
||||
if (unknownName) { |
||||
throw new TypeError(`Unknown property name: "${unknownName}"`); |
||||
} |
||||
|
||||
for (let def of PropertyDefs.defs) { |
||||
if (!Object.prototype.hasOwnProperty.call(o, def.name)) { |
||||
continue; |
||||
} |
||||
if (typeof o[def.name] !== def.type) { |
||||
throw new TypeError(`property "${def.name}" is not ${def.type}`); |
||||
} |
||||
} |
||||
return { |
||||
...DefaultProperties, |
||||
...o, |
||||
}; |
||||
}; |
||||
|
||||
export const blacklistValueOf = (o: any): string[] => { |
||||
if (!Array.isArray(o)) { |
||||
throw new TypeError(`"blacklist" is not an array of string`); |
||||
} |
||||
for (let x of o) { |
||||
if (typeof x !== 'string') { |
||||
throw new TypeError(`"blacklist" is not an array of string`); |
||||
} |
||||
} |
||||
return o as string[]; |
||||
}; |
||||
|
||||
export const valueOf = (o: any): Settings => { |
||||
let settings = { ...DefaultSetting }; |
||||
if (Object.prototype.hasOwnProperty.call(o, 'keymaps')) { |
||||
settings.keymaps = keymapsValueOf(o.keymaps); |
||||
} |
||||
if (Object.prototype.hasOwnProperty.call(o, 'search')) { |
||||
settings.search = searchValueOf(o.search); |
||||
} |
||||
if (Object.prototype.hasOwnProperty.call(o, 'properties')) { |
||||
settings.properties = propertiesValueOf(o.properties); |
||||
} |
||||
if (Object.prototype.hasOwnProperty.call(o, 'blacklist')) { |
||||
settings.blacklist = blacklistValueOf(o.blacklist); |
||||
} |
||||
return settings; |
||||
}; |
||||
|
||||
const DefaultSetting: Settings = { |
||||
keymaps: { |
||||
'0': { 'type': 'scroll.home' }, |
||||
':': { 'type': 'command.show' }, |
||||
'o': { 'type': 'command.show.open', 'alter': false }, |
||||
'O': { 'type': 'command.show.open', 'alter': true }, |
||||
't': { 'type': 'command.show.tabopen', 'alter': false }, |
||||
'T': { 'type': 'command.show.tabopen', 'alter': true }, |
||||
'w': { 'type': 'command.show.winopen', 'alter': false }, |
||||
'W': { 'type': 'command.show.winopen', 'alter': true }, |
||||
'b': { 'type': 'command.show.buffer' }, |
||||
'a': { 'type': 'command.show.addbookmark', 'alter': true }, |
||||
'k': { 'type': 'scroll.vertically', 'count': -1 }, |
||||
'j': { 'type': 'scroll.vertically', 'count': 1 }, |
||||
'h': { 'type': 'scroll.horizonally', 'count': -1 }, |
||||
'l': { 'type': 'scroll.horizonally', 'count': 1 }, |
||||
'<C-U>': { 'type': 'scroll.pages', 'count': -0.5 }, |
||||
'<C-D>': { 'type': 'scroll.pages', 'count': 0.5 }, |
||||
'<C-B>': { 'type': 'scroll.pages', 'count': -1 }, |
||||
'<C-F>': { 'type': 'scroll.pages', 'count': 1 }, |
||||
'gg': { 'type': 'scroll.top' }, |
||||
'G': { 'type': 'scroll.bottom' }, |
||||
'$': { 'type': 'scroll.end' }, |
||||
'd': { 'type': 'tabs.close' }, |
||||
'D': { 'type': 'tabs.close.right' }, |
||||
'!d': { 'type': 'tabs.close.force' }, |
||||
'u': { 'type': 'tabs.reopen' }, |
||||
'K': { 'type': 'tabs.prev' }, |
||||
'J': { 'type': 'tabs.next' }, |
||||
'gT': { 'type': 'tabs.prev' }, |
||||
'gt': { 'type': 'tabs.next' }, |
||||
'g0': { 'type': 'tabs.first' }, |
||||
'g$': { 'type': 'tabs.last' }, |
||||
'<C-6>': { 'type': 'tabs.prevsel' }, |
||||
'r': { 'type': 'tabs.reload', 'cache': false }, |
||||
'R': { 'type': 'tabs.reload', 'cache': true }, |
||||
'zp': { 'type': 'tabs.pin.toggle' }, |
||||
'zd': { 'type': 'tabs.duplicate' }, |
||||
'zi': { 'type': 'zoom.in' }, |
||||
'zo': { 'type': 'zoom.out' }, |
||||
'zz': { 'type': 'zoom.neutral' }, |
||||
'f': { 'type': 'follow.start', 'newTab': false, 'background': false }, |
||||
'F': { 'type': 'follow.start', 'newTab': true, 'background': false }, |
||||
'm': { 'type': 'mark.set.prefix' }, |
||||
'\'': { 'type': 'mark.jump.prefix' }, |
||||
'H': { 'type': 'navigate.history.prev' }, |
||||
'L': { 'type': 'navigate.history.next' }, |
||||
'[[': { 'type': 'navigate.link.prev' }, |
||||
']]': { 'type': 'navigate.link.next' }, |
||||
'gu': { 'type': 'navigate.parent' }, |
||||
'gU': { 'type': 'navigate.root' }, |
||||
'gi': { 'type': 'focus.input' }, |
||||
'gf': { 'type': 'page.source' }, |
||||
'gh': { 'type': 'page.home', 'newTab': false }, |
||||
'gH': { 'type': 'page.home', 'newTab': true }, |
||||
'y': { 'type': 'urls.yank' }, |
||||
'p': { 'type': 'urls.paste', 'newTab': false }, |
||||
'P': { 'type': 'urls.paste', 'newTab': true }, |
||||
'/': { 'type': 'find.start' }, |
||||
'n': { 'type': 'find.next' }, |
||||
'N': { 'type': 'find.prev' }, |
||||
'<S-Esc>': { 'type': 'addon.toggle.enabled' } |
||||
}, |
||||
search: { |
||||
default: 'google', |
||||
engines: { |
||||
'google': 'https://google.com/search?q={}', |
||||
'yahoo': 'https://search.yahoo.com/search?p={}', |
||||
'bing': 'https://www.bing.com/search?q={}', |
||||
'duckduckgo': 'https://duckduckgo.com/?q={}', |
||||
'twitter': 'https://twitter.com/search?q={}', |
||||
'wikipedia': 'https://en.wikipedia.org/w/index.php?search={}' |
||||
} |
||||
}, |
||||
properties: { |
||||
hintchars: 'abcdefghijklmnopqrstuvwxyz', |
||||
smoothscroll: false, |
||||
complete: 'sbh' |
||||
}, |
||||
blacklist: [] |
||||
}; |
@ -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'), |
||||
]; |
@ -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'), |
||||
]; |
@ -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": [ |
||||
] |
||||
}`,
|
||||
}; |
@ -1,24 +0,0 @@ |
||||
// describe types of a propety as:
|
||||
// mystr: 'string',
|
||||
// mynum: 'number',
|
||||
// mybool: 'boolean',
|
||||
const types: { [key: string]: string } = { |
||||
hintchars: 'string', |
||||
smoothscroll: 'boolean', |
||||
complete: 'string', |
||||
}; |
||||
|
||||
// describe default values of a property
|
||||
const defaults: { [key: string]: string | number | boolean } = { |
||||
hintchars: 'abcdefghijklmnopqrstuvwxyz', |
||||
smoothscroll: false, |
||||
complete: 'sbh', |
||||
}; |
||||
|
||||
const docs: { [key: string]: string } = { |
||||
hintchars: 'hint characters on follow mode', |
||||
smoothscroll: 'smooth scroll', |
||||
complete: 'which are completed at the open page', |
||||
}; |
||||
|
||||
export { types, defaults, docs }; |
@ -1,32 +0,0 @@ |
||||
import DefaultSettings from './default'; |
||||
import * as settingsValues from './values'; |
||||
|
||||
const loadRaw = async(): Promise<any> => { |
||||
let { settings } = await browser.storage.local.get('settings'); |
||||
if (!settings) { |
||||
return DefaultSettings; |
||||
} |
||||
return { ...DefaultSettings, ...settings as object }; |
||||
}; |
||||
|
||||
const loadValue = async() => { |
||||
let settings = await loadRaw(); |
||||
let value = JSON.parse(DefaultSettings.json); |
||||
if (settings.source === 'json') { |
||||
value = settingsValues.valueFromJson(settings.json); |
||||
} else if (settings.source === 'form') { |
||||
value = settingsValues.valueFromForm(settings.form); |
||||
} |
||||
if (!value.properties) { |
||||
value.properties = {}; |
||||
} |
||||
return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value }; |
||||
}; |
||||
|
||||
const save = (settings: any): Promise<any> => { |
||||
return browser.storage.local.set({ |
||||
settings, |
||||
}); |
||||
}; |
||||
|
||||
export { loadRaw, loadValue, save }; |
@ -1,76 +0,0 @@ |
||||
import * as operations from '../operations'; |
||||
import * as properties from './properties'; |
||||
|
||||
const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties']; |
||||
const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => { |
||||
return operations[key]; |
||||
}); |
||||
|
||||
const validateInvalidTopKeys = (settings: any): void => { |
||||
let invalidKey = Object.keys(settings).find((key) => { |
||||
return !VALID_TOP_KEYS.includes(key); |
||||
}); |
||||
if (invalidKey) { |
||||
throw Error(`Unknown key: "${invalidKey}"`); |
||||
} |
||||
}; |
||||
|
||||
const validateKeymaps = (keymaps: any): void => { |
||||
for (let key of Object.keys(keymaps)) { |
||||
let value = keymaps[key]; |
||||
if (!VALID_OPERATION_VALUES.includes(value.type)) { |
||||
throw Error(`Unknown operation: "${value.type}"`); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
const validateSearch = (search: any): void => { |
||||
let engines = search.engines; |
||||
for (let key of Object.keys(engines)) { |
||||
if ((/\s/).test(key)) { |
||||
throw new Error( |
||||
`While space in search engine name is not allowed: "${key}"` |
||||
); |
||||
} |
||||
let url = engines[key]; |
||||
if (!url.match(/{}/)) { |
||||
throw new Error(`No {}-placeholders in URL of "${key}"`); |
||||
} |
||||
if (url.match(/{}/g).length > 1) { |
||||
throw new Error(`Multiple {}-placeholders in URL of "${key}"`); |
||||
} |
||||
} |
||||
|
||||
if (!search.default) { |
||||
throw new Error(`Default engine is not set`); |
||||
} |
||||
if (!Object.keys(engines).includes(search.default)) { |
||||
throw new Error(`Default engine "${search.default}" not found`); |
||||
} |
||||
}; |
||||
|
||||
const validateProperties = (props: any): void => { |
||||
for (let name of Object.keys(props)) { |
||||
if (!properties.types[name]) { |
||||
throw new Error(`Unknown property name: "${name}"`); |
||||
} |
||||
if (typeof props[name] !== properties.types[name]) { |
||||
throw new Error(`Invalid type for property: "${name}"`); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
const validate = (settings: any): void => { |
||||
validateInvalidTopKeys(settings); |
||||
if (settings.keymaps) { |
||||
validateKeymaps(settings.keymaps); |
||||
} |
||||
if (settings.search) { |
||||
validateSearch(settings.search); |
||||
} |
||||
if (settings.properties) { |
||||
validateProperties(settings.properties); |
||||
} |
||||
}; |
||||
|
||||
export { validate }; |
@ -1,108 +0,0 @@ |
||||
import * as properties from './properties'; |
||||
|
||||
const operationFromFormName = (name: string): any => { |
||||
let [type, argStr] = name.split('?'); |
||||
let args = {}; |
||||
if (argStr) { |
||||
args = JSON.parse(argStr); |
||||
} |
||||
return { type, ...args }; |
||||
}; |
||||
|
||||
const operationToFormName = (op: any): string => { |
||||
let type = op.type; |
||||
let args = { ...op }; |
||||
delete args.type; |
||||
|
||||
if (Object.keys(args).length === 0) { |
||||
return type; |
||||
} |
||||
return op.type + '?' + JSON.stringify(args); |
||||
}; |
||||
|
||||
const valueFromJson = (json: string): object => { |
||||
return JSON.parse(json); |
||||
}; |
||||
|
||||
const valueFromForm = (form: any): object => { |
||||
let keymaps: any = undefined; |
||||
if (form.keymaps) { |
||||
keymaps = {}; |
||||
for (let name of Object.keys(form.keymaps)) { |
||||
let keys = form.keymaps[name]; |
||||
keymaps[keys] = operationFromFormName(name); |
||||
} |
||||
} |
||||
|
||||
let search: any = undefined; |
||||
if (form.search) { |
||||
search = { default: form.search.default }; |
||||
|
||||
if (form.search.engines) { |
||||
search.engines = {}; |
||||
for (let [name, url] of form.search.engines) { |
||||
search.engines[name] = url; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return { |
||||
keymaps, |
||||
search, |
||||
blacklist: form.blacklist, |
||||
properties: form.properties |
||||
}; |
||||
}; |
||||
|
||||
const jsonFromValue = (value: any): string => { |
||||
return JSON.stringify(value, undefined, 2); |
||||
}; |
||||
|
||||
const formFromValue = (value: any, allowedOps: any[]): any => { |
||||
let keymaps: any = undefined; |
||||
|
||||
if (value.keymaps) { |
||||
let allowedSet = new Set(allowedOps); |
||||
|
||||
keymaps = {}; |
||||
for (let keys of Object.keys(value.keymaps)) { |
||||
let op = operationToFormName(value.keymaps[keys]); |
||||
if (allowedSet.has(op)) { |
||||
keymaps[op] = keys; |
||||
} |
||||
} |
||||
} |
||||
|
||||
let search: any = undefined; |
||||
if (value.search) { |
||||
search = { default: value.search.default }; |
||||
if (value.search.engines) { |
||||
search.engines = Object.keys(value.search.engines).map((name) => { |
||||
return [name, value.search.engines[name]]; |
||||
}); |
||||
} |
||||
} |
||||
|
||||
let formProperties = { ...properties.defaults, ...value.properties }; |
||||
|
||||
return { |
||||
keymaps, |
||||
search, |
||||
blacklist: value.blacklist, |
||||
properties: formProperties, |
||||
}; |
||||
}; |
||||
|
||||
const jsonFromForm = (form: any): string => { |
||||
return jsonFromValue(valueFromForm(form)); |
||||
}; |
||||
|
||||
const formFromJson = (json: string, allowedOps: any[]): any => { |
||||
let value = valueFromJson(json); |
||||
return formFromValue(value, allowedOps); |
||||
}; |
||||
|
||||
export { |
||||
valueFromJson, valueFromForm, jsonFromValue, formFromValue, |
||||
jsonFromForm, formFromJson |
||||
}; |
@ -0,0 +1,293 @@ |
||||
import SettingData, { |
||||
FormKeymaps, JSONSettings, FormSettings, |
||||
} from '../../src/shared/SettingData'; |
||||
import Settings, { Keymaps } from '../../src/shared/Settings'; |
||||
import { expect } from 'chai'; |
||||
|
||||
describe('shared/SettingData', () => { |
||||
describe('FormKeymaps', () => { |
||||
describe('#valueOF to #toKeymaps', () => { |
||||
it('parses form keymaps and convert to operations', () => { |
||||
let data = { |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.home': '0', |
||||
} |
||||
|
||||
let keymaps = FormKeymaps.valueOf(data).toKeymaps(); |
||||
expect(keymaps).to.deep.equal({ |
||||
'j': { type: 'scroll.vertically', count: 1 }, |
||||
'0': { type: 'scroll.home' }, |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('#fromKeymaps to #toJSON', () => { |
||||
it('create from a Keymaps and create a JSON object', () => { |
||||
let data: Keymaps = { |
||||
'j': { type: 'scroll.vertically', count: 1 }, |
||||
'0': { type: 'scroll.home' }, |
||||
} |
||||
|
||||
let keymaps = FormKeymaps.fromKeymaps(data).toJSON(); |
||||
expect(keymaps).to.deep.equal({ |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.home': '0', |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('JSONSettings', () => { |
||||
describe('#valueOf to #toSettings', () => { |
||||
it('parse object and create a Settings', () => { |
||||
let o = `{
|
||||
"keymaps": {}, |
||||
"search": { |
||||
"default": "google", |
||||
"engines": { |
||||
"google": "https://google.com/search?q={}" |
||||
} |
||||
}, |
||||
"properties": { |
||||
"hintchars": "abcdefghijklmnopqrstuvwxyz", |
||||
"smoothscroll": false, |
||||
"complete": "sbh" |
||||
}, |
||||
"blacklist": [] |
||||
}`;
|
||||
|
||||
let settings = JSONSettings.valueOf(o).toSettings(); |
||||
expect(settings).to.deep.equal(JSON.parse(o)); |
||||
}); |
||||
}); |
||||
|
||||
describe('#fromSettings to #toJSON', () => { |
||||
it('create from a Settings and create a JSON string', () => { |
||||
let o = { |
||||
keymaps: {}, |
||||
search: { |
||||
default: "google", |
||||
engines: { |
||||
google: "https://google.com/search?q={}", |
||||
}, |
||||
}, |
||||
properties: { |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}, |
||||
blacklist: [], |
||||
}; |
||||
|
||||
let json = JSONSettings.fromSettings(o).toJSON(); |
||||
expect(JSON.parse(json)).to.deep.equal(o); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('FormSettings', () => { |
||||
describe('#valueOf to #toSettings', () => { |
||||
it('parse object and create a Settings', () => { |
||||
let data = { |
||||
keymaps: { |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.home': '0', |
||||
}, |
||||
search: { |
||||
default: "google", |
||||
engines: [ |
||||
["google", "https://google.com/search?q={}"], |
||||
] |
||||
}, |
||||
properties: { |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}, |
||||
blacklist: [] |
||||
}; |
||||
|
||||
let settings = FormSettings.valueOf(data).toSettings(); |
||||
expect(settings).to.deep.equal({ |
||||
keymaps: { |
||||
'j': { type: 'scroll.vertically', count: 1 }, |
||||
'0': { type: 'scroll.home' }, |
||||
}, |
||||
search: { |
||||
default: "google", |
||||
engines: { |
||||
"google": "https://google.com/search?q={}" |
||||
} |
||||
}, |
||||
properties: { |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}, |
||||
blacklist: [] |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('#fromSettings to #toJSON', () => { |
||||
it('create from a Settings and create a JSON string', () => { |
||||
let data: Settings = { |
||||
keymaps: { |
||||
'j': { type: 'scroll.vertically', count: 1 }, |
||||
'0': { type: 'scroll.home' }, |
||||
}, |
||||
search: { |
||||
default: "google", |
||||
engines: { |
||||
"google": "https://google.com/search?q={}" |
||||
} |
||||
}, |
||||
properties: { |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}, |
||||
blacklist: [] |
||||
}; |
||||
|
||||
let json = FormSettings.fromSettings(data).toJSON(); |
||||
expect(json).to.deep.equal({ |
||||
keymaps: { |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.home': '0', |
||||
}, |
||||
search: { |
||||
default: "google", |
||||
engines: [ |
||||
["google", "https://google.com/search?q={}"], |
||||
] |
||||
}, |
||||
properties: { |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}, |
||||
blacklist: [], |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('SettingData', () => { |
||||
describe('#valueOf to #toJSON', () => { |
||||
it('parse object from json source', () => { |
||||
let data = { |
||||
source: 'json', |
||||
json: `{
|
||||
"keymaps": {}, |
||||
"search": { |
||||
"default": "google", |
||||
"engines": { |
||||
"google": "https://google.com/search?q={}" |
||||
} |
||||
}, |
||||
"properties": { |
||||
"hintchars": "abcdefghijklmnopqrstuvwxyz", |
||||
"smoothscroll": false, |
||||
"complete": "sbh" |
||||
}, |
||||
"blacklist": [] |
||||
}`,
|
||||
}; |
||||
|
||||
let j = SettingData.valueOf(data).toJSON(); |
||||
expect(j.source).to.equal('json'); |
||||
expect(j.json).to.be.a('string'); |
||||
}); |
||||
|
||||
it('parse object from form source', () => { |
||||
let data = { |
||||
source: 'form', |
||||
form: { |
||||
keymaps: {}, |
||||
search: { |
||||
default: "yahoo", |
||||
engines: [ |
||||
['yahoo', 'https://yahoo.com/search?q={}'], |
||||
], |
||||
}, |
||||
properties: { |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}, |
||||
blacklist: [], |
||||
}, |
||||
}; |
||||
|
||||
let j = SettingData.valueOf(data).toJSON(); |
||||
expect(j.source).to.equal('form'); |
||||
expect(j.form).to.deep.equal({ |
||||
keymaps: {}, |
||||
search: { |
||||
default: "yahoo", |
||||
engines: [ |
||||
['yahoo', 'https://yahoo.com/search?q={}'], |
||||
], |
||||
}, |
||||
properties: { |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}, |
||||
blacklist: [], |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('#toSettings', () => { |
||||
it('parse object from json source', () => { |
||||
let data = { |
||||
source: 'json', |
||||
json: `{
|
||||
"keymaps": {}, |
||||
"search": { |
||||
"default": "google", |
||||
"engines": { |
||||
"google": "https://google.com/search?q={}" |
||||
} |
||||
}, |
||||
"properties": { |
||||
"hintchars": "abcdefghijklmnopqrstuvwxyz", |
||||
"smoothscroll": false, |
||||
"complete": "sbh" |
||||
}, |
||||
"blacklist": [] |
||||
}`,
|
||||
}; |
||||
|
||||
let settings = SettingData.valueOf(data).toSettings(); |
||||
expect(settings.search.default).to.equal('google'); |
||||
}); |
||||
|
||||
it('parse object from form source', () => { |
||||
let data = { |
||||
source: 'form', |
||||
form: { |
||||
keymaps: {}, |
||||
search: { |
||||
default: "yahoo", |
||||
engines: [ |
||||
['yahoo', 'https://yahoo.com/search?q={}'], |
||||
], |
||||
}, |
||||
properties: { |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}, |
||||
blacklist: [], |
||||
}, |
||||
}; |
||||
|
||||
let settings = SettingData.valueOf(data).toSettings(); |
||||
expect(settings.search.default).to.equal('yahoo'); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,190 @@ |
||||
import * as settings from '../../src/shared/Settings'; |
||||
import { expect } from 'chai'; |
||||
|
||||
describe('Settings', () => { |
||||
describe('#keymapsValueOf', () => { |
||||
it('returns empty object by empty settings', () => { |
||||
let keymaps = settings.keymapsValueOf({}); |
||||
expect(keymaps).to.be.empty; |
||||
}); |
||||
|
||||
it('returns keymaps by valid settings', () => { |
||||
let keymaps = settings.keymapsValueOf({ |
||||
k: { type: "scroll.vertically", count: -1 }, |
||||
j: { type: "scroll.vertically", count: 1 }, |
||||
}); |
||||
|
||||
expect(keymaps['k']).to.deep.equal({ type: "scroll.vertically", count: -1 }); |
||||
expect(keymaps['j']).to.deep.equal({ type: "scroll.vertically", count: 1 }); |
||||
}); |
||||
|
||||
it('throws a TypeError by invalid settings', () => { |
||||
expect(() => settings.keymapsValueOf(null)).to.throw(TypeError); |
||||
expect(() => settings.keymapsValueOf({ |
||||
k: { type: "invalid.operation" }, |
||||
})).to.throw(TypeError); |
||||
}); |
||||
}); |
||||
|
||||
describe('#searchValueOf', () => { |
||||
it('returns search settings by valid settings', () => { |
||||
let search = settings.searchValueOf({ |
||||
default: "google", |
||||
engines: { |
||||
"google": "https://google.com/search?q={}", |
||||
"yahoo": "https://search.yahoo.com/search?p={}", |
||||
} |
||||
}); |
||||
|
||||
expect(search).to.deep.equal({ |
||||
default: "google", |
||||
engines: { |
||||
"google": "https://google.com/search?q={}", |
||||
"yahoo": "https://search.yahoo.com/search?p={}", |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
it('throws a TypeError by invalid settings', () => { |
||||
expect(() => settings.searchValueOf(null)).to.throw(TypeError); |
||||
expect(() => settings.searchValueOf({})).to.throw(TypeError); |
||||
expect(() => settings.searchValueOf([])).to.throw(TypeError); |
||||
expect(() => settings.searchValueOf({ |
||||
default: 123, |
||||
engines: {} |
||||
})).to.throw(TypeError); |
||||
expect(() => settings.searchValueOf({ |
||||
default: "google", |
||||
engines: { |
||||
"google": 123456, |
||||
} |
||||
})).to.throw(TypeError); |
||||
expect(() => settings.searchValueOf({ |
||||
default: "wikipedia", |
||||
engines: { |
||||
"google": "https://google.com/search?q={}", |
||||
"yahoo": "https://search.yahoo.com/search?p={}", |
||||
} |
||||
})).to.throw(TypeError); |
||||
expect(() => settings.searchValueOf({ |
||||
default: "g o o g l e", |
||||
engines: { |
||||
"g o o g l e": "https://google.com/search?q={}", |
||||
} |
||||
})).to.throw(TypeError); |
||||
expect(() => settings.searchValueOf({ |
||||
default: "google", |
||||
engines: { |
||||
"google": "https://google.com/search", |
||||
} |
||||
})).to.throw(TypeError); |
||||
expect(() => settings.searchValueOf({ |
||||
default: "google", |
||||
engines: { |
||||
"google": "https://google.com/search?q={}&r={}", |
||||
} |
||||
})).to.throw(TypeError); |
||||
}); |
||||
}); |
||||
|
||||
describe('#propertiesValueOf', () => { |
||||
it('returns with default properties by empty settings', () => { |
||||
let props = settings.propertiesValueOf({}); |
||||
expect(props).to.deep.equal({ |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}) |
||||
}); |
||||
|
||||
it('returns properties by valid settings', () => { |
||||
let props = settings.propertiesValueOf({ |
||||
hintchars: "abcdefgh", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}); |
||||
|
||||
expect(props).to.deep.equal({ |
||||
hintchars: "abcdefgh", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}); |
||||
}); |
||||
|
||||
it('throws a TypeError by invalid settings', () => { |
||||
expect(() => settings.keymapsValueOf(null)).to.throw(TypeError); |
||||
expect(() => settings.keymapsValueOf({ |
||||
smoothscroll: 'false', |
||||
})).to.throw(TypeError); |
||||
expect(() => settings.keymapsValueOf({ |
||||
unknown: 'xyz' |
||||
})).to.throw(TypeError); |
||||
}); |
||||
}); |
||||
|
||||
describe('#blacklistValueOf', () => { |
||||
it('returns empty array by empty settings', () => { |
||||
let blacklist = settings.blacklistValueOf([]); |
||||
expect(blacklist).to.be.empty; |
||||
}); |
||||
|
||||
it('returns blacklist by valid settings', () => { |
||||
let blacklist = settings.blacklistValueOf([ |
||||
"github.com", |
||||
"circleci.com", |
||||
]); |
||||
|
||||
expect(blacklist).to.deep.equal([ |
||||
"github.com", |
||||
"circleci.com", |
||||
]); |
||||
}); |
||||
|
||||
it('throws a TypeError by invalid settings', () => { |
||||
expect(() => settings.blacklistValueOf(null)).to.throw(TypeError); |
||||
expect(() => settings.blacklistValueOf({})).to.throw(TypeError); |
||||
expect(() => settings.blacklistValueOf([1,2,3])).to.throw(TypeError); |
||||
}); |
||||
}); |
||||
|
||||
describe('#valueOf', () => { |
||||
it('returns settings by valid settings', () => { |
||||
let x = settings.valueOf({ |
||||
keymaps: {}, |
||||
"search": { |
||||
"default": "google", |
||||
"engines": { |
||||
"google": "https://google.com/search?q={}", |
||||
} |
||||
}, |
||||
"properties": {}, |
||||
"blacklist": [] |
||||
}); |
||||
|
||||
expect(x).to.deep.equal({ |
||||
keymaps: {}, |
||||
search: { |
||||
default: "google", |
||||
engines: { |
||||
google: "https://google.com/search?q={}", |
||||
} |
||||
}, |
||||
properties: { |
||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||
smoothscroll: false, |
||||
complete: "sbh" |
||||
}, |
||||
blacklist: [] |
||||
}); |
||||
}); |
||||
|
||||
it('sets default settings', () => { |
||||
let value = settings.valueOf({}); |
||||
expect(value.keymaps).to.not.be.empty; |
||||
expect(value.properties).to.not.be.empty; |
||||
expect(value.search.default).to.be.a('string'); |
||||
expect(value.search.engines).to.be.an('object'); |
||||
expect(value.blacklist).to.be.empty; |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,18 @@ |
||||
import * as settings from 'shared/settings'; |
||||
|
||||
describe('properties', () => { |
||||
describe('Def class', () => { |
||||
it('returns property definitions', () => { |
||||
let def = new proerties.Def( |
||||
'smoothscroll', |
||||
'smooth scroll', |
||||
false); |
||||
|
||||
expect(def.name).to.equal('smoothscroll'); |
||||
expect(def.describe).to.equal('smooth scroll'); |
||||
expect(def.defaultValue).to.equal(false); |
||||
expect(def.type).to.equal('boolean'); |
||||
}); |
||||
}); |
||||
}); |
||||
|
@ -0,0 +1,18 @@ |
||||
import * as settings from 'shared/settings'; |
||||
|
||||
describe('properties', () => { |
||||
describe('Def class', () => { |
||||
it('returns property definitions', () => { |
||||
let def = new proerties.Def( |
||||
'smoothscroll', |
||||
'smooth scroll', |
||||
false); |
||||
|
||||
expect(def.name).to.equal('smoothscroll'); |
||||
expect(def.describe).to.equal('smooth scroll'); |
||||
expect(def.defaultValue).to.equal(false); |
||||
expect(def.type).to.equal('boolean'); |
||||
}); |
||||
}); |
||||
}); |
||||
|
@ -1,81 +0,0 @@ |
||||
import { validate } from 'shared/settings/validator'; |
||||
|
||||
describe("setting validator", () => { |
||||
describe("unknown top keys", () => { |
||||
it('throws an error for unknown settings', () => { |
||||
let settings = { keymaps: {}, poison: 123 }; |
||||
let fn = validate.bind(undefined, settings) |
||||
expect(fn).to.throw(Error, 'poison'); |
||||
}) |
||||
}); |
||||
|
||||
describe("keymaps settings", () => { |
||||
it('throws an error for unknown operation', () => { |
||||
let settings = { |
||||
keymaps: { |
||||
a: { 'type': 'scroll.home' }, |
||||
b: { 'type': 'poison.dressing' }, |
||||
} |
||||
}; |
||||
let fn = validate.bind(undefined, settings) |
||||
expect(fn).to.throw(Error, 'poison.dressing'); |
||||
}); |
||||
}); |
||||
|
||||
describe("search settings", () => { |
||||
it('throws an error for invalid search engine name', () => { |
||||
let settings = { |
||||
search: { |
||||
default: 'google', |
||||
engines: { |
||||
'google': 'https://google.com/search?q={}', |
||||
'cherry pie': 'https://cherypie.com/search?q={}', |
||||
} |
||||
} |
||||
}; |
||||
let fn = validate.bind(undefined, settings) |
||||
expect(fn).to.throw(Error, 'cherry pie'); |
||||
}); |
||||
|
||||
it('throws an error for no {}-placeholder', () => { |
||||
let settings = { |
||||
search: { |
||||
default: 'google', |
||||
engines: { |
||||
'google': 'https://google.com/search?q={}', |
||||
'yahoo': 'https://search.yahoo.com/search', |
||||
} |
||||
} |
||||
}; |
||||
let fn = validate.bind(undefined, settings) |
||||
expect(fn).to.throw(Error, 'yahoo'); |
||||
}); |
||||
|
||||
it('throws an error for no default engines', () => { |
||||
let settings = { |
||||
search: { |
||||
engines: { |
||||
'google': 'https://google.com/search?q={}', |
||||
'yahoo': 'https://search.yahoo.com/search?q={}', |
||||
} |
||||
} |
||||
}; |
||||
let fn = validate.bind(undefined, settings) |
||||
expect(fn).to.throw(Error, 'Default engine'); |
||||
}); |
||||
|
||||
it('throws an error for invalid default engine', () => { |
||||
let settings = { |
||||
search: { |
||||
default: 'twitter', |
||||
engines: { |
||||
'google': 'https://google.com/search?q={}', |
||||
'yahoo': 'https://search.yahoo.com/search?q={}', |
||||
} |
||||
} |
||||
}; |
||||
let fn = validate.bind(undefined, settings) |
||||
expect(fn).to.throw(Error, 'twitter'); |
||||
}); |
||||
}); |
||||
}); |
@ -1,138 +0,0 @@ |
||||
import * as values from 'shared/settings/values'; |
||||
|
||||
describe("settings values", () => { |
||||
describe('valueFromJson', () => { |
||||
it('return object from json string', () => { |
||||
let json = `{
|
||||
"keymaps": { "0": {"type": "scroll.home"}}, |
||||
"search": { "default": "google", "engines": { "google": "https://google.com/search?q={}" }}, |
||||
"blacklist": [ "*.slack.com"], |
||||
"properties": { |
||||
"mystr": "value", |
||||
"mynum": 123, |
||||
"mybool": true |
||||
} |
||||
}`;
|
||||
let value = values.valueFromJson(json); |
||||
|
||||
expect(value.keymaps).to.deep.equal({ 0: {type: "scroll.home"}}); |
||||
expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} }); |
||||
expect(value.blacklist).to.deep.equal(["*.slack.com"]); |
||||
expect(value.properties).to.have.property('mystr', 'value'); |
||||
expect(value.properties).to.have.property('mynum', 123); |
||||
expect(value.properties).to.have.property('mybool', true); |
||||
}); |
||||
}); |
||||
|
||||
describe('valueFromForm', () => { |
||||
it('returns value from form', () => { |
||||
let form = { |
||||
keymaps: { |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.home': '0', |
||||
}, |
||||
search: { |
||||
default: 'google', |
||||
engines: [['google', 'https://google.com/search?q={}']], |
||||
}, |
||||
blacklist: ['*.slack.com'], |
||||
"properties": { |
||||
"mystr": "value", |
||||
"mynum": 123, |
||||
"mybool": true, |
||||
} |
||||
}; |
||||
let value = values.valueFromForm(form); |
||||
|
||||
expect(value.keymaps).to.have.deep.property('j', { type: "scroll.vertically", count: 1 }); |
||||
expect(value.keymaps).to.have.deep.property('0', { type: "scroll.home" }); |
||||
expect(JSON.stringify(value.search)).to.deep.equal(JSON.stringify({ default: "google", engines: { google: "https://google.com/search?q={}"} })); |
||||
expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} }); |
||||
expect(value.blacklist).to.deep.equal(["*.slack.com"]); |
||||
expect(value.properties).to.have.property('mystr', 'value'); |
||||
expect(value.properties).to.have.property('mynum', 123); |
||||
expect(value.properties).to.have.property('mybool', true); |
||||
}); |
||||
|
||||
it('convert from empty form', () => { |
||||
let form = {}; |
||||
let value = values.valueFromForm(form); |
||||
expect(value).to.not.have.key('keymaps'); |
||||
expect(value).to.not.have.key('search'); |
||||
expect(value).to.not.have.key('blacklist'); |
||||
expect(value).to.not.have.key('properties'); |
||||
}); |
||||
|
||||
it('override keymaps', () => { |
||||
let form = { |
||||
keymaps: { |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.vertically?{"count":-1}': 'j', |
||||
} |
||||
}; |
||||
let value = values.valueFromForm(form); |
||||
|
||||
expect(value.keymaps).to.have.key('j'); |
||||
}); |
||||
|
||||
it('override search engine', () => { |
||||
let form = { |
||||
search: { |
||||
default: 'google', |
||||
engines: [ |
||||
['google', 'https://google.com/search?q={}'], |
||||
['google', 'https://google.co.jp/search?q={}'], |
||||
] |
||||
} |
||||
}; |
||||
let value = values.valueFromForm(form); |
||||
|
||||
expect(value.search.engines).to.have.property('google', 'https://google.co.jp/search?q={}'); |
||||
}); |
||||
}); |
||||
|
||||
describe('jsonFromValue', () => { |
||||
}); |
||||
|
||||
describe('formFromValue', () => { |
||||
it('convert empty value to form', () => { |
||||
let value = {}; |
||||
let form = values.formFromValue(value); |
||||
|
||||
expect(value).to.not.have.key('keymaps'); |
||||
expect(value).to.not.have.key('search'); |
||||
expect(value).to.not.have.key('blacklist'); |
||||
}); |
||||
|
||||
it('convert value to form', () => { |
||||
let value = { |
||||
keymaps: { |
||||
j: { type: 'scroll.vertically', count: 1 }, |
||||
JJ: { type: 'scroll.vertically', count: 100 }, |
||||
0: { type: 'scroll.home' }, |
||||
}, |
||||
search: { default: 'google', engines: { google: 'https://google.com/search?q={}' }}, |
||||
blacklist: [ '*.slack.com'], |
||||
properties: { |
||||
"mystr": "value", |
||||
"mynum": 123, |
||||
"mybool": true, |
||||
} |
||||
}; |
||||
let allowed = ['scroll.vertically?{"count":1}', 'scroll.home' ]; |
||||
let form = values.formFromValue(value, allowed); |
||||
|
||||
expect(form.keymaps).to.have.property('scroll.vertically?{"count":1}', 'j'); |
||||
expect(form.keymaps).to.not.have.property('scroll.vertically?{"count":100}'); |
||||
expect(form.keymaps).to.have.property('scroll.home', '0'); |
||||
expect(Object.keys(form.keymaps)).to.have.lengthOf(2); |
||||
expect(form.search).to.have.property('default', 'google'); |
||||
expect(form.search).to.have.deep.property('engines', [['google', 'https://google.com/search?q={}']]); |
||||
expect(form.blacklist).to.have.lengthOf(1); |
||||
expect(form.blacklist).to.include('*.slack.com'); |
||||
expect(form.properties).to.have.property('mystr', 'value'); |
||||
expect(form.properties).to.have.property('mynum', 123); |
||||
expect(form.properties).to.have.property('mybool', true); |
||||
}); |
||||
}); |
||||
}); |
Reference in new issue