Merge pull request #654 from ueokande/settings-as-a-class
Refactor settings on shared logics
This commit is contained in:
commit
8eddcc1785
67 changed files with 1314 additions and 1287 deletions
|
@ -1,7 +1,7 @@
|
||||||
import { injectable } from 'tsyringe';
|
import { injectable } from 'tsyringe';
|
||||||
import SettingUseCase from '../usecases/SettingUseCase';
|
import SettingUseCase from '../usecases/SettingUseCase';
|
||||||
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
||||||
import Settings from '../../shared/Settings';
|
import Settings from '../../shared/settings/Settings';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class SettingController {
|
export default class SettingController {
|
||||||
|
|
|
@ -101,8 +101,8 @@ export default class ContentMessageListener {
|
||||||
return this.commandController.exec(text);
|
return this.commandController.exec(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSettingsQuery(): Promise<any> {
|
async onSettingsQuery(): Promise<any> {
|
||||||
return this.settingController.getSetting();
|
return (await this.settingController.getSetting()).toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFindGetKeyword(): Promise<string> {
|
onFindGetKeyword(): Promise<string> {
|
||||||
|
|
|
@ -8,7 +8,7 @@ export default class SettingRepository {
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return SettingData.valueOf(settings as any);
|
return SettingData.fromJSON(settings as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { injectable } from 'tsyringe';
|
import { injectable } from 'tsyringe';
|
||||||
import MemoryStorage from '../infrastructures/MemoryStorage';
|
import MemoryStorage from '../infrastructures/MemoryStorage';
|
||||||
import Settings from '../../shared/Settings';
|
import Settings from '../../shared/settings/Settings';
|
||||||
import * as PropertyDefs from '../../shared/property-defs';
|
import Properties from '../../shared/settings/Properties';
|
||||||
|
|
||||||
const CACHED_SETTING_KEY = 'setting';
|
const CACHED_SETTING_KEY = 'setting';
|
||||||
|
|
||||||
|
@ -14,17 +14,18 @@ export default class SettingRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
get(): Promise<Settings> {
|
get(): Promise<Settings> {
|
||||||
return Promise.resolve(this.cache.get(CACHED_SETTING_KEY));
|
let data = this.cache.get(CACHED_SETTING_KEY);
|
||||||
|
return Promise.resolve(Settings.fromJSON(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
update(value: Settings): void {
|
update(value: Settings): void {
|
||||||
return this.cache.set(CACHED_SETTING_KEY, value);
|
return this.cache.set(CACHED_SETTING_KEY, value.toJSON());
|
||||||
}
|
}
|
||||||
|
|
||||||
async setProperty(
|
async setProperty(
|
||||||
name: string, value: string | number | boolean,
|
name: string, value: string | number | boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let def = PropertyDefs.defs.find(d => name === d.name);
|
let def = Properties.def(name);
|
||||||
if (!def) {
|
if (!def) {
|
||||||
throw new Error('unknown property: ' + name);
|
throw new Error('unknown property: ' + name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import CompletionsRepository from '../repositories/CompletionsRepository';
|
||||||
import * as filters from './filters';
|
import * as filters from './filters';
|
||||||
import SettingRepository from '../repositories/SettingRepository';
|
import SettingRepository from '../repositories/SettingRepository';
|
||||||
import TabPresenter from '../presenters/TabPresenter';
|
import TabPresenter from '../presenters/TabPresenter';
|
||||||
import * as PropertyDefs from '../../shared/property-defs';
|
import Properties from '../../shared/settings/Properties';
|
||||||
|
|
||||||
const COMPLETION_ITEM_LIMIT = 10;
|
const COMPLETION_ITEM_LIMIT = 10;
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ export default class CompletionsUseCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
querySet(name: string, keywords: string): Promise<CompletionGroup[]> {
|
querySet(name: string, keywords: string): Promise<CompletionGroup[]> {
|
||||||
let items = PropertyDefs.defs.map((def) => {
|
let items = Properties.defs().map((def) => {
|
||||||
if (def.type === 'boolean') {
|
if (def.type === 'boolean') {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PersistentSettingRepository
|
||||||
from '../repositories/PersistentSettingRepository';
|
from '../repositories/PersistentSettingRepository';
|
||||||
import SettingRepository from '../repositories/SettingRepository';
|
import SettingRepository from '../repositories/SettingRepository';
|
||||||
import { DefaultSettingData } from '../../shared/SettingData';
|
import { DefaultSettingData } from '../../shared/SettingData';
|
||||||
import Settings from '../../shared/Settings';
|
import Settings from '../../shared/settings/Settings';
|
||||||
import NotifyPresenter from '../presenters/NotifyPresenter';
|
import NotifyPresenter from '../presenters/NotifyPresenter';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as PropertyDefs from '../../shared//property-defs';
|
import Properties from '../../shared/settings/Properties';
|
||||||
|
|
||||||
const mustNumber = (v: any): number => {
|
const mustNumber = (v: any): number => {
|
||||||
let num = Number(v);
|
let num = Number(v);
|
||||||
|
@ -16,7 +16,7 @@ const parseSetOption = (
|
||||||
value = !key.startsWith('no');
|
value = !key.startsWith('no');
|
||||||
key = value ? key : key.slice(2);
|
key = value ? key : key.slice(2);
|
||||||
}
|
}
|
||||||
let def = PropertyDefs.defs.find(d => d.name === key);
|
let def = Properties.def(key);
|
||||||
if (!def) {
|
if (!def) {
|
||||||
throw new Error('Unknown property: ' + key);
|
throw new Error('Unknown property: ' + key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as dom from '../shared/utils/dom';
|
import * as dom from '../shared/utils/dom';
|
||||||
import Key, * as keys from './domains/Key';
|
import Key from '../shared/settings/Key';
|
||||||
|
|
||||||
const cancelKey = (e: KeyboardEvent): boolean => {
|
const cancelKey = (e: KeyboardEvent): boolean => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
@ -11,6 +11,38 @@ const cancelKey = (e: KeyboardEvent): boolean => {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const modifiedKeyName = (name: string): string => {
|
||||||
|
if (name === ' ') {
|
||||||
|
return 'Space';
|
||||||
|
}
|
||||||
|
if (name.length === 1) {
|
||||||
|
return name;
|
||||||
|
} else if (name === 'Escape') {
|
||||||
|
return 'Esc';
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
|
export const keyFromKeyboardEvent = (e: KeyboardEvent): Key => {
|
||||||
|
let key = modifiedKeyName(e.key);
|
||||||
|
let shift = e.shiftKey;
|
||||||
|
if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) {
|
||||||
|
// make shift false for symbols to enable key bindings by symbold keys.
|
||||||
|
// But this limits key bindings by symbol keys with Shift
|
||||||
|
// (such as Shift+$>.
|
||||||
|
shift = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Key({
|
||||||
|
key: modifiedKeyName(e.key),
|
||||||
|
shift: shift,
|
||||||
|
ctrl: e.ctrlKey,
|
||||||
|
alt: e.altKey,
|
||||||
|
meta: e.metaKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default class InputDriver {
|
export default class InputDriver {
|
||||||
private pressed: {[key: string]: string} = {};
|
private pressed: {[key: string]: string} = {};
|
||||||
|
|
||||||
|
@ -66,7 +98,7 @@ export default class InputDriver {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = keys.fromKeyboardEvent(e);
|
let key = keyFromKeyboardEvent(e);
|
||||||
for (let listener of this.onKeyListeners) {
|
for (let listener of this.onKeyListeners) {
|
||||||
let stop = listener(key);
|
let stop = listener(key);
|
||||||
if (stop) {
|
if (stop) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
import Key from '../domains/Key';
|
import Key from '../../shared/settings/Key';
|
||||||
|
|
||||||
export default interface FollowMasterClient {
|
export default interface FollowMasterClient {
|
||||||
startFollow(newTab: boolean, background: boolean): void;
|
startFollow(newTab: boolean, background: boolean): void;
|
||||||
|
@ -35,7 +35,7 @@ export class FollowMasterClientImpl implements FollowMasterClient {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
type: messages.FOLLOW_KEY_PRESS,
|
type: messages.FOLLOW_KEY_PRESS,
|
||||||
key: key.key,
|
key: key.key,
|
||||||
ctrlKey: key.ctrlKey || false,
|
ctrlKey: key.ctrl || false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Settings from '../../shared/Settings';
|
import Settings from '../../shared/settings/Settings';
|
||||||
import * as messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
export default interface SettingClient {
|
export default interface SettingClient {
|
||||||
|
@ -10,6 +10,6 @@ export class SettingClientImpl {
|
||||||
let settings = await browser.runtime.sendMessage({
|
let settings = await browser.runtime.sendMessage({
|
||||||
type: messages.SETTINGS_QUERY,
|
type: messages.SETTINGS_QUERY,
|
||||||
});
|
});
|
||||||
return settings as Settings;
|
return Settings.fromJSON(settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { injectable } from 'tsyringe';
|
import { injectable } from 'tsyringe';
|
||||||
import FollowSlaveUseCase from '../usecases/FollowSlaveUseCase';
|
import FollowSlaveUseCase from '../usecases/FollowSlaveUseCase';
|
||||||
import Key from '../domains/Key';
|
import Key from '../../shared/settings/Key';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class FollowKeyController {
|
export default class FollowKeyController {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import ClipboardUseCase from '../usecases/ClipboardUseCase';
|
||||||
import OperationClient from '../client/OperationClient';
|
import OperationClient from '../client/OperationClient';
|
||||||
import MarkKeyyUseCase from '../usecases/MarkKeyUseCase';
|
import MarkKeyyUseCase from '../usecases/MarkKeyUseCase';
|
||||||
import FollowMasterClient from '../client/FollowMasterClient';
|
import FollowMasterClient from '../client/FollowMasterClient';
|
||||||
import Key from '../domains/Key';
|
import Key from '../../shared/settings/Key';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class KeymapController {
|
export default class KeymapController {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { injectable } from 'tsyringe';
|
import { injectable } from 'tsyringe';
|
||||||
import MarkUseCase from '../usecases/MarkUseCase';
|
import MarkUseCase from '../usecases/MarkUseCase';
|
||||||
import MarkKeyyUseCase from '../usecases/MarkKeyUseCase';
|
import MarkKeyyUseCase from '../usecases/MarkKeyUseCase';
|
||||||
import Key from '../domains/Key';
|
import Key from '../../shared/settings/Key';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class MarkKeyController {
|
export default class MarkKeyController {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { injectable } from 'tsyringe';
|
import { injectable } from 'tsyringe';
|
||||||
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
|
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
|
||||||
import SettingUseCase from '../usecases/SettingUseCase';
|
import SettingUseCase from '../usecases/SettingUseCase';
|
||||||
import * as blacklists from '../../shared/blacklists';
|
|
||||||
|
|
||||||
import * as messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
|
@ -17,9 +15,7 @@ export default class SettingController {
|
||||||
async initSettings(): Promise<void> {
|
async initSettings(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let current = await this.settingUseCase.reload();
|
let current = await this.settingUseCase.reload();
|
||||||
let disabled = blacklists.includes(
|
let disabled = current.blacklist.includes(window.location.href);
|
||||||
current.blacklist, window.location.href,
|
|
||||||
);
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
this.addonEnabledUseCase.disable();
|
this.addonEnabledUseCase.disable();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
export default interface Key {
|
|
||||||
key: string;
|
|
||||||
shiftKey?: boolean;
|
|
||||||
ctrlKey?: boolean;
|
|
||||||
altKey?: boolean;
|
|
||||||
metaKey?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const modifiedKeyName = (name: string): string => {
|
|
||||||
if (name === ' ') {
|
|
||||||
return 'Space';
|
|
||||||
}
|
|
||||||
if (name.length === 1) {
|
|
||||||
return name;
|
|
||||||
} else if (name === 'Escape') {
|
|
||||||
return 'Esc';
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fromKeyboardEvent = (e: KeyboardEvent): Key => {
|
|
||||||
let key = modifiedKeyName(e.key);
|
|
||||||
let shift = e.shiftKey;
|
|
||||||
if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) {
|
|
||||||
// make shift false for symbols to enable key bindings by symbold keys.
|
|
||||||
// But this limits key bindings by symbol keys with Shift (such as Shift+$>.
|
|
||||||
shift = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: modifiedKeyName(e.key),
|
|
||||||
shiftKey: shift,
|
|
||||||
ctrlKey: e.ctrlKey,
|
|
||||||
altKey: e.altKey,
|
|
||||||
metaKey: e.metaKey,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fromMapKey = (key: string): Key => {
|
|
||||||
if (key.startsWith('<') && key.endsWith('>')) {
|
|
||||||
let inner = key.slice(1, -1);
|
|
||||||
let shift = inner.includes('S-');
|
|
||||||
let base = inner.slice(inner.lastIndexOf('-') + 1);
|
|
||||||
if (shift && base.length === 1) {
|
|
||||||
base = base.toUpperCase();
|
|
||||||
} else if (!shift && base.length === 1) {
|
|
||||||
base = base.toLowerCase();
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
key: base,
|
|
||||||
shiftKey: inner.includes('S-'),
|
|
||||||
ctrlKey: inner.includes('C-'),
|
|
||||||
altKey: inner.includes('A-'),
|
|
||||||
metaKey: inner.includes('M-'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
key: key,
|
|
||||||
shiftKey: key.toLowerCase() !== key,
|
|
||||||
ctrlKey: false,
|
|
||||||
altKey: false,
|
|
||||||
metaKey: false,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const equals = (e1: Key, e2: Key): boolean => {
|
|
||||||
return e1.key === e2.key &&
|
|
||||||
e1.ctrlKey === e2.ctrlKey &&
|
|
||||||
e1.metaKey === e2.metaKey &&
|
|
||||||
e1.altKey === e2.altKey &&
|
|
||||||
e1.shiftKey === e2.shiftKey;
|
|
||||||
};
|
|
|
@ -1,64 +0,0 @@
|
||||||
import Key, * as keyUtils from './Key';
|
|
||||||
|
|
||||||
export default class KeySequence {
|
|
||||||
private keys: Key[];
|
|
||||||
|
|
||||||
private constructor(keys: Key[]) {
|
|
||||||
this.keys = keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
static from(keys: Key[]): KeySequence {
|
|
||||||
return new KeySequence(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
push(key: Key): number {
|
|
||||||
return this.keys.push(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
length(): number {
|
|
||||||
return this.keys.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
startsWith(o: KeySequence): boolean {
|
|
||||||
if (this.keys.length < o.keys.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < o.keys.length; ++i) {
|
|
||||||
if (!keyUtils.equals(this.keys[i], o.keys[i])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
getKeyArray(): Key[] {
|
|
||||||
return this.keys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fromMapKeys = (keys: string): KeySequence => {
|
|
||||||
const fromMapKeysRecursive = (
|
|
||||||
remainings: string, mappedKeys: Key[],
|
|
||||||
): Key[] => {
|
|
||||||
if (remainings.length === 0) {
|
|
||||||
return mappedKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
let nextPos = 1;
|
|
||||||
if (remainings.startsWith('<')) {
|
|
||||||
let ltPos = remainings.indexOf('>');
|
|
||||||
if (ltPos > 0) {
|
|
||||||
nextPos = ltPos + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fromMapKeysRecursive(
|
|
||||||
remainings.slice(nextPos),
|
|
||||||
mappedKeys.concat([keyUtils.fromMapKey(remainings.slice(0, nextPos))])
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = fromMapKeysRecursive(keys, []);
|
|
||||||
return KeySequence.from(data);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Key from '../domains/Key';
|
import Key from '../../shared/settings/Key';
|
||||||
import KeySequence from '../domains/KeySequence';
|
import KeySequence from '../../shared/settings/KeySequence';
|
||||||
|
|
||||||
export default interface KeymapRepository {
|
export default interface KeymapRepository {
|
||||||
enqueueKey(key: Key): KeySequence;
|
enqueueKey(key: Key): KeySequence;
|
||||||
|
@ -7,7 +7,7 @@ export default interface KeymapRepository {
|
||||||
clear(): void;
|
clear(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let current: KeySequence = KeySequence.from([]);
|
let current: KeySequence = new KeySequence([]);
|
||||||
|
|
||||||
export class KeymapRepositoryImpl {
|
export class KeymapRepositoryImpl {
|
||||||
|
|
||||||
|
@ -17,6 +17,6 @@ export class KeymapRepositoryImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
current = KeySequence.from([]);
|
current = new KeySequence([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Settings, { DefaultSetting } from '../../shared/Settings';
|
import Settings, { DefaultSetting } from '../../shared/settings/Settings';
|
||||||
|
|
||||||
let current: Settings = DefaultSetting;
|
let current: Settings = DefaultSetting;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import FollowPresenter from '../presenters/FollowPresenter';
|
||||||
import TabsClient from '../client/TabsClient';
|
import TabsClient from '../client/TabsClient';
|
||||||
import FollowMasterClient from '../client/FollowMasterClient';
|
import FollowMasterClient from '../client/FollowMasterClient';
|
||||||
import { LinkHint, InputHint } from '../presenters/Hint';
|
import { LinkHint, InputHint } from '../presenters/Hint';
|
||||||
import Key from '../domains/Key';
|
import Key from '../../shared/settings/Key';
|
||||||
|
|
||||||
interface Size {
|
interface Size {
|
||||||
width: number;
|
width: number;
|
||||||
|
|
|
@ -3,16 +3,16 @@ import KeymapRepository from '../repositories/KeymapRepository';
|
||||||
import SettingRepository from '../repositories/SettingRepository';
|
import SettingRepository from '../repositories/SettingRepository';
|
||||||
import AddonEnabledRepository from '../repositories/AddonEnabledRepository';
|
import AddonEnabledRepository from '../repositories/AddonEnabledRepository';
|
||||||
import * as operations from '../../shared/operations';
|
import * as operations from '../../shared/operations';
|
||||||
import { Keymaps } from '../../shared/Settings';
|
import Keymaps from '../../shared/settings/Keymaps';
|
||||||
import Key from '../domains/Key';
|
import Key from '../../shared/settings/Key';
|
||||||
import KeySequence, * as keySequenceUtils from '../domains/KeySequence';
|
import KeySequence from '../../shared/settings/KeySequence';
|
||||||
|
|
||||||
type KeymapEntityMap = Map<KeySequence, operations.Operation>;
|
type KeymapEntityMap = Map<KeySequence, operations.Operation>;
|
||||||
|
|
||||||
const reservedKeymaps: Keymaps = {
|
const reservedKeymaps = Keymaps.fromJSON({
|
||||||
'<Esc>': { type: operations.CANCEL },
|
'<Esc>': { type: operations.CANCEL },
|
||||||
'<C-[>': { type: operations.CANCEL },
|
'<C-[>': { type: operations.CANCEL },
|
||||||
};
|
});
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class KeymapUseCase {
|
export default class KeymapUseCase {
|
||||||
|
@ -65,16 +65,10 @@ export default class KeymapUseCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private keymapEntityMap(): KeymapEntityMap {
|
private keymapEntityMap(): KeymapEntityMap {
|
||||||
let keymaps = {
|
let keymaps = this.settingRepository.get().keymaps.combine(reservedKeymaps);
|
||||||
...this.settingRepository.get().keymaps,
|
let entries = keymaps.entries().map(
|
||||||
...reservedKeymaps,
|
([keys, op]) => [KeySequence.fromMapKeys(keys), op]
|
||||||
};
|
) as [KeySequence, operations.Operation][];
|
||||||
let entries = Object.entries(keymaps).map((entry) => {
|
|
||||||
return [
|
|
||||||
keySequenceUtils.fromMapKeys(entry[0]),
|
|
||||||
entry[1],
|
|
||||||
];
|
|
||||||
}) as [KeySequence, operations.Operation][];
|
|
||||||
return new Map<KeySequence, operations.Operation>(entries);
|
return new Map<KeySequence, operations.Operation>(entries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { injectable, inject } from 'tsyringe';
|
import { injectable, inject } from 'tsyringe';
|
||||||
import SettingRepository from '../repositories/SettingRepository';
|
import SettingRepository from '../repositories/SettingRepository';
|
||||||
import SettingClient from '../client/SettingClient';
|
import SettingClient from '../client/SettingClient';
|
||||||
import Settings from '../../shared/Settings';
|
import Settings from '../../shared/settings/Settings';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class SettingUseCase {
|
export default class SettingUseCase {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
JSONSettings, FormSettings, SettingSource,
|
JSONTextSettings, FormSettings, SettingSource,
|
||||||
} from '../../shared/SettingData';
|
} from '../../shared/SettingData';
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
|
@ -11,14 +11,14 @@ export const SETTING_SWITCH_TO_JSON = 'setting.switch.to.json';
|
||||||
interface SettingSetSettingsAcion {
|
interface SettingSetSettingsAcion {
|
||||||
type: typeof SETTING_SET_SETTINGS;
|
type: typeof SETTING_SET_SETTINGS;
|
||||||
source: SettingSource;
|
source: SettingSource;
|
||||||
json?: JSONSettings;
|
json?: JSONTextSettings;
|
||||||
form?: FormSettings;
|
form?: FormSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SettingShowErrorAction {
|
interface SettingShowErrorAction {
|
||||||
type: typeof SETTING_SHOW_ERROR;
|
type: typeof SETTING_SHOW_ERROR;
|
||||||
error: string;
|
error: string;
|
||||||
json: JSONSettings;
|
json: JSONTextSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SettingSwitchToFormAction {
|
interface SettingSwitchToFormAction {
|
||||||
|
@ -28,7 +28,7 @@ interface SettingSwitchToFormAction {
|
||||||
|
|
||||||
interface SettingSwitchToJsonAction {
|
interface SettingSwitchToJsonAction {
|
||||||
type: typeof SETTING_SWITCH_TO_JSON;
|
type: typeof SETTING_SWITCH_TO_JSON;
|
||||||
json: JSONSettings,
|
json: JSONTextSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SettingAction =
|
export type SettingAction =
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as actions from './index';
|
import * as actions from './index';
|
||||||
import * as storages from '../storage';
|
import * as storages from '../storage';
|
||||||
import SettingData, {
|
import SettingData, {
|
||||||
JSONSettings, FormSettings, SettingSource,
|
JSONTextSettings, FormSettings, SettingSource,
|
||||||
} from '../../shared/SettingData';
|
} from '../../shared/SettingData';
|
||||||
|
|
||||||
const load = async(): Promise<actions.SettingAction> => {
|
const load = async(): Promise<actions.SettingAction> => {
|
||||||
|
@ -26,7 +26,7 @@ const save = async(data: SettingData): Promise<actions.SettingAction> => {
|
||||||
return set(data);
|
return set(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const switchToForm = (json: JSONSettings): actions.SettingAction => {
|
const switchToForm = (json: JSONTextSettings): actions.SettingAction => {
|
||||||
try {
|
try {
|
||||||
// toSettings exercise validation
|
// toSettings exercise validation
|
||||||
let form = FormSettings.fromSettings(json.toSettings());
|
let form = FormSettings.fromSettings(json.toSettings());
|
||||||
|
@ -44,7 +44,7 @@ const switchToForm = (json: JSONSettings): actions.SettingAction => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const switchToJson = (form: FormSettings): actions.SettingAction => {
|
const switchToJson = (form: FormSettings): actions.SettingAction => {
|
||||||
let json = JSONSettings.fromSettings(form.toSettings());
|
let json = JSONTextSettings.fromSettings(form.toSettings());
|
||||||
return {
|
return {
|
||||||
type: actions.SETTING_SWITCH_TO_JSON,
|
type: actions.SETTING_SWITCH_TO_JSON,
|
||||||
json,
|
json,
|
||||||
|
|
|
@ -2,10 +2,11 @@ import './BlacklistForm.scss';
|
||||||
import AddButton from '../ui/AddButton';
|
import AddButton from '../ui/AddButton';
|
||||||
import DeleteButton from '../ui/DeleteButton';
|
import DeleteButton from '../ui/DeleteButton';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { BlacklistJSON } from '../../../shared/settings/Blacklist';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string[];
|
value: BlacklistJSON;
|
||||||
onChange: (value: string[]) => void;
|
onChange: (value: BlacklistJSON) => void;
|
||||||
onBlur: () => void;
|
onBlur: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,19 +20,24 @@ class BlacklistForm extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
return <div className='form-blacklist-form'>
|
return <div className='form-blacklist-form'>
|
||||||
{
|
{
|
||||||
this.props.value.map((url, index) => {
|
this.props.value
|
||||||
return <div key={index} className='form-blacklist-form-row'>
|
.map((item, index) => {
|
||||||
<input data-index={index} type='text' name='url'
|
if (typeof item !== 'string') {
|
||||||
className='column-url' value={url}
|
// TODO support partial blacklist;
|
||||||
onChange={this.bindValue.bind(this)}
|
return null;
|
||||||
onBlur={this.props.onBlur}
|
}
|
||||||
/>
|
return <div key={index} className='form-blacklist-form-row'>
|
||||||
<DeleteButton data-index={index} name='delete'
|
<input data-index={index} type='text' name='url'
|
||||||
onClick={this.bindValue.bind(this)}
|
className='column-url' value={item}
|
||||||
onBlur={this.props.onBlur}
|
onChange={this.bindValue.bind(this)}
|
||||||
/>
|
onBlur={this.props.onBlur}
|
||||||
</div>;
|
/>
|
||||||
})
|
<DeleteButton data-index={index} name='delete'
|
||||||
|
onClick={this.bindValue.bind(this)}
|
||||||
|
onBlur={this.props.onBlur}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
<AddButton name='add' style={{ float: 'right' }}
|
<AddButton name='add' style={{ float: 'right' }}
|
||||||
onClick={this.bindValue.bind(this)} />
|
onClick={this.bindValue.bind(this)} />
|
||||||
|
@ -41,7 +47,7 @@ class BlacklistForm extends React.Component<Props> {
|
||||||
bindValue(e: any) {
|
bindValue(e: any) {
|
||||||
let name = e.target.name;
|
let name = e.target.name;
|
||||||
let index = e.target.getAttribute('data-index');
|
let index = e.target.getAttribute('data-index');
|
||||||
let next = this.props.value ? this.props.value.slice() : [];
|
let next = this.props.value.slice();
|
||||||
|
|
||||||
if (name === 'url') {
|
if (name === 'url') {
|
||||||
next[index] = e.target.value;
|
next[index] = e.target.value;
|
||||||
|
|
|
@ -12,7 +12,7 @@ interface Props {
|
||||||
|
|
||||||
class KeymapsForm extends React.Component<Props> {
|
class KeymapsForm extends React.Component<Props> {
|
||||||
public static defaultProps: Props = {
|
public static defaultProps: Props = {
|
||||||
value: FormKeymaps.valueOf({}),
|
value: FormKeymaps.fromJSON({}),
|
||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
onBlur: () => {},
|
onBlur: () => {},
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,7 @@ class PropertiesForm extends React.Component<Props> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let types = this.props.types;
|
let types = this.props.types;
|
||||||
let value = this.props.value;
|
let values = this.props.value;
|
||||||
|
|
||||||
return <div className='form-properties-form'>
|
return <div className='form-properties-form'>
|
||||||
{
|
{
|
||||||
|
@ -46,10 +46,10 @@ class PropertiesForm extends React.Component<Props> {
|
||||||
<span className='column-name'>{name}</span>
|
<span className='column-name'>{name}</span>
|
||||||
<input type={inputType} name={name}
|
<input type={inputType} name={name}
|
||||||
className='column-input'
|
className='column-input'
|
||||||
value={value[name] ? value[name] : ''}
|
value={values[name] ? values[name] : ''}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onBlur={this.props.onBlur}
|
onBlur={this.props.onBlur}
|
||||||
checked={value[name]}
|
checked={values[name]}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -12,7 +12,7 @@ interface Props {
|
||||||
|
|
||||||
class SearchForm extends React.Component<Props> {
|
class SearchForm extends React.Component<Props> {
|
||||||
public static defaultProps: Props = {
|
public static defaultProps: Props = {
|
||||||
value: FormSearch.valueOf({ default: '', engines: []}),
|
value: FormSearch.fromJSON({ default: '', engines: []}),
|
||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
onBlur: () => {},
|
onBlur: () => {},
|
||||||
};
|
};
|
||||||
|
@ -81,7 +81,7 @@ class SearchForm extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onChange(FormSearch.valueOf(next));
|
this.props.onChange(FormSearch.fromJSON(next));
|
||||||
if (name === 'delete' || name === 'default') {
|
if (name === 'delete' || name === 'default') {
|
||||||
this.props.onBlur();
|
this.props.onBlur();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,11 @@ import BlacklistForm from './form/BlacklistForm';
|
||||||
import PropertiesForm from './form/PropertiesForm';
|
import PropertiesForm from './form/PropertiesForm';
|
||||||
import * as settingActions from '../../settings/actions/setting';
|
import * as settingActions from '../../settings/actions/setting';
|
||||||
import SettingData, {
|
import SettingData, {
|
||||||
JSONSettings, FormKeymaps, FormSearch, FormSettings,
|
FormKeymaps, FormSearch, FormSettings, JSONTextSettings,
|
||||||
} from '../../shared/SettingData';
|
} from '../../shared/SettingData';
|
||||||
import { State as AppState } from '../reducers/setting';
|
import { State as AppState } from '../reducers/setting';
|
||||||
import * as settings from '../../shared/Settings';
|
import Properties from '../../shared/settings/Properties';
|
||||||
import * as PropertyDefs from '../../shared/property-defs';
|
import Blacklist from '../../shared/settings/Blacklist';
|
||||||
|
|
||||||
const DO_YOU_WANT_TO_CONTINUE =
|
const DO_YOU_WANT_TO_CONTINUE =
|
||||||
'Some settings in JSON can be lost when migrating. ' +
|
'Some settings in JSON can be lost when migrating. ' +
|
||||||
|
@ -32,12 +32,7 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
this.props.dispatch(settingActions.load());
|
this.props.dispatch(settingActions.load());
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFormFields(form: any) {
|
renderFormFields(form: FormSettings) {
|
||||||
let types = PropertyDefs.defs.reduce(
|
|
||||||
(o: {[key: string]: string}, def) => {
|
|
||||||
o[def.name] = def.type;
|
|
||||||
return o;
|
|
||||||
}, {});
|
|
||||||
return <div>
|
return <div>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Keybindings</legend>
|
<legend>Keybindings</legend>
|
||||||
|
@ -58,7 +53,7 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Blacklist</legend>
|
<legend>Blacklist</legend>
|
||||||
<BlacklistForm
|
<BlacklistForm
|
||||||
value={form.blacklist}
|
value={form.blacklist.toJSON()}
|
||||||
onChange={this.bindBlacklistForm.bind(this)}
|
onChange={this.bindBlacklistForm.bind(this)}
|
||||||
onBlur={this.save.bind(this)}
|
onBlur={this.save.bind(this)}
|
||||||
/>
|
/>
|
||||||
|
@ -66,8 +61,8 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Properties</legend>
|
<legend>Properties</legend>
|
||||||
<PropertiesForm
|
<PropertiesForm
|
||||||
types={types}
|
types={Properties.types()}
|
||||||
value={form.properties}
|
value={form.properties.toJSON()}
|
||||||
onChange={this.bindPropertiesForm.bind(this)}
|
onChange={this.bindPropertiesForm.bind(this)}
|
||||||
onBlur={this.save.bind(this)}
|
onBlur={this.save.bind(this)}
|
||||||
/>
|
/>
|
||||||
|
@ -75,7 +70,7 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJsonFields(json: JSONSettings, error: string) {
|
renderJsonFields(json: JSONTextSettings, error: string) {
|
||||||
return <div>
|
return <div>
|
||||||
<Input
|
<Input
|
||||||
type='textarea'
|
type='textarea'
|
||||||
|
@ -85,7 +80,7 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
error={error}
|
error={error}
|
||||||
onValueChange={this.bindJson.bind(this)}
|
onValueChange={this.bindJson.bind(this)}
|
||||||
onBlur={this.save.bind(this)}
|
onBlur={this.save.bind(this)}
|
||||||
value={json.toJSON()}
|
value={json.toJSONText()}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -94,10 +89,9 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
let fields = null;
|
let fields = null;
|
||||||
let disabled = this.props.error.length > 0;
|
let disabled = this.props.error.length > 0;
|
||||||
if (this.props.source === 'form') {
|
if (this.props.source === 'form') {
|
||||||
fields = this.renderFormFields(this.props.form);
|
fields = this.renderFormFields(this.props.form!!);
|
||||||
} else if (this.props.source === 'json') {
|
} else if (this.props.source === 'json') {
|
||||||
fields = this.renderJsonFields(
|
fields = this.renderJsonFields(this.props.json!!, this.props.error);
|
||||||
this.props.json as JSONSettings, this.props.error);
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -139,7 +133,7 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
let data = new SettingData({
|
let data = new SettingData({
|
||||||
source: this.props.source,
|
source: this.props.source,
|
||||||
form: (this.props.form as FormSettings).buildWithSearch(
|
form: (this.props.form as FormSettings).buildWithSearch(
|
||||||
FormSearch.valueOf(value)),
|
FormSearch.fromJSON(value)),
|
||||||
});
|
});
|
||||||
this.props.dispatch(settingActions.set(data));
|
this.props.dispatch(settingActions.set(data));
|
||||||
}
|
}
|
||||||
|
@ -148,7 +142,7 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
let data = new SettingData({
|
let data = new SettingData({
|
||||||
source: this.props.source,
|
source: this.props.source,
|
||||||
form: (this.props.form as FormSettings).buildWithBlacklist(
|
form: (this.props.form as FormSettings).buildWithBlacklist(
|
||||||
settings.blacklistValueOf(value)),
|
Blacklist.fromJSON(value)),
|
||||||
});
|
});
|
||||||
this.props.dispatch(settingActions.set(data));
|
this.props.dispatch(settingActions.set(data));
|
||||||
}
|
}
|
||||||
|
@ -157,7 +151,7 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
let data = new SettingData({
|
let data = new SettingData({
|
||||||
source: this.props.source,
|
source: this.props.source,
|
||||||
form: (this.props.form as FormSettings).buildWithProperties(
|
form: (this.props.form as FormSettings).buildWithProperties(
|
||||||
settings.propertiesValueOf(value)),
|
Properties.fromJSON(value))
|
||||||
});
|
});
|
||||||
this.props.dispatch(settingActions.set(data));
|
this.props.dispatch(settingActions.set(data));
|
||||||
}
|
}
|
||||||
|
@ -165,7 +159,7 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
bindJson(_name: string, value: string) {
|
bindJson(_name: string, value: string) {
|
||||||
let data = new SettingData({
|
let data = new SettingData({
|
||||||
source: this.props.source,
|
source: this.props.source,
|
||||||
json: JSONSettings.valueOf(value),
|
json: JSONTextSettings.fromText(value),
|
||||||
});
|
});
|
||||||
this.props.dispatch(settingActions.set(data));
|
this.props.dispatch(settingActions.set(data));
|
||||||
}
|
}
|
||||||
|
@ -183,7 +177,7 @@ class SettingsComponent extends React.Component<Props> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.props.dispatch(
|
this.props.dispatch(
|
||||||
settingActions.switchToForm(this.props.json as JSONSettings));
|
settingActions.switchToForm(this.props.json as JSONTextSettings));
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import {
|
import {
|
||||||
JSONSettings, FormSettings, SettingSource,
|
JSONTextSettings, FormSettings, SettingSource,
|
||||||
} from '../../shared/SettingData';
|
} from '../../shared/SettingData';
|
||||||
|
import { DefaultSetting } from '../../shared/settings/Settings';
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
source: SettingSource;
|
source: SettingSource;
|
||||||
json?: JSONSettings;
|
json?: JSONTextSettings;
|
||||||
form?: FormSettings;
|
form?: FormSettings;
|
||||||
error: string;
|
error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: State = {
|
const defaultState: State = {
|
||||||
source: SettingSource.JSON,
|
source: SettingSource.JSON,
|
||||||
json: JSONSettings.valueOf(''),
|
json: JSONTextSettings.fromText(''),
|
||||||
|
form: FormSettings.fromSettings(DefaultSetting),
|
||||||
error: '',
|
error: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const load = async(): Promise<SettingData> => {
|
||||||
return DefaultSettingData;
|
return DefaultSettingData;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return SettingData.valueOf(settings as any);
|
return SettingData.fromJSON(settings as any);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('unable to load settings', e);
|
console.error('unable to load settings', e);
|
||||||
return DefaultSettingData;
|
return DefaultSettingData;
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import * as operations from './operations';
|
import * as operations from './operations';
|
||||||
import Settings, * as settings from './Settings';
|
import Settings, { DefaultSettingJSONText } from './settings/Settings';
|
||||||
|
import Keymaps from './settings/Keymaps';
|
||||||
|
import Search from './settings/Search';
|
||||||
|
import Properties from './settings/Properties';
|
||||||
|
import Blacklist from './settings/Blacklist';
|
||||||
|
|
||||||
export class FormKeymaps {
|
export class FormKeymaps {
|
||||||
private data: {[op: string]: string};
|
private readonly data: {[op: string]: string};
|
||||||
|
|
||||||
constructor(data: {[op: string]: string}) {
|
private constructor(data: {[op: string]: string}) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
toKeymaps(): settings.Keymaps {
|
toKeymaps(): Keymaps {
|
||||||
let keymaps: settings.Keymaps = {};
|
let keymaps: { [key: string]: operations.Operation } = {};
|
||||||
for (let name of Object.keys(this.data)) {
|
for (let name of Object.keys(this.data)) {
|
||||||
let [type, argStr] = name.split('?');
|
let [type, argStr] = name.split('?');
|
||||||
let args = {};
|
let args = {};
|
||||||
|
@ -19,7 +23,7 @@ export class FormKeymaps {
|
||||||
let key = this.data[name];
|
let key = this.data[name];
|
||||||
keymaps[key] = operations.valueOf({ type, ...args });
|
keymaps[key] = operations.valueOf({ type, ...args });
|
||||||
}
|
}
|
||||||
return keymaps;
|
return Keymaps.fromJSON(keymaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): {[op: string]: string} {
|
toJSON(): {[op: string]: string} {
|
||||||
|
@ -34,7 +38,7 @@ export class FormKeymaps {
|
||||||
return new FormKeymaps(newData);
|
return new FormKeymaps(newData);
|
||||||
}
|
}
|
||||||
|
|
||||||
static valueOf(o: ReturnType<FormKeymaps['toJSON']>): FormKeymaps {
|
static fromJSON(o: ReturnType<FormKeymaps['toJSON']>): FormKeymaps {
|
||||||
let data: {[op: string]: string} = {};
|
let data: {[op: string]: string} = {};
|
||||||
for (let op of Object.keys(o)) {
|
for (let op of Object.keys(o)) {
|
||||||
data[op] = o[op] as string;
|
data[op] = o[op] as string;
|
||||||
|
@ -42,10 +46,11 @@ export class FormKeymaps {
|
||||||
return new FormKeymaps(data);
|
return new FormKeymaps(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromKeymaps(keymaps: settings.Keymaps): FormKeymaps {
|
static fromKeymaps(keymaps: Keymaps): FormKeymaps {
|
||||||
|
let json = keymaps.toJSON();
|
||||||
let data: {[op: string]: string} = {};
|
let data: {[op: string]: string} = {};
|
||||||
for (let key of Object.keys(keymaps)) {
|
for (let key of Object.keys(json)) {
|
||||||
let op = keymaps[key];
|
let op = json[key];
|
||||||
let args = { ...op };
|
let args = { ...op };
|
||||||
delete args.type;
|
delete args.type;
|
||||||
|
|
||||||
|
@ -60,24 +65,21 @@ export class FormKeymaps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FormSearch {
|
export class FormSearch {
|
||||||
private default: string;
|
private readonly default: string;
|
||||||
|
|
||||||
private engines: string[][];
|
private readonly engines: string[][];
|
||||||
|
|
||||||
constructor(defaultEngine: string, engines: string[][]) {
|
constructor(defaultEngine: string, engines: string[][]) {
|
||||||
this.default = defaultEngine;
|
this.default = defaultEngine;
|
||||||
this.engines = engines;
|
this.engines = engines;
|
||||||
}
|
}
|
||||||
|
|
||||||
toSearchSettings(): settings.Search {
|
toSearchSettings(): Search {
|
||||||
return {
|
let engines: { [name: string]: string } = {};
|
||||||
default: this.default,
|
for (let entry of this.engines) {
|
||||||
engines: this.engines.reduce(
|
engines[entry[0]] = entry[1];
|
||||||
(o: {[key: string]: string}, [name, url]) => {
|
}
|
||||||
o[name] = url;
|
return new Search(this.default, engines);
|
||||||
return o;
|
|
||||||
}, {}),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): {
|
toJSON(): {
|
||||||
|
@ -90,7 +92,7 @@ export class FormSearch {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static valueOf(o: ReturnType<FormSearch['toJSON']>): FormSearch {
|
static fromJSON(o: ReturnType<FormSearch['toJSON']>): FormSearch {
|
||||||
if (!Object.prototype.hasOwnProperty.call(o, 'default')) {
|
if (!Object.prototype.hasOwnProperty.call(o, 'default')) {
|
||||||
throw new TypeError(`"default" field not set`);
|
throw new TypeError(`"default" field not set`);
|
||||||
}
|
}
|
||||||
|
@ -100,53 +102,58 @@ export class FormSearch {
|
||||||
return new FormSearch(o.default, o.engines);
|
return new FormSearch(o.default, o.engines);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromSearch(search: settings.Search): FormSearch {
|
static fromSearch(search: Search): FormSearch {
|
||||||
let engines = Object.entries(search.engines).reduce(
|
let engines = Object.entries(search.engines).reduce(
|
||||||
(o: string[][], [name, url]) => {
|
(o: string[][], [name, url]) => {
|
||||||
return o.concat([[name, url]]);
|
return o.concat([[name, url]]);
|
||||||
}, []);
|
}, []);
|
||||||
return new FormSearch(search.default, engines);
|
return new FormSearch(search.defaultEngine, engines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JSONSettings {
|
export class JSONTextSettings {
|
||||||
private json: string;
|
constructor(
|
||||||
|
private json: string,
|
||||||
constructor(json: any) {
|
) {
|
||||||
this.json = json;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toSettings(): Settings {
|
toSettings(): Settings {
|
||||||
return settings.valueOf(JSON.parse(this.json));
|
return Settings.fromJSON(JSON.parse(this.json));
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): string {
|
toJSONText(): string {
|
||||||
return this.json;
|
return this.json;
|
||||||
}
|
}
|
||||||
|
|
||||||
static valueOf(o: ReturnType<JSONSettings['toJSON']>): JSONSettings {
|
static fromText(o: string): JSONTextSettings {
|
||||||
return new JSONSettings(o);
|
return new JSONTextSettings(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromSettings(data: Settings): JSONSettings {
|
static fromSettings(data: Settings): JSONTextSettings {
|
||||||
return new JSONSettings(JSON.stringify(data, undefined, 2));
|
let json = {
|
||||||
|
keymaps: data.keymaps.toJSON(),
|
||||||
|
search: data.search,
|
||||||
|
properties: data.properties,
|
||||||
|
blacklist: data.blacklist,
|
||||||
|
};
|
||||||
|
return new JSONTextSettings(JSON.stringify(json, undefined, 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FormSettings {
|
export class FormSettings {
|
||||||
private keymaps: FormKeymaps;
|
public readonly keymaps: FormKeymaps;
|
||||||
|
|
||||||
private search: FormSearch;
|
public readonly search: FormSearch;
|
||||||
|
|
||||||
private properties: settings.Properties;
|
public readonly properties: Properties;
|
||||||
|
|
||||||
private blacklist: string[];
|
public readonly blacklist: Blacklist;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
keymaps: FormKeymaps,
|
keymaps: FormKeymaps,
|
||||||
search: FormSearch,
|
search: FormSearch,
|
||||||
properties: settings.Properties,
|
properties: Properties,
|
||||||
blacklist: string[],
|
blacklist: Blacklist,
|
||||||
) {
|
) {
|
||||||
this.keymaps = keymaps;
|
this.keymaps = keymaps;
|
||||||
this.search = search;
|
this.search = search;
|
||||||
|
@ -172,7 +179,7 @@ export class FormSettings {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildWithProperties(props: settings.Properties): FormSettings {
|
buildWithProperties(props: Properties): FormSettings {
|
||||||
return new FormSettings(
|
return new FormSettings(
|
||||||
this.keymaps,
|
this.keymaps,
|
||||||
this.search,
|
this.search,
|
||||||
|
@ -181,7 +188,7 @@ export class FormSettings {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildWithBlacklist(blacklist: string[]): FormSettings {
|
buildWithBlacklist(blacklist: Blacklist): FormSettings {
|
||||||
return new FormSettings(
|
return new FormSettings(
|
||||||
this.keymaps,
|
this.keymaps,
|
||||||
this.search,
|
this.search,
|
||||||
|
@ -191,39 +198,39 @@ export class FormSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
toSettings(): Settings {
|
toSettings(): Settings {
|
||||||
return settings.valueOf({
|
return Settings.fromJSON({
|
||||||
keymaps: this.keymaps.toKeymaps(),
|
keymaps: this.keymaps.toKeymaps().toJSON(),
|
||||||
search: this.search.toSearchSettings(),
|
search: this.search.toSearchSettings().toJSON(),
|
||||||
properties: this.properties,
|
properties: this.properties.toJSON(),
|
||||||
blacklist: this.blacklist,
|
blacklist: this.blacklist.toJSON(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): {
|
toJSON(): {
|
||||||
keymaps: ReturnType<FormKeymaps['toJSON']>;
|
keymaps: ReturnType<FormKeymaps['toJSON']>;
|
||||||
search: ReturnType<FormSearch['toJSON']>;
|
search: ReturnType<FormSearch['toJSON']>;
|
||||||
properties: settings.Properties;
|
properties: ReturnType<Properties['toJSON']>;
|
||||||
blacklist: string[];
|
blacklist: ReturnType<Blacklist['toJSON']>;
|
||||||
} {
|
} {
|
||||||
return {
|
return {
|
||||||
keymaps: this.keymaps.toJSON(),
|
keymaps: this.keymaps.toJSON(),
|
||||||
search: this.search.toJSON(),
|
search: this.search.toJSON(),
|
||||||
properties: this.properties,
|
properties: this.properties.toJSON(),
|
||||||
blacklist: this.blacklist,
|
blacklist: this.blacklist.toJSON(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static valueOf(o: ReturnType<FormSettings['toJSON']>): FormSettings {
|
static fromJSON(o: ReturnType<FormSettings['toJSON']>): FormSettings {
|
||||||
for (let name of ['keymaps', 'search', 'properties', 'blacklist']) {
|
for (let name of ['keymaps', 'search', 'properties', 'blacklist']) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(o, name)) {
|
if (!Object.prototype.hasOwnProperty.call(o, name)) {
|
||||||
throw new Error(`"${name}" field not set`);
|
throw new Error(`"${name}" field not set`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new FormSettings(
|
return new FormSettings(
|
||||||
FormKeymaps.valueOf(o.keymaps),
|
FormKeymaps.fromJSON(o.keymaps),
|
||||||
FormSearch.valueOf(o.search),
|
FormSearch.fromJSON(o.search),
|
||||||
settings.propertiesValueOf(o.properties),
|
Properties.fromJSON(o.properties),
|
||||||
settings.blacklistValueOf(o.blacklist),
|
Blacklist.fromJSON(o.blacklist),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +251,7 @@ export enum SettingSource {
|
||||||
export default class SettingData {
|
export default class SettingData {
|
||||||
private source: SettingSource;
|
private source: SettingSource;
|
||||||
|
|
||||||
private json?: JSONSettings;
|
private json?: JSONTextSettings;
|
||||||
|
|
||||||
private form?: FormSettings;
|
private form?: FormSettings;
|
||||||
|
|
||||||
|
@ -252,7 +259,7 @@ export default class SettingData {
|
||||||
source, json, form
|
source, json, form
|
||||||
}: {
|
}: {
|
||||||
source: SettingSource,
|
source: SettingSource,
|
||||||
json?: JSONSettings,
|
json?: JSONTextSettings,
|
||||||
form?: FormSettings,
|
form?: FormSettings,
|
||||||
}) {
|
}) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
|
@ -264,7 +271,7 @@ export default class SettingData {
|
||||||
return this.source;
|
return this.source;
|
||||||
}
|
}
|
||||||
|
|
||||||
getJSON(): JSONSettings {
|
getJSON(): JSONTextSettings {
|
||||||
if (!this.json) {
|
if (!this.json) {
|
||||||
throw new TypeError('json settings not set');
|
throw new TypeError('json settings not set');
|
||||||
}
|
}
|
||||||
|
@ -283,7 +290,7 @@ export default class SettingData {
|
||||||
case SettingSource.JSON:
|
case SettingSource.JSON:
|
||||||
return {
|
return {
|
||||||
source: this.source,
|
source: this.source,
|
||||||
json: (this.json as JSONSettings).toJSON(),
|
json: (this.json as JSONTextSettings).toJSONText(),
|
||||||
};
|
};
|
||||||
case SettingSource.Form:
|
case SettingSource.Form:
|
||||||
return {
|
return {
|
||||||
|
@ -304,7 +311,7 @@ export default class SettingData {
|
||||||
throw new Error(`unknown settings source: ${this.source}`);
|
throw new Error(`unknown settings source: ${this.source}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static valueOf(o: {
|
static fromJSON(o: {
|
||||||
source: string;
|
source: string;
|
||||||
json?: string;
|
json?: string;
|
||||||
form?: ReturnType<FormSettings['toJSON']>;
|
form?: ReturnType<FormSettings['toJSON']>;
|
||||||
|
@ -313,13 +320,13 @@ export default class SettingData {
|
||||||
case SettingSource.JSON:
|
case SettingSource.JSON:
|
||||||
return new SettingData({
|
return new SettingData({
|
||||||
source: o.source,
|
source: o.source,
|
||||||
json: JSONSettings.valueOf(
|
json: JSONTextSettings.fromText(
|
||||||
o.json as ReturnType<JSONSettings['toJSON']>),
|
o.json as ReturnType<JSONTextSettings['toJSONText']>),
|
||||||
});
|
});
|
||||||
case SettingSource.Form:
|
case SettingSource.Form:
|
||||||
return new SettingData({
|
return new SettingData({
|
||||||
source: o.source,
|
source: o.source,
|
||||||
form: FormSettings.valueOf(
|
form: FormSettings.fromJSON(
|
||||||
o.form as ReturnType<FormSettings['toJSON']>),
|
o.form as ReturnType<FormSettings['toJSON']>),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -327,90 +334,7 @@ export default class SettingData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DefaultSettingData: SettingData = SettingData.valueOf({
|
export const DefaultSettingData: SettingData = SettingData.fromJSON({
|
||||||
source: 'json',
|
source: 'json',
|
||||||
json: `{
|
json: DefaultSettingJSONText,
|
||||||
"keymaps": {
|
|
||||||
"0": { "type": "scroll.home" },
|
|
||||||
":": { "type": "command.show" },
|
|
||||||
"o": { "type": "command.show.open", "alter": false },
|
|
||||||
"O": { "type": "command.show.open", "alter": true },
|
|
||||||
"t": { "type": "command.show.tabopen", "alter": false },
|
|
||||||
"T": { "type": "command.show.tabopen", "alter": true },
|
|
||||||
"w": { "type": "command.show.winopen", "alter": false },
|
|
||||||
"W": { "type": "command.show.winopen", "alter": true },
|
|
||||||
"b": { "type": "command.show.buffer" },
|
|
||||||
"a": { "type": "command.show.addbookmark", "alter": true },
|
|
||||||
"k": { "type": "scroll.vertically", "count": -1 },
|
|
||||||
"j": { "type": "scroll.vertically", "count": 1 },
|
|
||||||
"h": { "type": "scroll.horizonally", "count": -1 },
|
|
||||||
"l": { "type": "scroll.horizonally", "count": 1 },
|
|
||||||
"<C-U>": { "type": "scroll.pages", "count": -0.5 },
|
|
||||||
"<C-D>": { "type": "scroll.pages", "count": 0.5 },
|
|
||||||
"<C-B>": { "type": "scroll.pages", "count": -1 },
|
|
||||||
"<C-F>": { "type": "scroll.pages", "count": 1 },
|
|
||||||
"gg": { "type": "scroll.top" },
|
|
||||||
"G": { "type": "scroll.bottom" },
|
|
||||||
"$": { "type": "scroll.end" },
|
|
||||||
"d": { "type": "tabs.close" },
|
|
||||||
"D": { "type": "tabs.close", "select": "left" },
|
|
||||||
"x$": { "type": "tabs.close.right" },
|
|
||||||
"!d": { "type": "tabs.close.force" },
|
|
||||||
"u": { "type": "tabs.reopen" },
|
|
||||||
"K": { "type": "tabs.prev" },
|
|
||||||
"J": { "type": "tabs.next" },
|
|
||||||
"gT": { "type": "tabs.prev" },
|
|
||||||
"gt": { "type": "tabs.next" },
|
|
||||||
"g0": { "type": "tabs.first" },
|
|
||||||
"g$": { "type": "tabs.last" },
|
|
||||||
"<C-6>": { "type": "tabs.prevsel" },
|
|
||||||
"r": { "type": "tabs.reload", "cache": false },
|
|
||||||
"R": { "type": "tabs.reload", "cache": true },
|
|
||||||
"zp": { "type": "tabs.pin.toggle" },
|
|
||||||
"zd": { "type": "tabs.duplicate" },
|
|
||||||
"zi": { "type": "zoom.in" },
|
|
||||||
"zo": { "type": "zoom.out" },
|
|
||||||
"zz": { "type": "zoom.neutral" },
|
|
||||||
"f": { "type": "follow.start", "newTab": false },
|
|
||||||
"F": { "type": "follow.start", "newTab": true, "background": false },
|
|
||||||
"m": { "type": "mark.set.prefix" },
|
|
||||||
"'": { "type": "mark.jump.prefix" },
|
|
||||||
"H": { "type": "navigate.history.prev" },
|
|
||||||
"L": { "type": "navigate.history.next" },
|
|
||||||
"[[": { "type": "navigate.link.prev" },
|
|
||||||
"]]": { "type": "navigate.link.next" },
|
|
||||||
"gu": { "type": "navigate.parent" },
|
|
||||||
"gU": { "type": "navigate.root" },
|
|
||||||
"gi": { "type": "focus.input" },
|
|
||||||
"gf": { "type": "page.source" },
|
|
||||||
"gh": { "type": "page.home" },
|
|
||||||
"gH": { "type": "page.home", "newTab": true },
|
|
||||||
"y": { "type": "urls.yank" },
|
|
||||||
"p": { "type": "urls.paste", "newTab": false },
|
|
||||||
"P": { "type": "urls.paste", "newTab": true },
|
|
||||||
"/": { "type": "find.start" },
|
|
||||||
"n": { "type": "find.next" },
|
|
||||||
"N": { "type": "find.prev" },
|
|
||||||
".": { "type": "repeat.last" },
|
|
||||||
"<S-Esc>": { "type": "addon.toggle.enabled" }
|
|
||||||
},
|
|
||||||
"search": {
|
|
||||||
"default": "google",
|
|
||||||
"engines": {
|
|
||||||
"google": "https://google.com/search?q={}",
|
|
||||||
"yahoo": "https://search.yahoo.com/search?p={}",
|
|
||||||
"bing": "https://www.bing.com/search?q={}",
|
|
||||||
"duckduckgo": "https://duckduckgo.com/?q={}",
|
|
||||||
"twitter": "https://twitter.com/search?q={}",
|
|
||||||
"wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"hintchars": "abcdefghijklmnopqrstuvwxyz",
|
|
||||||
"smoothscroll": false,
|
|
||||||
"complete": "sbh"
|
|
||||||
},
|
|
||||||
"blacklist": [
|
|
||||||
]
|
|
||||||
}`,
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,200 +0,0 @@
|
||||||
import * as operations from './operations';
|
|
||||||
import * as PropertyDefs from './property-defs';
|
|
||||||
|
|
||||||
export type Keymaps = {[key: string]: operations.Operation};
|
|
||||||
|
|
||||||
export interface Search {
|
|
||||||
default: string;
|
|
||||||
engines: { [key: string]: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Properties {
|
|
||||||
hintchars: string;
|
|
||||||
smoothscroll: boolean;
|
|
||||||
complete: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default interface Settings {
|
|
||||||
keymaps: Keymaps;
|
|
||||||
search: Search;
|
|
||||||
properties: Properties;
|
|
||||||
blacklist: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const keymapsValueOf = (o: any): Keymaps => {
|
|
||||||
return Object.keys(o).reduce((keymaps: Keymaps, key: string): Keymaps => {
|
|
||||||
let op = operations.valueOf(o[key]);
|
|
||||||
keymaps[key] = op;
|
|
||||||
return keymaps;
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const searchValueOf = (o: any): Search => {
|
|
||||||
if (typeof o.default !== 'string') {
|
|
||||||
throw new TypeError('string field "default" not set"');
|
|
||||||
}
|
|
||||||
for (let name of Object.keys(o.engines)) {
|
|
||||||
if ((/\s/).test(name)) {
|
|
||||||
throw new TypeError(
|
|
||||||
`While space in the search engine not allowed: "${name}"`);
|
|
||||||
}
|
|
||||||
let url = o.engines[name];
|
|
||||||
if (typeof url !== 'string') {
|
|
||||||
throw new TypeError('"engines" not an object of string');
|
|
||||||
}
|
|
||||||
let matches = url.match(/{}/g);
|
|
||||||
if (matches === null) {
|
|
||||||
throw new TypeError(`No {}-placeholders in URL of "${name}"`);
|
|
||||||
} else if (matches.length > 1) {
|
|
||||||
throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(o.engines, o.default)) {
|
|
||||||
throw new TypeError(`Default engine "${o.default}" not found`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
default: o.default as string,
|
|
||||||
engines: { ...o.engines },
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const propertiesValueOf = (o: any): Properties => {
|
|
||||||
let defNames = new Set(PropertyDefs.defs.map(def => def.name));
|
|
||||||
let unknownName = Object.keys(o).find(name => !defNames.has(name));
|
|
||||||
if (unknownName) {
|
|
||||||
throw new TypeError(`Unknown property name: "${unknownName}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let def of PropertyDefs.defs) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(o, def.name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (typeof o[def.name] !== def.type) {
|
|
||||||
throw new TypeError(`property "${def.name}" is not ${def.type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...PropertyDefs.defaultValues,
|
|
||||||
...o,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const blacklistValueOf = (o: any): string[] => {
|
|
||||||
if (!Array.isArray(o)) {
|
|
||||||
throw new TypeError(`"blacklist" is not an array of string`);
|
|
||||||
}
|
|
||||||
for (let x of o) {
|
|
||||||
if (typeof x !== 'string') {
|
|
||||||
throw new TypeError(`"blacklist" is not an array of string`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o as string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const valueOf = (o: any): Settings => {
|
|
||||||
let settings = { ...DefaultSetting };
|
|
||||||
for (let key of Object.keys(o)) {
|
|
||||||
switch (key) {
|
|
||||||
case 'keymaps':
|
|
||||||
settings.keymaps = keymapsValueOf(o.keymaps);
|
|
||||||
break;
|
|
||||||
case 'search':
|
|
||||||
settings.search = searchValueOf(o.search);
|
|
||||||
break;
|
|
||||||
case 'properties':
|
|
||||||
settings.properties = propertiesValueOf(o.properties);
|
|
||||||
break;
|
|
||||||
case 'blacklist':
|
|
||||||
settings.blacklist = blacklistValueOf(o.blacklist);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new TypeError('unknown setting: ' + key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return settings;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DefaultSetting: Settings = {
|
|
||||||
keymaps: {
|
|
||||||
'0': { 'type': 'scroll.home' },
|
|
||||||
':': { 'type': 'command.show' },
|
|
||||||
'o': { 'type': 'command.show.open', 'alter': false },
|
|
||||||
'O': { 'type': 'command.show.open', 'alter': true },
|
|
||||||
't': { 'type': 'command.show.tabopen', 'alter': false },
|
|
||||||
'T': { 'type': 'command.show.tabopen', 'alter': true },
|
|
||||||
'w': { 'type': 'command.show.winopen', 'alter': false },
|
|
||||||
'W': { 'type': 'command.show.winopen', 'alter': true },
|
|
||||||
'b': { 'type': 'command.show.buffer' },
|
|
||||||
'a': { 'type': 'command.show.addbookmark', 'alter': true },
|
|
||||||
'k': { 'type': 'scroll.vertically', 'count': -1 },
|
|
||||||
'j': { 'type': 'scroll.vertically', 'count': 1 },
|
|
||||||
'h': { 'type': 'scroll.horizonally', 'count': -1 },
|
|
||||||
'l': { 'type': 'scroll.horizonally', 'count': 1 },
|
|
||||||
'<C-U>': { 'type': 'scroll.pages', 'count': -0.5 },
|
|
||||||
'<C-D>': { 'type': 'scroll.pages', 'count': 0.5 },
|
|
||||||
'<C-B>': { 'type': 'scroll.pages', 'count': -1 },
|
|
||||||
'<C-F>': { 'type': 'scroll.pages', 'count': 1 },
|
|
||||||
'gg': { 'type': 'scroll.top' },
|
|
||||||
'G': { 'type': 'scroll.bottom' },
|
|
||||||
'$': { 'type': 'scroll.end' },
|
|
||||||
'd': { 'type': 'tabs.close' },
|
|
||||||
'D': { 'type': 'tabs.close', 'select': 'left' },
|
|
||||||
'x$': { 'type': 'tabs.close.right' },
|
|
||||||
'!d': { 'type': 'tabs.close.force' },
|
|
||||||
'u': { 'type': 'tabs.reopen' },
|
|
||||||
'K': { 'type': 'tabs.prev' },
|
|
||||||
'J': { 'type': 'tabs.next' },
|
|
||||||
'gT': { 'type': 'tabs.prev' },
|
|
||||||
'gt': { 'type': 'tabs.next' },
|
|
||||||
'g0': { 'type': 'tabs.first' },
|
|
||||||
'g$': { 'type': 'tabs.last' },
|
|
||||||
'<C-6>': { 'type': 'tabs.prevsel' },
|
|
||||||
'r': { 'type': 'tabs.reload', 'cache': false },
|
|
||||||
'R': { 'type': 'tabs.reload', 'cache': true },
|
|
||||||
'zp': { 'type': 'tabs.pin.toggle' },
|
|
||||||
'zd': { 'type': 'tabs.duplicate' },
|
|
||||||
'zi': { 'type': 'zoom.in' },
|
|
||||||
'zo': { 'type': 'zoom.out' },
|
|
||||||
'zz': { 'type': 'zoom.neutral' },
|
|
||||||
'f': { 'type': 'follow.start', 'newTab': false, 'background': false },
|
|
||||||
'F': { 'type': 'follow.start', 'newTab': true, 'background': false },
|
|
||||||
'm': { 'type': 'mark.set.prefix' },
|
|
||||||
'\'': { 'type': 'mark.jump.prefix' },
|
|
||||||
'H': { 'type': 'navigate.history.prev' },
|
|
||||||
'L': { 'type': 'navigate.history.next' },
|
|
||||||
'[[': { 'type': 'navigate.link.prev' },
|
|
||||||
']]': { 'type': 'navigate.link.next' },
|
|
||||||
'gu': { 'type': 'navigate.parent' },
|
|
||||||
'gU': { 'type': 'navigate.root' },
|
|
||||||
'gi': { 'type': 'focus.input' },
|
|
||||||
'gf': { 'type': 'page.source' },
|
|
||||||
'gh': { 'type': 'page.home', 'newTab': false },
|
|
||||||
'gH': { 'type': 'page.home', 'newTab': true },
|
|
||||||
'y': { 'type': 'urls.yank' },
|
|
||||||
'p': { 'type': 'urls.paste', 'newTab': false },
|
|
||||||
'P': { 'type': 'urls.paste', 'newTab': true },
|
|
||||||
'/': { 'type': 'find.start' },
|
|
||||||
'n': { 'type': 'find.next' },
|
|
||||||
'N': { 'type': 'find.prev' },
|
|
||||||
'.': { 'type': 'repeat.last' },
|
|
||||||
'<S-Esc>': { 'type': 'addon.toggle.enabled' }
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
default: 'google',
|
|
||||||
engines: {
|
|
||||||
'google': 'https://google.com/search?q={}',
|
|
||||||
'yahoo': 'https://search.yahoo.com/search?p={}',
|
|
||||||
'bing': 'https://www.bing.com/search?q={}',
|
|
||||||
'duckduckgo': 'https://duckduckgo.com/?q={}',
|
|
||||||
'twitter': 'https://twitter.com/search?q={}',
|
|
||||||
'wikipedia': 'https://en.wikipedia.org/w/index.php?search={}'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
hintchars: 'abcdefghijklmnopqrstuvwxyz',
|
|
||||||
smoothscroll: false,
|
|
||||||
complete: 'sbh'
|
|
||||||
},
|
|
||||||
blacklist: []
|
|
||||||
};
|
|
|
@ -1,13 +0,0 @@
|
||||||
import * as re from './utils/re';
|
|
||||||
|
|
||||||
const includes = (blacklist: string[], url: string): boolean => {
|
|
||||||
let u = new URL(url);
|
|
||||||
return blacklist.some((item) => {
|
|
||||||
if (!item.includes('/')) {
|
|
||||||
return re.fromWildcard(item).test(u.host);
|
|
||||||
}
|
|
||||||
return re.fromWildcard(item).test(u.host + u.pathname);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export { includes };
|
|
|
@ -1,50 +0,0 @@
|
||||||
export type Type = string | number | boolean;
|
|
||||||
|
|
||||||
export class Def {
|
|
||||||
private name0: string;
|
|
||||||
|
|
||||||
private description0: string;
|
|
||||||
|
|
||||||
private defaultValue0: Type;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
name: string,
|
|
||||||
description: string,
|
|
||||||
defaultValue: Type,
|
|
||||||
) {
|
|
||||||
this.name0 = name;
|
|
||||||
this.description0 = description;
|
|
||||||
this.defaultValue0 = defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get name(): string {
|
|
||||||
return this.name0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get defaultValue(): Type {
|
|
||||||
return this.defaultValue0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get description(): Type {
|
|
||||||
return this.description0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get type(): string {
|
|
||||||
return typeof this.defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defs: Def[] = [
|
|
||||||
new Def(
|
|
||||||
'hintchars',
|
|
||||||
'hint characters on follow mode',
|
|
||||||
'abcdefghijklmnopqrstuvwxyz'),
|
|
||||||
new Def(
|
|
||||||
'smoothscroll',
|
|
||||||
'smooth scroll',
|
|
||||||
false),
|
|
||||||
new Def(
|
|
||||||
'complete',
|
|
||||||
'which are completed at the open page',
|
|
||||||
'sbh'),
|
|
||||||
];
|
|
|
@ -1,56 +0,0 @@
|
||||||
export type Type = string | number | boolean;
|
|
||||||
|
|
||||||
export class Def {
|
|
||||||
private name0: string;
|
|
||||||
|
|
||||||
private description0: string;
|
|
||||||
|
|
||||||
private defaultValue0: Type;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
name: string,
|
|
||||||
description: string,
|
|
||||||
defaultValue: Type,
|
|
||||||
) {
|
|
||||||
this.name0 = name;
|
|
||||||
this.description0 = description;
|
|
||||||
this.defaultValue0 = defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get name(): string {
|
|
||||||
return this.name0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get defaultValue(): Type {
|
|
||||||
return this.defaultValue0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get description(): Type {
|
|
||||||
return this.description0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get type(): string {
|
|
||||||
return typeof this.defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defs: Def[] = [
|
|
||||||
new Def(
|
|
||||||
'hintchars',
|
|
||||||
'hint characters on follow mode',
|
|
||||||
'abcdefghijklmnopqrstuvwxyz'),
|
|
||||||
new Def(
|
|
||||||
'smoothscroll',
|
|
||||||
'smooth scroll',
|
|
||||||
false),
|
|
||||||
new Def(
|
|
||||||
'complete',
|
|
||||||
'which are completed at the open page',
|
|
||||||
'sbh'),
|
|
||||||
];
|
|
||||||
|
|
||||||
export const defaultValues = {
|
|
||||||
hintchars: 'abcdefghijklmnopqrstuvwxyz',
|
|
||||||
smoothscroll: false,
|
|
||||||
complete: 'sbh',
|
|
||||||
};
|
|
39
src/shared/settings/Blacklist.ts
Normal file
39
src/shared/settings/Blacklist.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
export type BlacklistJSON = string[];
|
||||||
|
|
||||||
|
const fromWildcard = (pattern: string): RegExp => {
|
||||||
|
let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
|
||||||
|
return new RegExp(regexStr);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Blacklist {
|
||||||
|
constructor(
|
||||||
|
private blacklist: string[],
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: any): Blacklist {
|
||||||
|
if (!Array.isArray(json)) {
|
||||||
|
throw new TypeError(`"blacklist" is not an array of string`);
|
||||||
|
}
|
||||||
|
for (let x of json) {
|
||||||
|
if (typeof x !== 'string') {
|
||||||
|
throw new TypeError(`"blacklist" is not an array of string`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Blacklist(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): BlacklistJSON {
|
||||||
|
return this.blacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
includes(url: string): boolean {
|
||||||
|
let u = new URL(url);
|
||||||
|
return this.blacklist.some((item) => {
|
||||||
|
if (!item.includes('/')) {
|
||||||
|
return fromWildcard(item).test(u.host);
|
||||||
|
}
|
||||||
|
return fromWildcard(item).test(u.host + u.pathname);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
61
src/shared/settings/Key.ts
Normal file
61
src/shared/settings/Key.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
export default class Key {
|
||||||
|
public readonly key: string;
|
||||||
|
|
||||||
|
public readonly shift: boolean;
|
||||||
|
|
||||||
|
public readonly ctrl: boolean;
|
||||||
|
|
||||||
|
public readonly alt: boolean;
|
||||||
|
|
||||||
|
public readonly meta: boolean;
|
||||||
|
|
||||||
|
constructor({ key, shift, ctrl, alt, meta }: {
|
||||||
|
key: string;
|
||||||
|
shift: boolean;
|
||||||
|
ctrl: boolean;
|
||||||
|
alt: boolean;
|
||||||
|
meta: boolean;
|
||||||
|
}) {
|
||||||
|
this.key = key;
|
||||||
|
this.shift = shift;
|
||||||
|
this.ctrl = ctrl;
|
||||||
|
this.alt = alt;
|
||||||
|
this.meta = meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromMapKey(str: string): Key {
|
||||||
|
if (str.startsWith('<') && str.endsWith('>')) {
|
||||||
|
let inner = str.slice(1, -1);
|
||||||
|
let shift = inner.includes('S-');
|
||||||
|
let base = inner.slice(inner.lastIndexOf('-') + 1);
|
||||||
|
if (shift && base.length === 1) {
|
||||||
|
base = base.toUpperCase();
|
||||||
|
} else if (!shift && base.length === 1) {
|
||||||
|
base = base.toLowerCase();
|
||||||
|
}
|
||||||
|
return new Key({
|
||||||
|
key: base,
|
||||||
|
shift: shift,
|
||||||
|
ctrl: inner.includes('C-'),
|
||||||
|
alt: inner.includes('A-'),
|
||||||
|
meta: inner.includes('M-'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Key({
|
||||||
|
key: str,
|
||||||
|
shift: str.toLowerCase() !== str,
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
meta: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(key: Key) {
|
||||||
|
return this.key === key.key &&
|
||||||
|
this.ctrl === key.ctrl &&
|
||||||
|
this.meta === key.meta &&
|
||||||
|
this.alt === key.alt &&
|
||||||
|
this.shift === key.shift;
|
||||||
|
}
|
||||||
|
}
|
54
src/shared/settings/KeySequence.ts
Normal file
54
src/shared/settings/KeySequence.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import Key from '../../shared/settings/Key';
|
||||||
|
|
||||||
|
export default class KeySequence {
|
||||||
|
constructor(
|
||||||
|
public readonly keys: Key[],
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
push(key: Key): number {
|
||||||
|
return this.keys.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
length(): number {
|
||||||
|
return this.keys.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
startsWith(o: KeySequence): boolean {
|
||||||
|
if (this.keys.length < o.keys.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < o.keys.length; ++i) {
|
||||||
|
if (!this.keys[i].equals(o.keys[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromMapKeys(keys: string): KeySequence {
|
||||||
|
const fromMapKeysRecursive = (
|
||||||
|
remaining: string, mappedKeys: Key[],
|
||||||
|
): Key[] => {
|
||||||
|
if (remaining.length === 0) {
|
||||||
|
return mappedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextPos = 1;
|
||||||
|
if (remaining.startsWith('<')) {
|
||||||
|
let ltPos = remaining.indexOf('>');
|
||||||
|
if (ltPos > 0) {
|
||||||
|
nextPos = ltPos + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromMapKeysRecursive(
|
||||||
|
remaining.slice(nextPos),
|
||||||
|
mappedKeys.concat([Key.fromMapKey(remaining.slice(0, nextPos))])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = fromMapKeysRecursive(keys, []);
|
||||||
|
return new KeySequence(data);
|
||||||
|
}
|
||||||
|
}
|
37
src/shared/settings/Keymaps.ts
Normal file
37
src/shared/settings/Keymaps.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import * as operations from '../operations';
|
||||||
|
|
||||||
|
export type KeymapsJSON = { [key: string]: operations.Operation };
|
||||||
|
|
||||||
|
export default class Keymaps {
|
||||||
|
constructor(
|
||||||
|
private readonly data: KeymapsJSON,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: any): Keymaps {
|
||||||
|
if (typeof json !== 'object' || json === null) {
|
||||||
|
throw new TypeError('invalid keymaps type: ' + JSON.stringify(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: KeymapsJSON = {};
|
||||||
|
for (let key of Object.keys(json)) {
|
||||||
|
data[key] = operations.valueOf(json[key]);
|
||||||
|
}
|
||||||
|
return new Keymaps(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
combine(other: Keymaps): Keymaps {
|
||||||
|
return new Keymaps({
|
||||||
|
...this.data,
|
||||||
|
...other.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): KeymapsJSON {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries(): [string, operations.Operation][] {
|
||||||
|
return Object.entries(this.data);
|
||||||
|
}
|
||||||
|
}
|
110
src/shared/settings/Properties.ts
Normal file
110
src/shared/settings/Properties.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
export type PropertiesJSON = {
|
||||||
|
hintchars?: string;
|
||||||
|
smoothscroll?: boolean;
|
||||||
|
complete?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PropertyTypes = {
|
||||||
|
hintchars: string;
|
||||||
|
smoothscroll: string;
|
||||||
|
complete: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PropertyName = 'hintchars' | 'smoothscroll' | 'complete';
|
||||||
|
|
||||||
|
type PropertyDef = {
|
||||||
|
name: PropertyName;
|
||||||
|
description: string;
|
||||||
|
defaultValue: string | number | boolean;
|
||||||
|
type: 'string' | 'number' | 'boolean';
|
||||||
|
};
|
||||||
|
|
||||||
|
const defs: PropertyDef[] = [
|
||||||
|
{
|
||||||
|
name: 'hintchars',
|
||||||
|
description: 'hint characters on follow mode',
|
||||||
|
defaultValue: 'abcdefghijklmnopqrstuvwxyz',
|
||||||
|
type: 'string',
|
||||||
|
}, {
|
||||||
|
name: 'smoothscroll',
|
||||||
|
description: 'smooth scroll',
|
||||||
|
defaultValue: false,
|
||||||
|
type: 'boolean',
|
||||||
|
}, {
|
||||||
|
name: 'complete',
|
||||||
|
description: 'which are completed at the open page',
|
||||||
|
defaultValue: 'sbh',
|
||||||
|
type: 'string',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
hintchars: 'abcdefghijklmnopqrstuvwxyz',
|
||||||
|
smoothscroll: false,
|
||||||
|
complete: 'sbh',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Properties {
|
||||||
|
public hintchars: string;
|
||||||
|
|
||||||
|
public smoothscroll: boolean;
|
||||||
|
|
||||||
|
public complete: string;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
hintchars,
|
||||||
|
smoothscroll,
|
||||||
|
complete,
|
||||||
|
}: {
|
||||||
|
hintchars?: string;
|
||||||
|
smoothscroll?: boolean;
|
||||||
|
complete?: string;
|
||||||
|
} = {}) {
|
||||||
|
this.hintchars = hintchars || defaultValues.hintchars;
|
||||||
|
this.smoothscroll = smoothscroll || defaultValues.smoothscroll;
|
||||||
|
this.complete = complete || defaultValues.complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: any): Properties {
|
||||||
|
let defNames: Set<string> = new Set(defs.map(def => def.name));
|
||||||
|
let unknownName = Object.keys(json).find(name => !defNames.has(name));
|
||||||
|
if (unknownName) {
|
||||||
|
throw new TypeError(`Unknown property name: "${unknownName}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let def of defs) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(json, def.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (typeof json[def.name] !== def.type) {
|
||||||
|
throw new TypeError(
|
||||||
|
`property "${def.name}" is not ${def.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Properties(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
static types(): PropertyTypes {
|
||||||
|
return {
|
||||||
|
hintchars: 'string',
|
||||||
|
smoothscroll: 'boolean',
|
||||||
|
complete: 'string',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static def(name: string): PropertyDef | undefined {
|
||||||
|
return defs.find(p => p.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static defs(): PropertyDef[] {
|
||||||
|
return defs;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): PropertiesJSON {
|
||||||
|
return {
|
||||||
|
hintchars: this.hintchars,
|
||||||
|
smoothscroll: this.smoothscroll,
|
||||||
|
complete: this.complete,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
76
src/shared/settings/Search.ts
Normal file
76
src/shared/settings/Search.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
type Entries = { [name: string]: string };
|
||||||
|
|
||||||
|
export type SearchJSON = {
|
||||||
|
default: string;
|
||||||
|
engines: { [key: string]: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Search {
|
||||||
|
constructor(
|
||||||
|
public defaultEngine: string,
|
||||||
|
public engines: Entries,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: any): Search {
|
||||||
|
let defaultEngine = Search.getStringField(json, 'default');
|
||||||
|
let engines = Search.getObjectField(json, 'engines');
|
||||||
|
|
||||||
|
for (let [name, url] of Object.entries(engines)) {
|
||||||
|
if ((/\s/).test(name)) {
|
||||||
|
throw new TypeError(
|
||||||
|
`While space in the search engine not allowed: "${name}"`);
|
||||||
|
}
|
||||||
|
if (typeof url !== 'string') {
|
||||||
|
throw new TypeError(
|
||||||
|
`Invalid type of value in filed "engines": ${JSON.stringify(json)}`);
|
||||||
|
}
|
||||||
|
let matches = url.match(/{}/g);
|
||||||
|
if (matches === null) {
|
||||||
|
throw new TypeError(`No {}-placeholders in URL of "${name}"`);
|
||||||
|
} else if (matches.length > 1) {
|
||||||
|
throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.keys(engines).includes(defaultEngine)) {
|
||||||
|
throw new TypeError(`Default engine "${defaultEngine}" not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Search(
|
||||||
|
json.default as string,
|
||||||
|
json.engines,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): SearchJSON {
|
||||||
|
return {
|
||||||
|
default: this.defaultEngine,
|
||||||
|
engines: this.engines,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getStringField(json: any, name: string): string {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(json, name)) {
|
||||||
|
throw new TypeError(
|
||||||
|
`missing field "${name}" on search: ${JSON.stringify(json)}`);
|
||||||
|
}
|
||||||
|
if (typeof json[name] !== 'string') {
|
||||||
|
throw new TypeError(
|
||||||
|
`invalid type of filed "${name}" on search: ${JSON.stringify(json)}`);
|
||||||
|
}
|
||||||
|
return json[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getObjectField(json: any, name: string): Object {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(json, name)) {
|
||||||
|
throw new TypeError(
|
||||||
|
`missing field "${name}" on search: ${JSON.stringify(json)}`);
|
||||||
|
}
|
||||||
|
if (typeof json[name] !== 'object' || json[name] === null) {
|
||||||
|
throw new TypeError(
|
||||||
|
`invalid type of filed "${name}" on search: ${JSON.stringify(json)}`);
|
||||||
|
}
|
||||||
|
return json[name];
|
||||||
|
}
|
||||||
|
}
|
158
src/shared/settings/Settings.ts
Normal file
158
src/shared/settings/Settings.ts
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
import Keymaps, { KeymapsJSON } from './Keymaps';
|
||||||
|
import Search, { SearchJSON } from './Search';
|
||||||
|
import Properties, { PropertiesJSON } from './Properties';
|
||||||
|
import Blacklist, { BlacklistJSON } from './Blacklist';
|
||||||
|
|
||||||
|
export type SettingsJSON = {
|
||||||
|
keymaps: KeymapsJSON,
|
||||||
|
search: SearchJSON,
|
||||||
|
properties: PropertiesJSON,
|
||||||
|
blacklist: BlacklistJSON,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Settings {
|
||||||
|
public keymaps: Keymaps;
|
||||||
|
|
||||||
|
public search: Search;
|
||||||
|
|
||||||
|
public properties: Properties;
|
||||||
|
|
||||||
|
public blacklist: Blacklist;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
keymaps,
|
||||||
|
search,
|
||||||
|
properties,
|
||||||
|
blacklist,
|
||||||
|
}: {
|
||||||
|
keymaps: Keymaps;
|
||||||
|
search: Search;
|
||||||
|
properties: Properties;
|
||||||
|
blacklist: Blacklist;
|
||||||
|
}) {
|
||||||
|
this.keymaps = keymaps;
|
||||||
|
this.search = search;
|
||||||
|
this.properties = properties;
|
||||||
|
this.blacklist = blacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: any): Settings {
|
||||||
|
let settings = { ...DefaultSetting };
|
||||||
|
for (let key of Object.keys(json)) {
|
||||||
|
switch (key) {
|
||||||
|
case 'keymaps':
|
||||||
|
settings.keymaps = Keymaps.fromJSON(json.keymaps);
|
||||||
|
break;
|
||||||
|
case 'search':
|
||||||
|
settings.search = Search.fromJSON(json.search);
|
||||||
|
break;
|
||||||
|
case 'properties':
|
||||||
|
settings.properties = Properties.fromJSON(json.properties);
|
||||||
|
break;
|
||||||
|
case 'blacklist':
|
||||||
|
settings.blacklist = Blacklist.fromJSON(json.blacklist);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new TypeError('unknown setting: ' + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Settings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): SettingsJSON {
|
||||||
|
return {
|
||||||
|
keymaps: this.keymaps.toJSON(),
|
||||||
|
search: this.search.toJSON(),
|
||||||
|
properties: this.properties.toJSON(),
|
||||||
|
blacklist: this.blacklist.toJSON(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultSettingJSONText = `{
|
||||||
|
"keymaps": {
|
||||||
|
"0": { "type": "scroll.home" },
|
||||||
|
":": { "type": "command.show" },
|
||||||
|
"o": { "type": "command.show.open", "alter": false },
|
||||||
|
"O": { "type": "command.show.open", "alter": true },
|
||||||
|
"t": { "type": "command.show.tabopen", "alter": false },
|
||||||
|
"T": { "type": "command.show.tabopen", "alter": true },
|
||||||
|
"w": { "type": "command.show.winopen", "alter": false },
|
||||||
|
"W": { "type": "command.show.winopen", "alter": true },
|
||||||
|
"b": { "type": "command.show.buffer" },
|
||||||
|
"a": { "type": "command.show.addbookmark", "alter": true },
|
||||||
|
"k": { "type": "scroll.vertically", "count": -1 },
|
||||||
|
"j": { "type": "scroll.vertically", "count": 1 },
|
||||||
|
"h": { "type": "scroll.horizonally", "count": -1 },
|
||||||
|
"l": { "type": "scroll.horizonally", "count": 1 },
|
||||||
|
"<C-U>": { "type": "scroll.pages", "count": -0.5 },
|
||||||
|
"<C-D>": { "type": "scroll.pages", "count": 0.5 },
|
||||||
|
"<C-B>": { "type": "scroll.pages", "count": -1 },
|
||||||
|
"<C-F>": { "type": "scroll.pages", "count": 1 },
|
||||||
|
"gg": { "type": "scroll.top" },
|
||||||
|
"G": { "type": "scroll.bottom" },
|
||||||
|
"$": { "type": "scroll.end" },
|
||||||
|
"d": { "type": "tabs.close" },
|
||||||
|
"D": { "type": "tabs.close", "select": "left" },
|
||||||
|
"x$": { "type": "tabs.close.right" },
|
||||||
|
"!d": { "type": "tabs.close.force" },
|
||||||
|
"u": { "type": "tabs.reopen" },
|
||||||
|
"K": { "type": "tabs.prev" },
|
||||||
|
"J": { "type": "tabs.next" },
|
||||||
|
"gT": { "type": "tabs.prev" },
|
||||||
|
"gt": { "type": "tabs.next" },
|
||||||
|
"g0": { "type": "tabs.first" },
|
||||||
|
"g$": { "type": "tabs.last" },
|
||||||
|
"<C-6>": { "type": "tabs.prevsel" },
|
||||||
|
"r": { "type": "tabs.reload", "cache": false },
|
||||||
|
"R": { "type": "tabs.reload", "cache": true },
|
||||||
|
"zp": { "type": "tabs.pin.toggle" },
|
||||||
|
"zd": { "type": "tabs.duplicate" },
|
||||||
|
"zi": { "type": "zoom.in" },
|
||||||
|
"zo": { "type": "zoom.out" },
|
||||||
|
"zz": { "type": "zoom.neutral" },
|
||||||
|
"f": { "type": "follow.start", "newTab": false },
|
||||||
|
"F": { "type": "follow.start", "newTab": true, "background": false },
|
||||||
|
"m": { "type": "mark.set.prefix" },
|
||||||
|
"'": { "type": "mark.jump.prefix" },
|
||||||
|
"H": { "type": "navigate.history.prev" },
|
||||||
|
"L": { "type": "navigate.history.next" },
|
||||||
|
"[[": { "type": "navigate.link.prev" },
|
||||||
|
"]]": { "type": "navigate.link.next" },
|
||||||
|
"gu": { "type": "navigate.parent" },
|
||||||
|
"gU": { "type": "navigate.root" },
|
||||||
|
"gi": { "type": "focus.input" },
|
||||||
|
"gf": { "type": "page.source" },
|
||||||
|
"gh": { "type": "page.home" },
|
||||||
|
"gH": { "type": "page.home", "newTab": true },
|
||||||
|
"y": { "type": "urls.yank" },
|
||||||
|
"p": { "type": "urls.paste", "newTab": false },
|
||||||
|
"P": { "type": "urls.paste", "newTab": true },
|
||||||
|
"/": { "type": "find.start" },
|
||||||
|
"n": { "type": "find.next" },
|
||||||
|
"N": { "type": "find.prev" },
|
||||||
|
".": { "type": "repeat.last" },
|
||||||
|
"<S-Esc>": { "type": "addon.toggle.enabled" }
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"default": "google",
|
||||||
|
"engines": {
|
||||||
|
"google": "https://google.com/search?q={}",
|
||||||
|
"yahoo": "https://search.yahoo.com/search?p={}",
|
||||||
|
"bing": "https://www.bing.com/search?q={}",
|
||||||
|
"duckduckgo": "https://duckduckgo.com/?q={}",
|
||||||
|
"twitter": "https://twitter.com/search?q={}",
|
||||||
|
"wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"hintchars": "abcdefghijklmnopqrstuvwxyz",
|
||||||
|
"smoothscroll": false,
|
||||||
|
"complete": "sbh"
|
||||||
|
},
|
||||||
|
"blacklist": [
|
||||||
|
]
|
||||||
|
}`;
|
||||||
|
|
||||||
|
export const DefaultSetting: Settings =
|
||||||
|
Settings.fromJSON(JSON.parse(DefaultSettingJSONText));
|
|
@ -1,4 +1,4 @@
|
||||||
import { Search } from './Settings';
|
import Search from './settings/Search';
|
||||||
|
|
||||||
const trimStart = (str: string): string => {
|
const trimStart = (str: string): string => {
|
||||||
// NOTE String.trimStart is available on Firefox 61
|
// NOTE String.trimStart is available on Firefox 61
|
||||||
|
@ -19,7 +19,7 @@ const searchUrl = (keywords: string, search: Search): string => {
|
||||||
if (keywords.includes('.') && !keywords.includes(' ')) {
|
if (keywords.includes('.') && !keywords.includes(' ')) {
|
||||||
return 'http://' + keywords;
|
return 'http://' + keywords;
|
||||||
}
|
}
|
||||||
let template = search.engines[search.default];
|
let template = search.engines[search.defaultEngine];
|
||||||
let query = keywords;
|
let query = keywords;
|
||||||
|
|
||||||
let first = trimStart(keywords).split(' ')[0];
|
let first = trimStart(keywords).split(' ')[0];
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
const fromWildcard = (pattern: string): RegExp => {
|
|
||||||
let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
|
|
||||||
return new RegExp(regexStr);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { fromWildcard };
|
|
|
@ -1,6 +1,6 @@
|
||||||
import InputDriver from '../../src/content/InputDriver';
|
import InputDriver, {keyFromKeyboardEvent} from '../../src/content/InputDriver';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import Key from '../../src/content/domains/Key';
|
import Key from '../../src/shared/settings/Key';
|
||||||
|
|
||||||
describe('InputDriver', () => {
|
describe('InputDriver', () => {
|
||||||
let target: HTMLElement;
|
let target: HTMLElement;
|
||||||
|
@ -21,10 +21,10 @@ describe('InputDriver', () => {
|
||||||
it('register callbacks', (done) => {
|
it('register callbacks', (done) => {
|
||||||
driver.onKey((key: Key): boolean => {
|
driver.onKey((key: Key): boolean => {
|
||||||
expect(key.key).to.equal('a');
|
expect(key.key).to.equal('a');
|
||||||
expect(key.ctrlKey).to.be.true;
|
expect(key.ctrl).to.be.true;
|
||||||
expect(key.shiftKey).to.be.false;
|
expect(key.shift).to.be.false;
|
||||||
expect(key.altKey).to.be.false;
|
expect(key.alt).to.be.false;
|
||||||
expect(key.metaKey).to.be.false;
|
expect(key.meta).to.be.false;
|
||||||
done();
|
done();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -68,15 +68,15 @@ describe('InputDriver', () => {
|
||||||
|
|
||||||
it('propagates and stop handler chain', () => {
|
it('propagates and stop handler chain', () => {
|
||||||
let a = 0, b = 0, c = 0;
|
let a = 0, b = 0, c = 0;
|
||||||
driver.onKey((key: Key): boolean => {
|
driver.onKey((_key: Key): boolean => {
|
||||||
a++;
|
a++;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
driver.onKey((key: Key): boolean => {
|
driver.onKey((_key: Key): boolean => {
|
||||||
b++;
|
b++;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
driver.onKey((key: Key): boolean => {
|
driver.onKey((_key: Key): boolean => {
|
||||||
c++;
|
c++;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -89,7 +89,7 @@ describe('InputDriver', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not invoke only meta keys', () => {
|
it('does not invoke only meta keys', () => {
|
||||||
driver.onKey((key: Key): boolean=> {
|
driver.onKey((_key: Key): boolean=> {
|
||||||
expect.fail();
|
expect.fail();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -115,7 +115,7 @@ describe('InputDriver', () => {
|
||||||
it('ignores events from contenteditable elements', () => {
|
it('ignores events from contenteditable elements', () => {
|
||||||
let div = window.document.createElement('div');
|
let div = window.document.createElement('div');
|
||||||
let driver = new InputDriver(div);
|
let driver = new InputDriver(div);
|
||||||
driver.onKey((key: Key): boolean => {
|
driver.onKey((_key: Key): boolean => {
|
||||||
expect.fail();
|
expect.fail();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -127,3 +127,50 @@ describe('InputDriver', () => {
|
||||||
div.dispatchEvent(new KeyboardEvent('keydown', { key: 'x' }));
|
div.dispatchEvent(new KeyboardEvent('keydown', { key: 'x' }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#keyFromKeyboardEvent", () => {
|
||||||
|
it('returns from keyboard input Ctrl+X', () => {
|
||||||
|
let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', {
|
||||||
|
key: 'x', shiftKey: false, ctrlKey: true, altKey: false, metaKey: true,
|
||||||
|
}));
|
||||||
|
expect(k.key).to.equal('x');
|
||||||
|
expect(k.shift).to.be.false;
|
||||||
|
expect(k.ctrl).to.be.true;
|
||||||
|
expect(k.alt).to.be.false;
|
||||||
|
expect(k.meta).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns from keyboard input Shift+Esc', () => {
|
||||||
|
let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', {
|
||||||
|
key: 'Escape', shiftKey: true, ctrlKey: false, altKey: false, metaKey: true
|
||||||
|
}));
|
||||||
|
expect(k.key).to.equal('Esc');
|
||||||
|
expect(k.shift).to.be.true;
|
||||||
|
expect(k.ctrl).to.be.false;
|
||||||
|
expect(k.alt).to.be.false;
|
||||||
|
expect(k.meta).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns from keyboard input Ctrl+$', () => {
|
||||||
|
// $ required shift pressing on most keyboards
|
||||||
|
let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', {
|
||||||
|
key: '$', shiftKey: true, ctrlKey: true, altKey: false, metaKey: false
|
||||||
|
}));
|
||||||
|
expect(k.key).to.equal('$');
|
||||||
|
expect(k.shift).to.be.false;
|
||||||
|
expect(k.ctrl).to.be.true;
|
||||||
|
expect(k.alt).to.be.false;
|
||||||
|
expect(k.meta).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns from keyboard input Crtl+Space', () => {
|
||||||
|
let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', {
|
||||||
|
key: ' ', shiftKey: false, ctrlKey: true, altKey: false, metaKey: false
|
||||||
|
}));
|
||||||
|
expect(k.key).to.equal('Space');
|
||||||
|
expect(k.shift).to.be.false;
|
||||||
|
expect(k.ctrl).to.be.true;
|
||||||
|
expect(k.alt).to.be.false;
|
||||||
|
expect(k.meta).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
import Key, * as keys from '../../../src/content/domains/Key';
|
|
||||||
import { expect } from 'chai'
|
|
||||||
|
|
||||||
describe("Key", () => {
|
|
||||||
describe('fromKeyboardEvent', () => {
|
|
||||||
it('returns from keyboard input Ctrl+X', () => {
|
|
||||||
let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', {
|
|
||||||
key: 'x', shiftKey: false, ctrlKey: true, altKey: false, metaKey: true,
|
|
||||||
}));
|
|
||||||
expect(k.key).to.equal('x');
|
|
||||||
expect(k.shiftKey).to.be.false;
|
|
||||||
expect(k.ctrlKey).to.be.true;
|
|
||||||
expect(k.altKey).to.be.false;
|
|
||||||
expect(k.metaKey).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns from keyboard input Shift+Esc', () => {
|
|
||||||
let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', {
|
|
||||||
key: 'Escape', shiftKey: true, ctrlKey: false, altKey: false, metaKey: true
|
|
||||||
}));
|
|
||||||
expect(k.key).to.equal('Esc');
|
|
||||||
expect(k.shiftKey).to.be.true;
|
|
||||||
expect(k.ctrlKey).to.be.false;
|
|
||||||
expect(k.altKey).to.be.false;
|
|
||||||
expect(k.metaKey).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns from keyboard input Ctrl+$', () => {
|
|
||||||
// $ required shift pressing on most keyboards
|
|
||||||
let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', {
|
|
||||||
key: '$', shiftKey: true, ctrlKey: true, altKey: false, metaKey: false
|
|
||||||
}));
|
|
||||||
expect(k.key).to.equal('$');
|
|
||||||
expect(k.shiftKey).to.be.false;
|
|
||||||
expect(k.ctrlKey).to.be.true;
|
|
||||||
expect(k.altKey).to.be.false;
|
|
||||||
expect(k.metaKey).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns from keyboard input Crtl+Space', () => {
|
|
||||||
let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', {
|
|
||||||
key: ' ', shiftKey: false, ctrlKey: true, altKey: false, metaKey: false
|
|
||||||
}));
|
|
||||||
expect(k.key).to.equal('Space');
|
|
||||||
expect(k.shiftKey).to.be.false;
|
|
||||||
expect(k.ctrlKey).to.be.true;
|
|
||||||
expect(k.altKey).to.be.false;
|
|
||||||
expect(k.metaKey).to.be.false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('fromMapKey', () => {
|
|
||||||
it('return for X', () => {
|
|
||||||
let key = keys.fromMapKey('x');
|
|
||||||
expect(key.key).to.equal('x');
|
|
||||||
expect(key.shiftKey).to.be.false;
|
|
||||||
expect(key.ctrlKey).to.be.false;
|
|
||||||
expect(key.altKey).to.be.false;
|
|
||||||
expect(key.metaKey).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('return for Shift+X', () => {
|
|
||||||
let key = keys.fromMapKey('X');
|
|
||||||
expect(key.key).to.equal('X');
|
|
||||||
expect(key.shiftKey).to.be.true;
|
|
||||||
expect(key.ctrlKey).to.be.false;
|
|
||||||
expect(key.altKey).to.be.false;
|
|
||||||
expect(key.metaKey).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('return for Ctrl+X', () => {
|
|
||||||
let key = keys.fromMapKey('<C-X>');
|
|
||||||
expect(key.key).to.equal('x');
|
|
||||||
expect(key.shiftKey).to.be.false;
|
|
||||||
expect(key.ctrlKey).to.be.true;
|
|
||||||
expect(key.altKey).to.be.false;
|
|
||||||
expect(key.metaKey).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns for Ctrl+Meta+X', () => {
|
|
||||||
let key = keys.fromMapKey('<C-M-X>');
|
|
||||||
expect(key.key).to.equal('x');
|
|
||||||
expect(key.shiftKey).to.be.false;
|
|
||||||
expect(key.ctrlKey).to.be.true;
|
|
||||||
expect(key.altKey).to.be.false;
|
|
||||||
expect(key.metaKey).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns for Ctrl+Shift+x', () => {
|
|
||||||
let key = keys.fromMapKey('<C-S-x>');
|
|
||||||
expect(key.key).to.equal('X');
|
|
||||||
expect(key.shiftKey).to.be.true;
|
|
||||||
expect(key.ctrlKey).to.be.true;
|
|
||||||
expect(key.altKey).to.be.false;
|
|
||||||
expect(key.metaKey).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns for Shift+Esc', () => {
|
|
||||||
let key = keys.fromMapKey('<S-Esc>');
|
|
||||||
expect(key.key).to.equal('Esc');
|
|
||||||
expect(key.shiftKey).to.be.true;
|
|
||||||
expect(key.ctrlKey).to.be.false;
|
|
||||||
expect(key.altKey).to.be.false;
|
|
||||||
expect(key.metaKey).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns for Ctrl+Esc', () => {
|
|
||||||
let key = keys.fromMapKey('<C-Esc>');
|
|
||||||
expect(key.key).to.equal('Esc');
|
|
||||||
expect(key.shiftKey).to.be.false;
|
|
||||||
expect(key.ctrlKey).to.be.true;
|
|
||||||
expect(key.altKey).to.be.false;
|
|
||||||
expect(key.metaKey).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns for Ctrl+Esc', () => {
|
|
||||||
let key = keys.fromMapKey('<C-Space>');
|
|
||||||
expect(key.key).to.equal('Space');
|
|
||||||
expect(key.shiftKey).to.be.false;
|
|
||||||
expect(key.ctrlKey).to.be.true;
|
|
||||||
expect(key.altKey).to.be.false;
|
|
||||||
expect(key.metaKey).to.be.false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('equals', () => {
|
|
||||||
expect(keys.equals(
|
|
||||||
{ key: 'x', ctrlKey: true, },
|
|
||||||
{ key: 'x', ctrlKey: true, },
|
|
||||||
)).to.be.true;
|
|
||||||
|
|
||||||
expect(keys.equals(
|
|
||||||
{ key: 'X', shiftKey: true, },
|
|
||||||
{ key: 'x', ctrlKey: true, },
|
|
||||||
)).to.be.false;
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,72 +0,0 @@
|
||||||
import KeySequence, * as utils from '../../../src/content/domains/KeySequence';
|
|
||||||
import { expect } from 'chai'
|
|
||||||
|
|
||||||
describe("KeySequence", () => {
|
|
||||||
describe('#push', () => {
|
|
||||||
it('append a key to the sequence', () => {
|
|
||||||
let seq = KeySequence.from([]);
|
|
||||||
seq.push({ key: 'g' });
|
|
||||||
seq.push({ key: 'u', shiftKey: true });
|
|
||||||
|
|
||||||
let array = seq.getKeyArray();
|
|
||||||
expect(array[0]).to.deep.equal({ key: 'g' });
|
|
||||||
expect(array[1]).to.deep.equal({ key: 'u', shiftKey: true });
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#startsWith', () => {
|
|
||||||
it('returns true if the key sequence starts with param', () => {
|
|
||||||
let seq = KeySequence.from([
|
|
||||||
{ key: 'g' },
|
|
||||||
{ key: 'u', shiftKey: true },
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(seq.startsWith(KeySequence.from([
|
|
||||||
]))).to.be.true;
|
|
||||||
expect(seq.startsWith(KeySequence.from([
|
|
||||||
{ key: 'g' },
|
|
||||||
]))).to.be.true;
|
|
||||||
expect(seq.startsWith(KeySequence.from([
|
|
||||||
{ key: 'g' }, { key: 'u', shiftKey: true },
|
|
||||||
]))).to.be.true;
|
|
||||||
expect(seq.startsWith(KeySequence.from([
|
|
||||||
{ key: 'g' }, { key: 'u', shiftKey: true }, { key: 'x' },
|
|
||||||
]))).to.be.false;
|
|
||||||
expect(seq.startsWith(KeySequence.from([
|
|
||||||
{ key: 'h' },
|
|
||||||
]))).to.be.false;
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns true if the empty sequence starts with an empty sequence', () => {
|
|
||||||
let seq = KeySequence.from([]);
|
|
||||||
|
|
||||||
expect(seq.startsWith(KeySequence.from([]))).to.be.true;
|
|
||||||
expect(seq.startsWith(KeySequence.from([
|
|
||||||
{ key: 'h' },
|
|
||||||
]))).to.be.false;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#fromMapKeys', () => {
|
|
||||||
it('returns mapped keys for Shift+Esc', () => {
|
|
||||||
let keyArray = utils.fromMapKeys('<S-Esc>').getKeyArray();
|
|
||||||
expect(keyArray).to.have.lengthOf(1);
|
|
||||||
expect(keyArray[0].key).to.equal('Esc');
|
|
||||||
expect(keyArray[0].shiftKey).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns mapped keys for a<C-B><A-C>d<M-e>', () => {
|
|
||||||
let keyArray = utils.fromMapKeys('a<C-B><A-C>d<M-e>').getKeyArray();
|
|
||||||
expect(keyArray).to.have.lengthOf(5);
|
|
||||||
expect(keyArray[0].key).to.equal('a');
|
|
||||||
expect(keyArray[1].ctrlKey).to.be.true;
|
|
||||||
expect(keyArray[1].key).to.equal('b');
|
|
||||||
expect(keyArray[2].altKey).to.be.true;
|
|
||||||
expect(keyArray[2].key).to.equal('c');
|
|
||||||
expect(keyArray[3].key).to.equal('d');
|
|
||||||
expect(keyArray[4].metaKey).to.be.true;
|
|
||||||
expect(keyArray[4].key).to.equal('e');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,6 +1,7 @@
|
||||||
import KeymapRepository, { KeymapRepositoryImpl }
|
import KeymapRepository, { KeymapRepositoryImpl }
|
||||||
from '../../../src/content/repositories/KeymapRepository';
|
from '../../../src/content/repositories/KeymapRepository';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import Key from "../../../src/shared/settings/Key";
|
||||||
|
|
||||||
describe('KeymapRepositoryImpl', () => {
|
describe('KeymapRepositoryImpl', () => {
|
||||||
let sut: KeymapRepository;
|
let sut: KeymapRepository;
|
||||||
|
@ -11,24 +12,25 @@ describe('KeymapRepositoryImpl', () => {
|
||||||
|
|
||||||
describe('#enqueueKey()', () => {
|
describe('#enqueueKey()', () => {
|
||||||
it('enqueues keys', () => {
|
it('enqueues keys', () => {
|
||||||
sut.enqueueKey({ key: 'a' });
|
sut.enqueueKey(Key.fromMapKey('a');
|
||||||
sut.enqueueKey({ key: 'b' });
|
sut.enqueueKey(Key.fromMapKey('b');
|
||||||
let sequence = sut.enqueueKey({ key: 'c' });
|
let sequence = sut.enqueueKey(Key.fromMapKey('c'));
|
||||||
|
|
||||||
expect(sequence.getKeyArray()).deep.equals([
|
let keys = sequence.keys;
|
||||||
{ key: 'a' }, { key: 'b' }, { key: 'c' },
|
expect(keys[0].equals(Key.fromMapKey('a'))).to.be.true;
|
||||||
]);
|
expect(keys[1].equals(Key.fromMapKey('b'))).to.be.true;
|
||||||
|
expect(keys[2].equals(Key.fromMapKey('c'))).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#clear()', () => {
|
describe('#clear()', () => {
|
||||||
it('clears keys', () => {
|
it('clears keys', () => {
|
||||||
sut.enqueueKey({ key: 'a' });
|
sut.enqueueKey(Key.fromMapKey('a'));
|
||||||
sut.enqueueKey({ key: 'b' });
|
sut.enqueueKey(Key.fromMapKey('b'));
|
||||||
sut.enqueueKey({ key: 'c' });
|
sut.enqueueKey(Key.fromMapKey('c'));
|
||||||
sut.clear();
|
sut.clear();
|
||||||
|
|
||||||
let sequence = sut.enqueueKey({ key: 'a' });
|
let sequence = sut.enqueueKey(Key.fromMapKey('a'));
|
||||||
expect(sequence.length()).to.equal(1);
|
expect(sequence.length()).to.equal(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { SettingRepositoryImpl } from '../../../src/content/repositories/SettingRepository';
|
import { SettingRepositoryImpl } from '../../../src/content/repositories/SettingRepository';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import Settings from '../../../src/shared/settings/Settings';
|
||||||
|
|
||||||
describe('SettingRepositoryImpl', () => {
|
describe('SettingRepositoryImpl', () => {
|
||||||
it('updates and gets current value', () => {
|
it('updates and gets current value', () => {
|
||||||
let sut = new SettingRepositoryImpl();
|
let sut = new SettingRepositoryImpl();
|
||||||
|
|
||||||
let settings = {
|
let settings = Settings.fromJSON({
|
||||||
keymaps: {},
|
keymaps: {},
|
||||||
search: {
|
search:{
|
||||||
default: 'google',
|
default: 'google',
|
||||||
engines: {
|
engines: {
|
||||||
google: 'https://google.com/?q={}',
|
google: 'https://google.com/?q={}',
|
||||||
|
@ -19,7 +20,7 @@ describe('SettingRepositoryImpl', () => {
|
||||||
complete: 'sbh',
|
complete: 'sbh',
|
||||||
},
|
},
|
||||||
blacklist: [],
|
blacklist: [],
|
||||||
}
|
});
|
||||||
|
|
||||||
sut.set(settings);
|
sut.set(settings);
|
||||||
|
|
||||||
|
@ -27,4 +28,3 @@ describe('SettingRepositoryImpl', () => {
|
||||||
expect(actual.properties.hintchars).to.equal('abcd1234');
|
expect(actual.properties.hintchars).to.equal('abcd1234');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import SettingRepository from '../../../src/content/repositories/SettingRepository';
|
import SettingRepository from '../../../src/content/repositories/SettingRepository';
|
||||||
import SettingClient from '../../../src/content/client/SettingClient';
|
import SettingClient from '../../../src/content/client/SettingClient';
|
||||||
import SettingUseCase from '../../../src/content/usecases/SettingUseCase';
|
import SettingUseCase from '../../../src/content/usecases/SettingUseCase';
|
||||||
import Settings, { DefaultSetting } from '../../../src/shared/Settings';
|
import Settings, { DefaultSetting } from '../../../src/shared/settings/Settings';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
|
||||||
class MockSettingRepository implements SettingRepository {
|
class MockSettingRepository implements SettingRepository {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { expect } from 'chai';
|
||||||
describe("settings/form/KeymapsForm", () => {
|
describe("settings/form/KeymapsForm", () => {
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
it('renders keymap fields', () => {
|
it('renders keymap fields', () => {
|
||||||
let root = ReactTestRenderer.create(<KeymapsForm value={FormKeymaps.valueOf({
|
let root = ReactTestRenderer.create(<KeymapsForm value={FormKeymaps.fromJSON({
|
||||||
'scroll.vertically?{"count":1}': 'j',
|
'scroll.vertically?{"count":1}': 'j',
|
||||||
'scroll.vertically?{"count":-1}': 'k',
|
'scroll.vertically?{"count":-1}': 'k',
|
||||||
})} />).root
|
})} />).root
|
||||||
|
@ -48,7 +48,7 @@ describe("settings/form/KeymapsForm", () => {
|
||||||
it('invokes onChange event on edit', (done) => {
|
it('invokes onChange event on edit', (done) => {
|
||||||
ReactTestUtils.act(() => {
|
ReactTestUtils.act(() => {
|
||||||
ReactDOM.render(<KeymapsForm
|
ReactDOM.render(<KeymapsForm
|
||||||
value={FormKeymaps.valueOf({
|
value={FormKeymaps.fromJSON({
|
||||||
'scroll.vertically?{"count":1}': 'j',
|
'scroll.vertically?{"count":1}': 'j',
|
||||||
'scroll.vertically?{"count":-1}': 'k',
|
'scroll.vertically?{"count":-1}': 'k',
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -13,14 +13,14 @@ describe("settings/form/PropertiesForm", () => {
|
||||||
mybool: 'boolean',
|
mybool: 'boolean',
|
||||||
empty: 'string',
|
empty: 'string',
|
||||||
}
|
}
|
||||||
let value = {
|
let values = {
|
||||||
mystr: 'abc',
|
mystr: 'abc',
|
||||||
mynum: 123,
|
mynum: 123,
|
||||||
mybool: true,
|
mybool: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let root = ReactTestRenderer.create(
|
let root = ReactTestRenderer.create(
|
||||||
<PropertiesForm types={types} value={value} />,
|
<PropertiesForm types={types} value={values} />,
|
||||||
).root
|
).root
|
||||||
|
|
||||||
let input = root.findByProps({ name: 'mystr' });
|
let input = root.findByProps({ name: 'mystr' });
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { FormSearch } from 'shared/SettingData';
|
||||||
describe("settings/form/SearchForm", () => {
|
describe("settings/form/SearchForm", () => {
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
it('renders SearchForm', () => {
|
it('renders SearchForm', () => {
|
||||||
let root = ReactTestRenderer.create(<SearchForm value={FormSearch.valueOf({
|
let root = ReactTestRenderer.create(<SearchForm value={FormSearch.fromJSON({
|
||||||
default: 'google',
|
default: 'google',
|
||||||
engines: [['google', 'google.com'], ['yahoo', 'yahoo.com']],
|
engines: [['google', 'google.com'], ['yahoo', 'yahoo.com']],
|
||||||
})} />).root;
|
})} />).root;
|
||||||
|
@ -41,7 +41,7 @@ describe("settings/form/SearchForm", () => {
|
||||||
it('invokes onChange event on edit', (done) => {
|
it('invokes onChange event on edit', (done) => {
|
||||||
ReactTestUtils.act(() => {
|
ReactTestUtils.act(() => {
|
||||||
ReactDOM.render(<SearchForm
|
ReactDOM.render(<SearchForm
|
||||||
value={FormSearch.valueOf({
|
value={FormSearch.fromJSON({
|
||||||
default: 'google',
|
default: 'google',
|
||||||
engines: [['google', 'google.com'], ['yahoo', 'yahoo.com']]
|
engines: [['google', 'google.com'], ['yahoo', 'yahoo.com']]
|
||||||
})}
|
})}
|
||||||
|
@ -67,7 +67,7 @@ describe("settings/form/SearchForm", () => {
|
||||||
|
|
||||||
it('invokes onChange event on delete', (done) => {
|
it('invokes onChange event on delete', (done) => {
|
||||||
ReactTestUtils.act(() => {
|
ReactTestUtils.act(() => {
|
||||||
ReactDOM.render(<SearchForm value={FormSearch.valueOf({
|
ReactDOM.render(<SearchForm value={FormSearch.fromJSON({
|
||||||
default: 'yahoo',
|
default: 'yahoo',
|
||||||
engines: [['louvre', 'google.com'], ['yahoo', 'yahoo.com']]
|
engines: [['louvre', 'google.com'], ['yahoo', 'yahoo.com']]
|
||||||
})}
|
})}
|
||||||
|
@ -88,7 +88,7 @@ describe("settings/form/SearchForm", () => {
|
||||||
|
|
||||||
it('invokes onChange event on add', (done) => {
|
it('invokes onChange event on add', (done) => {
|
||||||
ReactTestUtils.act(() => {
|
ReactTestUtils.act(() => {
|
||||||
ReactDOM.render(<SearchForm value={FormSearch.valueOf({
|
ReactDOM.render(<SearchForm value={FormSearch.fromJSON({
|
||||||
default: 'yahoo',
|
default: 'yahoo',
|
||||||
engines: [['google', 'google.com']]
|
engines: [['google', 'google.com']]
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import SettingData, {
|
import SettingData, {
|
||||||
FormKeymaps, JSONSettings, FormSettings,
|
FormKeymaps, JSONTextSettings, FormSettings,
|
||||||
} from '../../src/shared/SettingData';
|
} from '../../src/shared/SettingData';
|
||||||
import Settings, { Keymaps } from '../../src/shared/Settings';
|
import Settings from '../../src/shared/settings/Settings';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import Keymaps from '../../src/shared/settings/Keymaps';
|
||||||
|
|
||||||
describe('shared/SettingData', () => {
|
describe('shared/SettingData', () => {
|
||||||
describe('FormKeymaps', () => {
|
describe('FormKeymaps', () => {
|
||||||
|
@ -11,9 +12,9 @@ describe('shared/SettingData', () => {
|
||||||
let data = {
|
let data = {
|
||||||
'scroll.vertically?{"count":1}': 'j',
|
'scroll.vertically?{"count":1}': 'j',
|
||||||
'scroll.home': '0',
|
'scroll.home': '0',
|
||||||
}
|
};
|
||||||
|
|
||||||
let keymaps = FormKeymaps.valueOf(data).toKeymaps();
|
let keymaps = FormKeymaps.fromJSON(data).toKeymaps().toJSON();
|
||||||
expect(keymaps).to.deep.equal({
|
expect(keymaps).to.deep.equal({
|
||||||
'j': { type: 'scroll.vertically', count: 1 },
|
'j': { type: 'scroll.vertically', count: 1 },
|
||||||
'0': { type: 'scroll.home' },
|
'0': { type: 'scroll.home' },
|
||||||
|
@ -23,13 +24,13 @@ describe('shared/SettingData', () => {
|
||||||
|
|
||||||
describe('#fromKeymaps to #toJSON', () => {
|
describe('#fromKeymaps to #toJSON', () => {
|
||||||
it('create from a Keymaps and create a JSON object', () => {
|
it('create from a Keymaps and create a JSON object', () => {
|
||||||
let data: Keymaps = {
|
let keymaps: Keymaps = Keymaps.fromJSON({
|
||||||
'j': { type: 'scroll.vertically', count: 1 },
|
'j': { type: 'scroll.vertically', count: 1 },
|
||||||
'0': { type: 'scroll.home' },
|
'0': { type: 'scroll.home' },
|
||||||
}
|
});
|
||||||
|
|
||||||
let keymaps = FormKeymaps.fromKeymaps(data).toJSON();
|
let form = FormKeymaps.fromKeymaps(keymaps).toJSON();
|
||||||
expect(keymaps).to.deep.equal({
|
expect(form).to.deep.equal({
|
||||||
'scroll.vertically?{"count":1}': 'j',
|
'scroll.vertically?{"count":1}': 'j',
|
||||||
'scroll.home': '0',
|
'scroll.home': '0',
|
||||||
});
|
});
|
||||||
|
@ -56,14 +57,14 @@ describe('shared/SettingData', () => {
|
||||||
"blacklist": []
|
"blacklist": []
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
let settings = JSONSettings.valueOf(o).toSettings();
|
let settings = JSONTextSettings.fromText(o).toSettings();
|
||||||
expect(settings).to.deep.equal(JSON.parse(o));
|
expect(settings.toJSON()).to.deep.equal(JSON.parse(o));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#fromSettings to #toJSON', () => {
|
describe('#fromSettings to #toJSON', () => {
|
||||||
it('create from a Settings and create a JSON string', () => {
|
it('create from a Settings and create a JSON string', () => {
|
||||||
let o = {
|
let o = Settings.fromJSON({
|
||||||
keymaps: {},
|
keymaps: {},
|
||||||
search: {
|
search: {
|
||||||
default: "google",
|
default: "google",
|
||||||
|
@ -77,10 +78,10 @@ describe('shared/SettingData', () => {
|
||||||
complete: "sbh"
|
complete: "sbh"
|
||||||
},
|
},
|
||||||
blacklist: [],
|
blacklist: [],
|
||||||
};
|
});
|
||||||
|
|
||||||
let json = JSONSettings.fromSettings(o).toJSON();
|
let json = JSONTextSettings.fromSettings(o).toJSONText();
|
||||||
expect(JSON.parse(json)).to.deep.equal(o);
|
expect(JSON.parse(json)).to.deep.equal(o.toJSON());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -107,8 +108,8 @@ describe('shared/SettingData', () => {
|
||||||
blacklist: []
|
blacklist: []
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = FormSettings.valueOf(data).toSettings();
|
let settings = FormSettings.fromJSON(data).toSettings();
|
||||||
expect(settings).to.deep.equal({
|
expect(settings.toJSON()).to.deep.equal({
|
||||||
keymaps: {
|
keymaps: {
|
||||||
'j': { type: 'scroll.vertically', count: 1 },
|
'j': { type: 'scroll.vertically', count: 1 },
|
||||||
'0': { type: 'scroll.home' },
|
'0': { type: 'scroll.home' },
|
||||||
|
@ -131,7 +132,7 @@ describe('shared/SettingData', () => {
|
||||||
|
|
||||||
describe('#fromSettings to #toJSON', () => {
|
describe('#fromSettings to #toJSON', () => {
|
||||||
it('create from a Settings and create a JSON string', () => {
|
it('create from a Settings and create a JSON string', () => {
|
||||||
let data: Settings = {
|
let data: Settings = Settings.fromJSON({
|
||||||
keymaps: {
|
keymaps: {
|
||||||
'j': { type: 'scroll.vertically', count: 1 },
|
'j': { type: 'scroll.vertically', count: 1 },
|
||||||
'0': { type: 'scroll.home' },
|
'0': { type: 'scroll.home' },
|
||||||
|
@ -147,8 +148,8 @@ describe('shared/SettingData', () => {
|
||||||
smoothscroll: false,
|
smoothscroll: false,
|
||||||
complete: "sbh"
|
complete: "sbh"
|
||||||
},
|
},
|
||||||
blacklist: []
|
blacklist: [],
|
||||||
};
|
});
|
||||||
|
|
||||||
let json = FormSettings.fromSettings(data).toJSON();
|
let json = FormSettings.fromSettings(data).toJSON();
|
||||||
expect(json).to.deep.equal({
|
expect(json).to.deep.equal({
|
||||||
|
@ -195,7 +196,7 @@ describe('shared/SettingData', () => {
|
||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
let j = SettingData.valueOf(data).toJSON();
|
let j = SettingData.fromJSON(data).toJSON();
|
||||||
expect(j.source).to.equal('json');
|
expect(j.source).to.equal('json');
|
||||||
expect(j.json).to.be.a('string');
|
expect(j.json).to.be.a('string');
|
||||||
});
|
});
|
||||||
|
@ -220,7 +221,7 @@ describe('shared/SettingData', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let j = SettingData.valueOf(data).toJSON();
|
let j = SettingData.fromJSON(data).toJSON();
|
||||||
expect(j.source).to.equal('form');
|
expect(j.source).to.equal('form');
|
||||||
expect(j.form).to.deep.equal({
|
expect(j.form).to.deep.equal({
|
||||||
keymaps: {},
|
keymaps: {},
|
||||||
|
@ -261,8 +262,8 @@ describe('shared/SettingData', () => {
|
||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = SettingData.valueOf(data).toSettings();
|
let settings = SettingData.fromJSON(data).toSettings();
|
||||||
expect(settings.search.default).to.equal('google');
|
expect(settings.search.defaultEngine).to.equal('google');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parse object from form source', () => {
|
it('parse object from form source', () => {
|
||||||
|
@ -285,8 +286,8 @@ describe('shared/SettingData', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = SettingData.valueOf(data).toSettings();
|
let settings = SettingData.fromJSON(data).toSettings();
|
||||||
expect(settings.search.default).to.equal('yahoo');
|
expect(settings.search.defaultEngine).to.equal('yahoo');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,194 +0,0 @@
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws a TypeError with an unknown field', () => {
|
|
||||||
expect(() => settings.valueOf({ name: 'alice' })).to.throw(TypeError)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,49 +0,0 @@
|
||||||
import { includes } from 'shared/blacklists';
|
|
||||||
|
|
||||||
describe("shared/blacklist", () => {
|
|
||||||
it('matches by *', () => {
|
|
||||||
let blacklist = ['*'];
|
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com/abc')).to.be.true;
|
|
||||||
})
|
|
||||||
|
|
||||||
it('matches by hostname', () => {
|
|
||||||
let blacklist = ['github.com'];
|
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com')).to.be.true;
|
|
||||||
expect(includes(blacklist, 'https://gist.github.com')).to.be.false;
|
|
||||||
expect(includes(blacklist, 'https://github.com/ueokande')).to.be.true;
|
|
||||||
expect(includes(blacklist, 'https://github.org')).to.be.false;
|
|
||||||
expect(includes(blacklist, 'https://google.com/search?q=github.org')).to.be.false;
|
|
||||||
})
|
|
||||||
|
|
||||||
it('matches by hostname with wildcard', () => {
|
|
||||||
let blacklist = ['*.github.com'];
|
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com')).to.be.false;
|
|
||||||
expect(includes(blacklist, 'https://gist.github.com')).to.be.true;
|
|
||||||
})
|
|
||||||
|
|
||||||
it('matches by path', () => {
|
|
||||||
let blacklist = ['github.com/abc'];
|
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com/abc')).to.be.true;
|
|
||||||
expect(includes(blacklist, 'https://github.com/abcdef')).to.be.false;
|
|
||||||
expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false;
|
|
||||||
})
|
|
||||||
|
|
||||||
it('matches by path with wildcard', () => {
|
|
||||||
let blacklist = ['github.com/abc*'];
|
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com/abc')).to.be.true;
|
|
||||||
expect(includes(blacklist, 'https://github.com/abcdef')).to.be.true;
|
|
||||||
expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false;
|
|
||||||
})
|
|
||||||
|
|
||||||
it('matches address and port', () => {
|
|
||||||
let blacklist = ['127.0.0.1:8888'];
|
|
||||||
|
|
||||||
expect(includes(blacklist, 'http://127.0.0.1:8888/')).to.be.true;
|
|
||||||
expect(includes(blacklist, 'http://127.0.0.1:8888/hello')).to.be.true;
|
|
||||||
})
|
|
||||||
});
|
|
|
@ -1,18 +0,0 @@
|
||||||
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,18 +0,0 @@
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
77
test/shared/settings/Blacklist.test.ts
Normal file
77
test/shared/settings/Blacklist.test.ts
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import Blacklist from '../../../src/shared/settings/Blacklist';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
describe('Blacklist', () => {
|
||||||
|
describe('fromJSON', () => {
|
||||||
|
it('returns empty array by empty settings', () => {
|
||||||
|
let blacklist = Blacklist.fromJSON([]);
|
||||||
|
expect(blacklist.toJSON()).to.be.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns blacklist by valid settings', () => {
|
||||||
|
let blacklist = Blacklist.fromJSON([
|
||||||
|
'github.com',
|
||||||
|
'circleci.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(blacklist.toJSON()).to.deep.equal([
|
||||||
|
'github.com',
|
||||||
|
'circleci.com',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws a TypeError by invalid settings', () => {
|
||||||
|
expect(() => Blacklist.fromJSON(null)).to.throw(TypeError);
|
||||||
|
expect(() => Blacklist.fromJSON({})).to.throw(TypeError);
|
||||||
|
expect(() => Blacklist.fromJSON([1,2,3])).to.throw(TypeError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#includes', () => {
|
||||||
|
it('matches by *', () => {
|
||||||
|
let blacklist = new Blacklist(['*']);
|
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com/abc')).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches by hostname', () => {
|
||||||
|
let blacklist = new Blacklist(['github.com']);
|
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com')).to.be.true;
|
||||||
|
expect(blacklist.includes('https://gist.github.com')).to.be.false;
|
||||||
|
expect(blacklist.includes('https://github.com/ueokande')).to.be.true;
|
||||||
|
expect(blacklist.includes('https://github.org')).to.be.false;
|
||||||
|
expect(blacklist.includes('https://google.com/search?q=github.org')).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches by hostname with wildcard', () => {
|
||||||
|
let blacklist = new Blacklist(['*.github.com']);
|
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com')).to.be.false;
|
||||||
|
expect(blacklist.includes('https://gist.github.com')).to.be.true;
|
||||||
|
})
|
||||||
|
|
||||||
|
it('matches by path', () => {
|
||||||
|
let blacklist = new Blacklist(['github.com/abc']);
|
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com/abc')).to.be.true;
|
||||||
|
expect(blacklist.includes('https://github.com/abcdef')).to.be.false;
|
||||||
|
expect(blacklist.includes('https://gist.github.com/abc')).to.be.false;
|
||||||
|
})
|
||||||
|
|
||||||
|
it('matches by path with wildcard', () => {
|
||||||
|
let blacklist = new Blacklist(['github.com/abc*']);
|
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com/abc')).to.be.true;
|
||||||
|
expect(blacklist.includes('https://github.com/abcdef')).to.be.true;
|
||||||
|
expect(blacklist.includes('https://gist.github.com/abc')).to.be.false;
|
||||||
|
})
|
||||||
|
|
||||||
|
it('matches address and port', () => {
|
||||||
|
let blacklist = new Blacklist(['127.0.0.1:8888']);
|
||||||
|
|
||||||
|
expect(blacklist.includes('http://127.0.0.1:8888/')).to.be.true;
|
||||||
|
expect(blacklist.includes('http://127.0.0.1:8888/hello')).to.be.true;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
92
test/shared/settings/Key.test.ts
Normal file
92
test/shared/settings/Key.test.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import Key from '../../../src/shared/settings/Key';
|
||||||
|
|
||||||
|
describe("Key", () => {
|
||||||
|
describe('fromMapKey', () => {
|
||||||
|
it('return for X', () => {
|
||||||
|
let key = Key.fromMapKey('x');
|
||||||
|
expect(key.key).to.equal('x');
|
||||||
|
expect(key.shift).to.be.false;
|
||||||
|
expect(key.ctrl).to.be.false;
|
||||||
|
expect(key.alt).to.be.false;
|
||||||
|
expect(key.meta).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return for Shift+X', () => {
|
||||||
|
let key = Key.fromMapKey('X');
|
||||||
|
expect(key.key).to.equal('X');
|
||||||
|
expect(key.shift).to.be.true;
|
||||||
|
expect(key.ctrl).to.be.false;
|
||||||
|
expect(key.alt).to.be.false;
|
||||||
|
expect(key.meta).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return for Ctrl+X', () => {
|
||||||
|
let key = Key.fromMapKey('<C-X>');
|
||||||
|
expect(key.key).to.equal('x');
|
||||||
|
expect(key.shift).to.be.false;
|
||||||
|
expect(key.ctrl).to.be.true;
|
||||||
|
expect(key.alt).to.be.false;
|
||||||
|
expect(key.meta).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns for Ctrl+Meta+X', () => {
|
||||||
|
let key = Key.fromMapKey('<C-M-X>');
|
||||||
|
expect(key.key).to.equal('x');
|
||||||
|
expect(key.shift).to.be.false;
|
||||||
|
expect(key.ctrl).to.be.true;
|
||||||
|
expect(key.alt).to.be.false;
|
||||||
|
expect(key.meta).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns for Ctrl+Shift+x', () => {
|
||||||
|
let key = Key.fromMapKey('<C-S-x>');
|
||||||
|
expect(key.key).to.equal('X');
|
||||||
|
expect(key.shift).to.be.true;
|
||||||
|
expect(key.ctrl).to.be.true;
|
||||||
|
expect(key.alt).to.be.false;
|
||||||
|
expect(key.meta).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns for Shift+Esc', () => {
|
||||||
|
let key = Key.fromMapKey('<S-Esc>');
|
||||||
|
expect(key.key).to.equal('Esc');
|
||||||
|
expect(key.shift).to.be.true;
|
||||||
|
expect(key.ctrl).to.be.false;
|
||||||
|
expect(key.alt).to.be.false;
|
||||||
|
expect(key.meta).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns for Ctrl+Esc', () => {
|
||||||
|
let key = Key.fromMapKey('<C-Esc>');
|
||||||
|
expect(key.key).to.equal('Esc');
|
||||||
|
expect(key.shift).to.be.false;
|
||||||
|
expect(key.ctrl).to.be.true;
|
||||||
|
expect(key.alt).to.be.false;
|
||||||
|
expect(key.meta).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns for Ctrl+Esc', () => {
|
||||||
|
let key = Key.fromMapKey('<C-Space>');
|
||||||
|
expect(key.key).to.equal('Space');
|
||||||
|
expect(key.shift).to.be.false;
|
||||||
|
expect(key.ctrl).to.be.true;
|
||||||
|
expect(key.alt).to.be.false;
|
||||||
|
expect(key.meta).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('equals', () => {
|
||||||
|
expect(new Key({
|
||||||
|
key: 'x', shift: false, ctrl: true, alt: false, meta: false,
|
||||||
|
}).equals(new Key({
|
||||||
|
key: 'x', shift: false, ctrl: true, alt: false, meta: false,
|
||||||
|
}))).to.be.true;
|
||||||
|
|
||||||
|
expect(new Key({
|
||||||
|
key: 'x', shift: false, ctrl: false, alt: false, meta: false,
|
||||||
|
}).equals(new Key({
|
||||||
|
key: 'X', shift: true, ctrl: false, alt: false, meta: false,
|
||||||
|
}))).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
72
test/shared/settings/KeySequence.test.ts
Normal file
72
test/shared/settings/KeySequence.test.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import KeySequence from '../../../src/shared/settings/KeySequence';
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import Key from "../../../src/shared/settings/Key";
|
||||||
|
|
||||||
|
describe("KeySequence", () => {
|
||||||
|
describe('#push', () => {
|
||||||
|
it('append a key to the sequence', () => {
|
||||||
|
let seq = new KeySequence([]);
|
||||||
|
seq.push(Key.fromMapKey('g'));
|
||||||
|
seq.push(Key.fromMapKey('<S-U>'));
|
||||||
|
|
||||||
|
expect(seq.keys[0].key).to.equal('g');
|
||||||
|
expect(seq.keys[1].key).to.equal('U');
|
||||||
|
expect(seq.keys[1].shift).to.be.true;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#startsWith', () => {
|
||||||
|
it('returns true if the key sequence starts with param', () => {
|
||||||
|
let seq = new KeySequence([
|
||||||
|
Key.fromMapKey('g'),
|
||||||
|
Key.fromMapKey('<S-U>'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(seq.startsWith(new KeySequence([
|
||||||
|
]))).to.be.true;
|
||||||
|
expect(seq.startsWith(new KeySequence([
|
||||||
|
Key.fromMapKey('g'),
|
||||||
|
]))).to.be.true;
|
||||||
|
expect(seq.startsWith(new KeySequence([
|
||||||
|
Key.fromMapKey('g'), Key.fromMapKey('<S-U>'),
|
||||||
|
]))).to.be.true;
|
||||||
|
expect(seq.startsWith(new KeySequence([
|
||||||
|
Key.fromMapKey('g'), Key.fromMapKey('<S-U>'), Key.fromMapKey('x'),
|
||||||
|
]))).to.be.false;
|
||||||
|
expect(seq.startsWith(new KeySequence([
|
||||||
|
Key.fromMapKey('h'),
|
||||||
|
]))).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if the empty sequence starts with an empty sequence', () => {
|
||||||
|
let seq = new KeySequence([]);
|
||||||
|
|
||||||
|
expect(seq.startsWith(new KeySequence([]))).to.be.true;
|
||||||
|
expect(seq.startsWith(new KeySequence([
|
||||||
|
Key.fromMapKey('h'),
|
||||||
|
]))).to.be.false;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#fromMapKeys', () => {
|
||||||
|
it('returns mapped keys for Shift+Esc', () => {
|
||||||
|
let keys = KeySequence.fromMapKeys('<S-Esc>').keys;
|
||||||
|
expect(keys).to.have.lengthOf(1);
|
||||||
|
expect(keys[0].key).to.equal('Esc');
|
||||||
|
expect(keys[0].shift).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns mapped keys for a<C-B><A-C>d<M-e>', () => {
|
||||||
|
let keys = KeySequence.fromMapKeys('a<C-B><A-C>d<M-e>').keys;
|
||||||
|
expect(keys).to.have.lengthOf(5);
|
||||||
|
expect(keys[0].key).to.equal('a');
|
||||||
|
expect(keys[1].ctrl).to.be.true;
|
||||||
|
expect(keys[1].key).to.equal('b');
|
||||||
|
expect(keys[2].alt).to.be.true;
|
||||||
|
expect(keys[2].key).to.equal('c');
|
||||||
|
expect(keys[3].key).to.equal('d');
|
||||||
|
expect(keys[4].meta).to.be.true;
|
||||||
|
expect(keys[4].key).to.equal('e');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
66
test/shared/settings/Keymaps.test.ts
Normal file
66
test/shared/settings/Keymaps.test.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import Keymaps from '../../../src/shared/settings/Keymaps';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
describe('Keymaps', () => {
|
||||||
|
describe('#valueOf', () => {
|
||||||
|
it('returns empty object by empty settings', () => {
|
||||||
|
let keymaps = Keymaps.fromJSON({}).toJSON();
|
||||||
|
expect(keymaps).to.be.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns keymaps by valid settings', () => {
|
||||||
|
let keymaps = Keymaps.fromJSON({
|
||||||
|
k: { type: "scroll.vertically", count: -1 },
|
||||||
|
j: { type: "scroll.vertically", count: 1 },
|
||||||
|
}).toJSON();
|
||||||
|
|
||||||
|
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(() => Keymaps.fromJSON(null)).to.throw(TypeError);
|
||||||
|
expect(() => Keymaps.fromJSON({
|
||||||
|
k: { type: "invalid.operation" },
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#combine', () => {
|
||||||
|
it('returns combined keymaps', () => {
|
||||||
|
let keymaps = Keymaps.fromJSON({
|
||||||
|
k: { type: "scroll.vertically", count: -1 },
|
||||||
|
j: { type: "scroll.vertically", count: 1 },
|
||||||
|
}).combine(Keymaps.fromJSON({
|
||||||
|
n: { type: "find.next" },
|
||||||
|
N: { type: "find.prev" },
|
||||||
|
}));
|
||||||
|
|
||||||
|
let entries = keymaps.entries().sort(([name1], [name2]) => name1.localeCompare(name2));
|
||||||
|
expect(entries).deep.equals([
|
||||||
|
['j', { type: "scroll.vertically", count: 1 }],
|
||||||
|
['k', { type: "scroll.vertically", count: -1 }],
|
||||||
|
['n', { type: "find.next" }],
|
||||||
|
['N', { type: "find.prev" }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('overrides current keymaps', () => {
|
||||||
|
let keymaps = Keymaps.fromJSON({
|
||||||
|
k: { type: "scroll.vertically", count: -1 },
|
||||||
|
j: { type: "scroll.vertically", count: 1 },
|
||||||
|
}).combine(Keymaps.fromJSON({
|
||||||
|
n: { type: "find.next" },
|
||||||
|
j: { type: "find.prev" },
|
||||||
|
}));
|
||||||
|
|
||||||
|
let entries = keymaps.entries().sort(([name1], [name2]) => name1.localeCompare(name2));
|
||||||
|
expect(entries).deep.equals([
|
||||||
|
['j', { type: "find.prev" }],
|
||||||
|
['k', { type: "scroll.vertically", count: -1 }],
|
||||||
|
['n', { type: "find.next" }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
30
test/shared/settings/Properties.test.ts
Normal file
30
test/shared/settings/Properties.test.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import Properties from '../../../src/shared/settings/Properties';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
describe('Properties', () => {
|
||||||
|
describe('#propertiesValueOf', () => {
|
||||||
|
it('returns with default properties by empty settings', () => {
|
||||||
|
let props = Properties.fromJSON({});
|
||||||
|
expect(props).to.deep.equal({
|
||||||
|
hintchars: "abcdefghijklmnopqrstuvwxyz",
|
||||||
|
smoothscroll: false,
|
||||||
|
complete: "sbh"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns properties by valid settings', () => {
|
||||||
|
let props = Properties.fromJSON({
|
||||||
|
hintchars: "abcdefgh",
|
||||||
|
smoothscroll: false,
|
||||||
|
complete: "sbh"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(props).to.deep.equal({
|
||||||
|
hintchars: "abcdefgh",
|
||||||
|
smoothscroll: false,
|
||||||
|
complete: "sbh"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
68
test/shared/settings/Search.test.ts
Normal file
68
test/shared/settings/Search.test.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import Search from '../../../src/shared/settings/Search';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
describe('Search', () => {
|
||||||
|
it('returns search settings by valid settings', () => {
|
||||||
|
let search = Search.fromJSON({
|
||||||
|
default: 'google',
|
||||||
|
engines: {
|
||||||
|
'google': 'https://google.com/search?q={}',
|
||||||
|
'yahoo': 'https://search.yahoo.com/search?p={}',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(search.defaultEngine).to.equal('google')
|
||||||
|
expect(search.engines).to.deep.equals({
|
||||||
|
'google': 'https://google.com/search?q={}',
|
||||||
|
'yahoo': 'https://search.yahoo.com/search?p={}',
|
||||||
|
});
|
||||||
|
expect(search.toJSON()).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(() => Search.fromJSON(null)).to.throw(TypeError);
|
||||||
|
expect(() => Search.fromJSON({})).to.throw(TypeError);
|
||||||
|
expect(() => Search.fromJSON([])).to.throw(TypeError);
|
||||||
|
expect(() => Search.fromJSON({
|
||||||
|
default: 123,
|
||||||
|
engines: {}
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
expect(() => Search.fromJSON({
|
||||||
|
default: 'google',
|
||||||
|
engines: {
|
||||||
|
'google': 123456,
|
||||||
|
}
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
expect(() => Search.fromJSON({
|
||||||
|
default: 'wikipedia',
|
||||||
|
engines: {
|
||||||
|
'google': 'https://google.com/search?q={}',
|
||||||
|
'yahoo': 'https://search.yahoo.com/search?p={}',
|
||||||
|
}
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
expect(() => Search.fromJSON({
|
||||||
|
default: 'g o o g l e',
|
||||||
|
engines: {
|
||||||
|
'g o o g l e': 'https://google.com/search?q={}',
|
||||||
|
}
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
expect(() => Search.fromJSON({
|
||||||
|
default: 'google',
|
||||||
|
engines: {
|
||||||
|
'google': 'https://google.com/search',
|
||||||
|
}
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
expect(() => Search.fromJSON({
|
||||||
|
default: 'google',
|
||||||
|
engines: {
|
||||||
|
'google': 'https://google.com/search?q={}&r={}',
|
||||||
|
}
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
});
|
||||||
|
});
|
54
test/shared/settings/Settings.test.ts
Normal file
54
test/shared/settings/Settings.test.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import Settings from '../../../src/shared/settings/Settings';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
describe('Settings', () => {
|
||||||
|
describe('#valueOf', () => {
|
||||||
|
it('returns settings by valid settings', () => {
|
||||||
|
let x = Settings.fromJSON({
|
||||||
|
keymaps: {},
|
||||||
|
"search": {
|
||||||
|
"default": "google",
|
||||||
|
"engines": {
|
||||||
|
"google": "https://google.com/search?q={}",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {},
|
||||||
|
"blacklist": []
|
||||||
|
});
|
||||||
|
|
||||||
|
expect({
|
||||||
|
keymaps: x.keymaps.toJSON(),
|
||||||
|
search: x.search.toJSON(),
|
||||||
|
properties: x.properties.toJSON(),
|
||||||
|
blacklist: x.blacklist.toJSON(),
|
||||||
|
}).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.fromJSON({});
|
||||||
|
expect(value.keymaps.toJSON()).to.not.be.empty;
|
||||||
|
expect(value.properties.toJSON()).to.not.be.empty;
|
||||||
|
expect(value.search.defaultEngine).to.be.a('string');
|
||||||
|
expect(value.search.engines).to.be.an('object');
|
||||||
|
expect(value.blacklist.toJSON()).to.be.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws a TypeError with an unknown field', () => {
|
||||||
|
expect(() => Settings.fromJSON({ name: 'alice' })).to.throw(TypeError)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,14 +1,16 @@
|
||||||
import * as parsers from 'shared/urls';
|
import * as parsers from '../../src/shared/urls';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import Search from '../../src/shared/settings/Search';
|
||||||
|
|
||||||
describe("shared/commands/parsers", () => {
|
describe("shared/commands/parsers", () => {
|
||||||
describe('#searchUrl', () => {
|
describe('#searchUrl', () => {
|
||||||
const config = {
|
const config = Search.fromJSON({
|
||||||
default: 'google',
|
default: 'google',
|
||||||
engines: {
|
engines: {
|
||||||
google: 'https://google.com/search?q={}',
|
google: 'https://google.com/search?q={}',
|
||||||
yahoo: 'https://yahoo.com/search?q={}',
|
yahoo: 'https://yahoo.com/search?q={}',
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
it('convertes search url', () => {
|
it('convertes search url', () => {
|
||||||
expect(parsers.searchUrl('google.com', config))
|
expect(parsers.searchUrl('google.com', config))
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import * as re from 'shared/utils/re';
|
|
||||||
|
|
||||||
describe("re util", () => {
|
|
||||||
it('matches by pattern', () => {
|
|
||||||
let regex = re.fromWildcard('*.example.com/*');
|
|
||||||
expect('foo.example.com/bar').to.match(regex);
|
|
||||||
expect('foo.example.com').not.to.match(regex);
|
|
||||||
expect('example.com/bar').not.to.match(regex);
|
|
||||||
|
|
||||||
regex = re.fromWildcard('example.com/*')
|
|
||||||
expect('example.com/foo').to.match(regex);
|
|
||||||
expect('example.com/').to.match(regex);
|
|
||||||
|
|
||||||
regex = re.fromWildcard('example.com/*bar')
|
|
||||||
expect('example.com/foobar').to.match(regex);
|
|
||||||
expect('example.com/bar').to.match(regex);
|
|
||||||
expect('example.com/foobarfoo').not.to.match(regex);
|
|
||||||
})
|
|
||||||
});
|
|
Reference in a new issue