Types src/content
This commit is contained in:
parent
992b3ac65d
commit
d01db82c0d
62 changed files with 1411 additions and 468 deletions
|
@ -35,6 +35,7 @@
|
||||||
"indent": ["error", 2],
|
"indent": ["error", 2],
|
||||||
"jsx-quotes": ["error", "prefer-single"],
|
"jsx-quotes": ["error", "prefer-single"],
|
||||||
"max-classes-per-file": "off",
|
"max-classes-per-file": "off",
|
||||||
|
"max-lines": "off",
|
||||||
"max-params": ["error", 5],
|
"max-params": ["error", 5],
|
||||||
"max-statements": ["error", 15],
|
"max-statements": ["error", 15],
|
||||||
"multiline-comment-style": "off",
|
"multiline-comment-style": "off",
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
"no-console": ["error", { "allow": ["warn", "error"] }],
|
"no-console": ["error", { "allow": ["warn", "error"] }],
|
||||||
"no-continue": "off",
|
"no-continue": "off",
|
||||||
"no-empty-function": "off",
|
"no-empty-function": "off",
|
||||||
|
"no-extra-parens": "off",
|
||||||
"no-magic-numbers": "off",
|
"no-magic-numbers": "off",
|
||||||
"no-mixed-operators": "off",
|
"no-mixed-operators": "off",
|
||||||
"no-plusplus": "off",
|
"no-plusplus": "off",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"build": "NODE_ENV=production webpack --mode production --progress --display-error-details",
|
"build": "NODE_ENV=production webpack --mode production --progress --display-error-details",
|
||||||
"package": "npm run build && script/package",
|
"package": "npm run build && script/package",
|
||||||
"lint": "eslint --ext .js,.jsx,.ts,.tsx src",
|
"lint": "eslint --ext .js,.jsx,.ts,.tsx src",
|
||||||
|
"type-checks": "tsc",
|
||||||
"test": "karma start",
|
"test": "karma start",
|
||||||
"test:e2e": "mocha --timeout 8000 e2e"
|
"test:e2e": "mocha --timeout 8000 e2e"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import operations from '../../shared/operations';
|
import * as operations from '../../shared/operations';
|
||||||
import FindUseCase from '../usecases/FindUseCase';
|
import FindUseCase from '../usecases/FindUseCase';
|
||||||
import ConsoleUseCase from '../usecases/ConsoleUseCase';
|
import ConsoleUseCase from '../usecases/ConsoleUseCase';
|
||||||
import TabUseCase from '../usecases/TabUseCase';
|
import TabUseCase from '../usecases/TabUseCase';
|
||||||
|
@ -25,7 +25,7 @@ export default class OperationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line complexity, max-lines-per-function
|
// eslint-disable-next-line complexity, max-lines-per-function
|
||||||
exec(operation: any): Promise<any> {
|
exec(operation: operations.Operation): Promise<any> {
|
||||||
switch (operation.type) {
|
switch (operation.type) {
|
||||||
case operations.TAB_CLOSE:
|
case operations.TAB_CLOSE:
|
||||||
return this.tabUseCase.close(false);
|
return this.tabUseCase.close(false);
|
||||||
|
|
|
@ -7,7 +7,7 @@ export default class VersionController {
|
||||||
this.versionUseCase = new VersionUseCase();
|
this.versionUseCase = new VersionUseCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(): void {
|
notify(): Promise<void> {
|
||||||
return this.versionUseCase.notify();
|
return this.versionUseCase.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,30 @@
|
||||||
import DefaultSettings from '../../shared/settings/default';
|
import DefaultSettings from '../../shared/settings/default';
|
||||||
import * as settingsValues from '../../shared/settings/values';
|
import * as settingsValues from '../../shared/settings/values';
|
||||||
|
|
||||||
|
type SettingValue = {
|
||||||
|
source: string,
|
||||||
|
json: string,
|
||||||
|
form: any
|
||||||
|
}
|
||||||
|
|
||||||
export default class Setting {
|
export default class Setting {
|
||||||
constructor({ source, json, form }) {
|
private obj: SettingValue;
|
||||||
|
|
||||||
|
constructor({ source, json, form }: SettingValue) {
|
||||||
this.obj = {
|
this.obj = {
|
||||||
source, json, form
|
source, json, form
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get source() {
|
get source(): string {
|
||||||
return this.obj.source;
|
return this.obj.source;
|
||||||
}
|
}
|
||||||
|
|
||||||
get json() {
|
get json(): string {
|
||||||
return this.obj.json;
|
return this.obj.json;
|
||||||
}
|
}
|
||||||
|
|
||||||
get form() {
|
get form(): any {
|
||||||
return this.obj.form;
|
return this.obj.form;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,11 +41,11 @@ export default class Setting {
|
||||||
return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value };
|
return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value };
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize() {
|
serialize(): SettingValue {
|
||||||
return this.obj;
|
return this.obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
static deserialize(obj) {
|
static deserialize(obj: SettingValue): Setting {
|
||||||
return new Setting({ source: obj.source, json: obj.json, form: obj.form });
|
return new Setting({ source: obj.source, json: obj.json, form: obj.form });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
export default class ConsoleClient {
|
export default class ConsoleClient {
|
||||||
showCommand(tabId: number, command: string): Promise<any> {
|
showCommand(tabId: number, command: string): Promise<any> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
export default class ContentMessageClient {
|
export default class ContentMessageClient {
|
||||||
async broadcastSettingsChanged(): Promise<void> {
|
async broadcastSettingsChanged(): Promise<void> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
import CompletionGroup from '../domains/CompletionGroup';
|
import CompletionGroup from '../domains/CompletionGroup';
|
||||||
import CommandController from '../controllers/CommandController';
|
import CommandController from '../controllers/CommandController';
|
||||||
import SettingController from '../controllers/SettingController';
|
import SettingController from '../controllers/SettingController';
|
||||||
|
@ -68,7 +68,9 @@ export default class ContentMessageListener {
|
||||||
browser.runtime.onConnect.addListener(this.onConnected.bind(this));
|
browser.runtime.onConnect.addListener(this.onConnected.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message: any, senderTab: browser.tabs.Tab): Promise<any> | any {
|
onMessage(
|
||||||
|
message: messages.Message, senderTab: browser.tabs.Tab,
|
||||||
|
): Promise<any> | any {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.CONSOLE_QUERY_COMPLETIONS:
|
case messages.CONSOLE_QUERY_COMPLETIONS:
|
||||||
return this.onConsoleQueryCompletions(message.text);
|
return this.onConsoleQueryCompletions(message.text);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
const NOTIFICATION_ID = 'vimvixen-update';
|
const NOTIFICATION_ID = 'vimvixen-update';
|
||||||
|
|
||||||
export default class NotifyPresenter {
|
export default class NotifyPresenter {
|
||||||
notify(
|
async notify(
|
||||||
title: string,
|
title: string,
|
||||||
message: string,
|
message: string,
|
||||||
onclick: () => void,
|
onclick: () => void,
|
||||||
): Promise<string> {
|
): Promise<void> {
|
||||||
const listener = (id: string) => {
|
const listener = (id: string) => {
|
||||||
if (id !== NOTIFICATION_ID) {
|
if (id !== NOTIFICATION_ID) {
|
||||||
return;
|
return;
|
||||||
|
@ -17,7 +17,7 @@ export default class NotifyPresenter {
|
||||||
};
|
};
|
||||||
browser.notifications.onClicked.addListener(listener);
|
browser.notifications.onClicked.addListener(listener);
|
||||||
|
|
||||||
return browser.notifications.create(NOTIFICATION_ID, {
|
await browser.notifications.create(NOTIFICATION_ID, {
|
||||||
'type': 'basic',
|
'type': 'basic',
|
||||||
'iconUrl': browser.extension.getURL('resources/icon_48x48.png'),
|
'iconUrl': browser.extension.getURL('resources/icon_48x48.png'),
|
||||||
title,
|
title,
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default class VersionUseCase {
|
||||||
this.notifyPresenter = new NotifyPresenter();
|
this.notifyPresenter = new NotifyPresenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(): Promise<string> {
|
notify(): Promise<void> {
|
||||||
let title = `Vim Vixen ${manifest.version} has been installed`;
|
let title = `Vim Vixen ${manifest.version} has been installed`;
|
||||||
let message = 'Click here to see release notes';
|
let message = 'Click here to see release notes';
|
||||||
let url = this.releaseNoteUrl(manifest.version);
|
let url = this.releaseNoteUrl(manifest.version);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
import * as actions from './index';
|
import * as actions from './index';
|
||||||
|
|
||||||
const hide = (): actions.ConsoleAction => {
|
const hide = (): actions.ConsoleAction => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import messages from '../shared/messages';
|
import * as messages from '../shared/messages';
|
||||||
import reducers from './reducers';
|
import reducers from './reducers';
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
import promise from 'redux-promise';
|
import promise from 'redux-promise';
|
||||||
|
@ -23,15 +23,16 @@ window.addEventListener('load', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const onMessage = (message: any): any => {
|
const onMessage = (message: any): any => {
|
||||||
switch (message.type) {
|
let msg = messages.valueOf(message);
|
||||||
|
switch (msg.type) {
|
||||||
case messages.CONSOLE_SHOW_COMMAND:
|
case messages.CONSOLE_SHOW_COMMAND:
|
||||||
return store.dispatch(consoleActions.showCommand(message.command));
|
return store.dispatch(consoleActions.showCommand(msg.command));
|
||||||
case messages.CONSOLE_SHOW_FIND:
|
case messages.CONSOLE_SHOW_FIND:
|
||||||
return store.dispatch(consoleActions.showFind());
|
return store.dispatch(consoleActions.showFind());
|
||||||
case messages.CONSOLE_SHOW_ERROR:
|
case messages.CONSOLE_SHOW_ERROR:
|
||||||
return store.dispatch(consoleActions.showError(message.text));
|
return store.dispatch(consoleActions.showError(msg.text));
|
||||||
case messages.CONSOLE_SHOW_INFO:
|
case messages.CONSOLE_SHOW_INFO:
|
||||||
return store.dispatch(consoleActions.showInfo(message.text));
|
return store.dispatch(consoleActions.showInfo(msg.text));
|
||||||
case messages.CONSOLE_HIDE:
|
case messages.CONSOLE_HIDE:
|
||||||
return store.dispatch(consoleActions.hide());
|
return store.dispatch(consoleActions.hide());
|
||||||
}
|
}
|
||||||
|
|
32
src/content/MessageListener.ts
Normal file
32
src/content/MessageListener.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { Message, valueOf } from '../shared/messages';
|
||||||
|
|
||||||
|
export type WebMessageSender = Window | MessagePort | ServiceWorker | null;
|
||||||
|
export type WebExtMessageSender = browser.runtime.MessageSender;
|
||||||
|
|
||||||
|
export default class MessageListener {
|
||||||
|
onWebMessage(
|
||||||
|
listener: (msg: Message, sender: WebMessageSender) => void,
|
||||||
|
) {
|
||||||
|
window.addEventListener('message', (event: MessageEvent) => {
|
||||||
|
let sender = event.source;
|
||||||
|
let message = null;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(event.data);
|
||||||
|
} catch (e) {
|
||||||
|
// ignore unexpected message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listener(message, sender);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackgroundMessage(
|
||||||
|
listener: (msg: Message, sender: WebExtMessageSender) => any,
|
||||||
|
) {
|
||||||
|
browser.runtime.onMessage.addListener(
|
||||||
|
(msg: any, sender: WebExtMessageSender) => {
|
||||||
|
listener(valueOf(msg), sender);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
import actions from 'content/actions';
|
import * as actions from './index';
|
||||||
|
|
||||||
const enable = () => setEnabled(true);
|
const enable = (): Promise<actions.AddonAction> => setEnabled(true);
|
||||||
|
|
||||||
const disable = () => setEnabled(false);
|
const disable = (): Promise<actions.AddonAction> => setEnabled(false);
|
||||||
|
|
||||||
const setEnabled = async(enabled) => {
|
const setEnabled = async(enabled: boolean): Promise<actions.AddonAction> => {
|
||||||
await browser.runtime.sendMessage({
|
await browser.runtime.sendMessage({
|
||||||
type: messages.ADDON_ENABLED_RESPONSE,
|
type: messages.ADDON_ENABLED_RESPONSE,
|
||||||
enabled,
|
enabled,
|
||||||
|
|
|
@ -5,28 +5,41 @@
|
||||||
// NOTE: window.find is not standard API
|
// NOTE: window.find is not standard API
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/find
|
// https://developer.mozilla.org/en-US/docs/Web/API/Window/find
|
||||||
|
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
import actions from 'content/actions';
|
import * as actions from './index';
|
||||||
import * as consoleFrames from '../console-frames';
|
import * as consoleFrames from '../console-frames';
|
||||||
|
|
||||||
const find = (string, backwards) => {
|
const find = (str: string, backwards: boolean): boolean => {
|
||||||
let caseSensitive = false;
|
let caseSensitive = false;
|
||||||
let wrapScan = true;
|
let wrapScan = true;
|
||||||
|
|
||||||
|
|
||||||
// NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work
|
// NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work
|
||||||
// because of same origin policy
|
// because of same origin policy
|
||||||
let found = window.find(string, caseSensitive, backwards, wrapScan);
|
|
||||||
|
// eslint-disable-next-line no-extra-parens
|
||||||
|
let found = (<any>window).find(str, caseSensitive, backwards, wrapScan);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
window.getSelection().removeAllRanges();
|
let sel = window.getSelection();
|
||||||
return window.find(string, caseSensitive, backwards, wrapScan);
|
if (sel) {
|
||||||
|
sel.removeAllRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-extra-parens
|
||||||
|
return (<any>window).find(str, caseSensitive, backwards, wrapScan);
|
||||||
};
|
};
|
||||||
|
|
||||||
const findNext = async(currentKeyword, reset, backwards) => {
|
// eslint-disable-next-line max-statements
|
||||||
|
const findNext = async(
|
||||||
|
currentKeyword: string, reset: boolean, backwards: boolean,
|
||||||
|
): Promise<actions.FindAction> => {
|
||||||
if (reset) {
|
if (reset) {
|
||||||
window.getSelection().removeAllRanges();
|
let sel = window.getSelection();
|
||||||
|
if (sel) {
|
||||||
|
sel.removeAllRanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyword = currentKeyword;
|
let keyword = currentKeyword;
|
||||||
|
@ -41,7 +54,8 @@ const findNext = async(currentKeyword, reset, backwards) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!keyword) {
|
if (!keyword) {
|
||||||
return consoleFrames.postError('No previous search keywords');
|
await consoleFrames.postError('No previous search keywords');
|
||||||
|
return { type: actions.NOOP };
|
||||||
}
|
}
|
||||||
let found = find(keyword, backwards);
|
let found = find(keyword, backwards);
|
||||||
if (found) {
|
if (found) {
|
||||||
|
@ -57,11 +71,15 @@ const findNext = async(currentKeyword, reset, backwards) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const next = (currentKeyword, reset) => {
|
const next = (
|
||||||
|
currentKeyword: string, reset: boolean,
|
||||||
|
): Promise<actions.FindAction> => {
|
||||||
return findNext(currentKeyword, reset, false);
|
return findNext(currentKeyword, reset, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const prev = (currentKeyword, reset) => {
|
const prev = (
|
||||||
|
currentKeyword: string, reset: boolean,
|
||||||
|
): Promise<actions.FindAction> => {
|
||||||
return findNext(currentKeyword, reset, true);
|
return findNext(currentKeyword, reset, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from './index';
|
||||||
|
|
||||||
const enable = (newTab, background) => {
|
const enable = (
|
||||||
|
newTab: boolean, background: boolean,
|
||||||
|
): actions.FollowAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.FOLLOW_CONTROLLER_ENABLE,
|
type: actions.FOLLOW_CONTROLLER_ENABLE,
|
||||||
newTab,
|
newTab,
|
||||||
|
@ -8,20 +10,20 @@ const enable = (newTab, background) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const disable = () => {
|
const disable = (): actions.FollowAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.FOLLOW_CONTROLLER_DISABLE,
|
type: actions.FOLLOW_CONTROLLER_DISABLE,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const keyPress = (key) => {
|
const keyPress = (key: string): actions.FollowAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.FOLLOW_CONTROLLER_KEY_PRESS,
|
type: actions.FOLLOW_CONTROLLER_KEY_PRESS,
|
||||||
key: key
|
key: key
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const backspace = () => {
|
const backspace = (): actions.FollowAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.FOLLOW_CONTROLLER_BACKSPACE,
|
type: actions.FOLLOW_CONTROLLER_BACKSPACE,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,31 +1,120 @@
|
||||||
export default {
|
import Redux from 'redux';
|
||||||
// Enable/disable
|
|
||||||
ADDON_SET_ENABLED: 'addon.set.enabled',
|
|
||||||
|
|
||||||
// Settings
|
// Enable/disable
|
||||||
SETTING_SET: 'setting.set',
|
export const ADDON_SET_ENABLED = 'addon.set.enabled';
|
||||||
|
|
||||||
// User input
|
// Find
|
||||||
INPUT_KEY_PRESS: 'input.key.press',
|
export const FIND_SET_KEYWORD = 'find.set.keyword';
|
||||||
INPUT_CLEAR_KEYS: 'input.clear.keys',
|
|
||||||
|
|
||||||
// Completion
|
// Settings
|
||||||
COMPLETION_SET_ITEMS: 'completion.set.items',
|
export const SETTING_SET = 'setting.set';
|
||||||
COMPLETION_SELECT_NEXT: 'completions.select.next',
|
|
||||||
COMPLETION_SELECT_PREV: 'completions.select.prev',
|
|
||||||
|
|
||||||
// Follow
|
// User input
|
||||||
FOLLOW_CONTROLLER_ENABLE: 'follow.controller.enable',
|
export const INPUT_KEY_PRESS = 'input.key.press';
|
||||||
FOLLOW_CONTROLLER_DISABLE: 'follow.controller.disable',
|
export const INPUT_CLEAR_KEYS = 'input.clear.keys';
|
||||||
FOLLOW_CONTROLLER_KEY_PRESS: 'follow.controller.key.press',
|
|
||||||
FOLLOW_CONTROLLER_BACKSPACE: 'follow.controller.backspace',
|
|
||||||
|
|
||||||
// Find
|
// Completion
|
||||||
FIND_SET_KEYWORD: 'find.set.keyword',
|
export const COMPLETION_SET_ITEMS = 'completion.set.items';
|
||||||
|
export const COMPLETION_SELECT_NEXT = 'completions.select.next';
|
||||||
|
export const COMPLETION_SELECT_PREV = 'completions.select.prev';
|
||||||
|
|
||||||
// Mark
|
// Follow
|
||||||
MARK_START_SET: 'mark.start.set',
|
export const FOLLOW_CONTROLLER_ENABLE = 'follow.controller.enable';
|
||||||
MARK_START_JUMP: 'mark.start.jump',
|
export const FOLLOW_CONTROLLER_DISABLE = 'follow.controller.disable';
|
||||||
MARK_CANCEL: 'mark.cancel',
|
export const FOLLOW_CONTROLLER_KEY_PRESS = 'follow.controller.key.press';
|
||||||
MARK_SET_LOCAL: 'mark.set.local',
|
export const FOLLOW_CONTROLLER_BACKSPACE = 'follow.controller.backspace';
|
||||||
};
|
|
||||||
|
// Mark
|
||||||
|
export const MARK_START_SET = 'mark.start.set';
|
||||||
|
export const MARK_START_JUMP = 'mark.start.jump';
|
||||||
|
export const MARK_CANCEL = 'mark.cancel';
|
||||||
|
export const MARK_SET_LOCAL = 'mark.set.local';
|
||||||
|
|
||||||
|
export const NOOP = 'noop';
|
||||||
|
|
||||||
|
export interface AddonSetEnabledAction extends Redux.Action {
|
||||||
|
type: typeof ADDON_SET_ENABLED;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FindSetKeywordAction extends Redux.Action {
|
||||||
|
type: typeof FIND_SET_KEYWORD;
|
||||||
|
keyword: string;
|
||||||
|
found: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SettingSetAction extends Redux.Action {
|
||||||
|
type: typeof SETTING_SET;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InputKeyPressAction extends Redux.Action {
|
||||||
|
type: typeof INPUT_KEY_PRESS;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InputClearKeysAction extends Redux.Action {
|
||||||
|
type: typeof INPUT_CLEAR_KEYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowControllerEnableAction extends Redux.Action {
|
||||||
|
type: typeof FOLLOW_CONTROLLER_ENABLE;
|
||||||
|
newTab: boolean;
|
||||||
|
background: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowControllerDisableAction extends Redux.Action {
|
||||||
|
type: typeof FOLLOW_CONTROLLER_DISABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowControllerKeyPressAction extends Redux.Action {
|
||||||
|
type: typeof FOLLOW_CONTROLLER_KEY_PRESS;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowControllerBackspaceAction extends Redux.Action {
|
||||||
|
type: typeof FOLLOW_CONTROLLER_BACKSPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkStartSetAction extends Redux.Action {
|
||||||
|
type: typeof MARK_START_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkStartJumpAction extends Redux.Action {
|
||||||
|
type: typeof MARK_START_JUMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkCancelAction extends Redux.Action {
|
||||||
|
type: typeof MARK_CANCEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkSetLocalAction extends Redux.Action {
|
||||||
|
type: typeof MARK_SET_LOCAL;
|
||||||
|
key: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoopAction extends Redux.Action {
|
||||||
|
type: typeof NOOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddonAction = AddonSetEnabledAction;
|
||||||
|
export type FindAction = FindSetKeywordAction | NoopAction;
|
||||||
|
export type SettingAction = SettingSetAction;
|
||||||
|
export type InputAction = InputKeyPressAction | InputClearKeysAction;
|
||||||
|
export type FollowAction =
|
||||||
|
FollowControllerEnableAction | FollowControllerDisableAction |
|
||||||
|
FollowControllerKeyPressAction | FollowControllerBackspaceAction;
|
||||||
|
export type MarkAction =
|
||||||
|
MarkStartSetAction | MarkStartJumpAction |
|
||||||
|
MarkCancelAction | MarkSetLocalAction | NoopAction;
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
AddonAction |
|
||||||
|
FindAction |
|
||||||
|
SettingAction |
|
||||||
|
InputAction |
|
||||||
|
FollowAction |
|
||||||
|
MarkAction |
|
||||||
|
NoopAction;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from './index';
|
||||||
|
|
||||||
const keyPress = (key) => {
|
const keyPress = (key: string): actions.InputAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.INPUT_KEY_PRESS,
|
type: actions.INPUT_KEY_PRESS,
|
||||||
key,
|
key,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearKeys = () => {
|
const clearKeys = (): actions.InputAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.INPUT_CLEAR_KEYS
|
type: actions.INPUT_CLEAR_KEYS
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from './index';
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
const startSet = () => {
|
const startSet = (): actions.MarkAction => {
|
||||||
return { type: actions.MARK_START_SET };
|
return { type: actions.MARK_START_SET };
|
||||||
};
|
};
|
||||||
|
|
||||||
const startJump = () => {
|
const startJump = (): actions.MarkAction => {
|
||||||
return { type: actions.MARK_START_JUMP };
|
return { type: actions.MARK_START_JUMP };
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = (): actions.MarkAction => {
|
||||||
return { type: actions.MARK_CANCEL };
|
return { type: actions.MARK_CANCEL };
|
||||||
};
|
};
|
||||||
|
|
||||||
const setLocal = (key, x, y) => {
|
const setLocal = (key: string, x: number, y: number): actions.MarkAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.MARK_SET_LOCAL,
|
type: actions.MARK_SET_LOCAL,
|
||||||
key,
|
key,
|
||||||
|
@ -22,22 +22,22 @@ const setLocal = (key, x, y) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const setGlobal = (key, x, y) => {
|
const setGlobal = (key: string, x: number, y: number): actions.MarkAction => {
|
||||||
browser.runtime.sendMessage({
|
browser.runtime.sendMessage({
|
||||||
type: messages.MARK_SET_GLOBAL,
|
type: messages.MARK_SET_GLOBAL,
|
||||||
key,
|
key,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
return { type: '' };
|
return { type: actions.NOOP };
|
||||||
};
|
};
|
||||||
|
|
||||||
const jumpGlobal = (key) => {
|
const jumpGlobal = (key: string): actions.MarkAction => {
|
||||||
browser.runtime.sendMessage({
|
browser.runtime.sendMessage({
|
||||||
type: messages.MARK_JUMP_GLOBAL,
|
type: messages.MARK_JUMP_GLOBAL,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
return { type: '' };
|
return { type: actions.NOOP };
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
import operations from 'shared/operations';
|
import * as operations from '../../shared/operations';
|
||||||
import messages from 'shared/messages';
|
import * as actions from './index';
|
||||||
import * as scrolls from 'content/scrolls';
|
import * as messages from '../../shared/messages';
|
||||||
import * as navigates from 'content/navigates';
|
import * as scrolls from '../scrolls';
|
||||||
import * as focuses from 'content/focuses';
|
import * as navigates from '../navigates';
|
||||||
import * as urls from 'content/urls';
|
import * as focuses from '../focuses';
|
||||||
import * as consoleFrames from 'content/console-frames';
|
import * as urls from '../urls';
|
||||||
|
import * as consoleFrames from '../console-frames';
|
||||||
import * as addonActions from './addon';
|
import * as addonActions from './addon';
|
||||||
import * as markActions from './mark';
|
import * as markActions from './mark';
|
||||||
import * as properties from 'shared/settings/properties';
|
import * as properties from '../../shared/settings/properties';
|
||||||
|
|
||||||
// eslint-disable-next-line complexity, max-lines-per-function
|
// eslint-disable-next-line complexity, max-lines-per-function
|
||||||
const exec = (operation, settings, addonEnabled) => {
|
const exec = (
|
||||||
|
operation: operations.Operation,
|
||||||
|
settings: any,
|
||||||
|
addonEnabled: boolean,
|
||||||
|
): Promise<actions.Action> | actions.Action => {
|
||||||
let smoothscroll = settings.properties.smoothscroll ||
|
let smoothscroll = settings.properties.smoothscroll ||
|
||||||
properties.defaults.smoothscroll;
|
properties.defaults.smoothscroll;
|
||||||
switch (operation.type) {
|
switch (operation.type) {
|
||||||
|
@ -98,7 +103,7 @@ const exec = (operation, settings, addonEnabled) => {
|
||||||
operation,
|
operation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { type: '' };
|
return { type: actions.NOOP };
|
||||||
};
|
};
|
||||||
|
|
||||||
export { exec };
|
export { exec };
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from './index';
|
||||||
import * as keyUtils from 'shared/utils/keys';
|
import * as keyUtils from '../../shared/utils/keys';
|
||||||
import operations from 'shared/operations';
|
import * as operations from '../../shared/operations';
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
const reservedKeymaps = {
|
const reservedKeymaps = {
|
||||||
'<Esc>': { type: operations.CANCEL },
|
'<Esc>': { type: operations.CANCEL },
|
||||||
'<C-[>': { type: operations.CANCEL },
|
'<C-[>': { type: operations.CANCEL },
|
||||||
};
|
};
|
||||||
|
|
||||||
const set = (value) => {
|
const set = (value: any): actions.SettingAction => {
|
||||||
let entries = [];
|
let entries: any[] = [];
|
||||||
if (value.keymaps) {
|
if (value.keymaps) {
|
||||||
let keymaps = { ...value.keymaps, ...reservedKeymaps };
|
let keymaps = { ...value.keymaps, ...reservedKeymaps };
|
||||||
entries = Object.entries(keymaps).map((entry) => {
|
entries = Object.entries(keymaps).map((entry) => {
|
||||||
|
@ -27,7 +27,7 @@ const set = (value) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const load = async() => {
|
const load = async(): Promise<actions.SettingAction> => {
|
||||||
let settings = await browser.runtime.sendMessage({
|
let settings = await browser.runtime.sendMessage({
|
||||||
type: messages.SETTINGS_QUERY,
|
type: messages.SETTINGS_QUERY,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import messages from 'shared/messages';
|
import MessageListener from '../../MessageListener';
|
||||||
import Hint from './hint';
|
import Hint from './hint';
|
||||||
import * as dom from 'shared/utils/dom';
|
import * as dom from '../../../shared/utils/dom';
|
||||||
|
import * as messages from '../../../shared/messages';
|
||||||
|
import * as keyUtils from '../../../shared/utils/keys';
|
||||||
|
|
||||||
const TARGET_SELECTOR = [
|
const TARGET_SELECTOR = [
|
||||||
'a', 'button', 'input', 'textarea', 'area',
|
'a', 'button', 'input', 'textarea', 'area',
|
||||||
|
@ -8,8 +10,22 @@ const TARGET_SELECTOR = [
|
||||||
'[role="button"]', 'summary'
|
'[role="button"]', 'summary'
|
||||||
].join(',');
|
].join(',');
|
||||||
|
|
||||||
|
interface Size {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
const inViewport = (win, element, viewSize, framePosition) => {
|
interface Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inViewport = (
|
||||||
|
win: Window,
|
||||||
|
element: Element,
|
||||||
|
viewSize: Size,
|
||||||
|
framePosition: Point,
|
||||||
|
): boolean => {
|
||||||
let {
|
let {
|
||||||
top, left, bottom, right
|
top, left, bottom, right
|
||||||
} = dom.viewportRect(element);
|
} = dom.viewportRect(element);
|
||||||
|
@ -30,34 +46,44 @@ const inViewport = (win, element, viewSize, framePosition) => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAriaHiddenOrAriaDisabled = (win, element) => {
|
const isAriaHiddenOrAriaDisabled = (win: Window, element: Element): boolean => {
|
||||||
if (!element || win.document.documentElement === element) {
|
if (!element || win.document.documentElement === element) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (let attr of ['aria-hidden', 'aria-disabled']) {
|
for (let attr of ['aria-hidden', 'aria-disabled']) {
|
||||||
if (element.hasAttribute(attr)) {
|
let value = element.getAttribute(attr);
|
||||||
let hidden = element.getAttribute(attr).toLowerCase();
|
if (value !== null) {
|
||||||
|
let hidden = value.toLowerCase();
|
||||||
if (hidden === '' || hidden === 'true') {
|
if (hidden === '' || hidden === 'true') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return isAriaHiddenOrAriaDisabled(win, element.parentNode);
|
return isAriaHiddenOrAriaDisabled(win, element.parentElement as Element);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Follow {
|
export default class Follow {
|
||||||
constructor(win, store) {
|
private win: Window;
|
||||||
|
|
||||||
|
private newTab: boolean;
|
||||||
|
|
||||||
|
private background: boolean;
|
||||||
|
|
||||||
|
private hints: {[key: string]: Hint };
|
||||||
|
|
||||||
|
private targets: HTMLElement[] = [];
|
||||||
|
|
||||||
|
constructor(win: Window) {
|
||||||
this.win = win;
|
this.win = win;
|
||||||
this.store = store;
|
|
||||||
this.newTab = false;
|
this.newTab = false;
|
||||||
this.background = false;
|
this.background = false;
|
||||||
this.hints = {};
|
this.hints = {};
|
||||||
this.targets = [];
|
this.targets = [];
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this));
|
new MessageListener().onWebMessage(this.onMessage.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
key(key) {
|
key(key: keyUtils.Key): boolean {
|
||||||
if (Object.keys(this.hints).length === 0) {
|
if (Object.keys(this.hints).length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +95,7 @@ export default class Follow {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
openLink(element) {
|
openLink(element: HTMLAreaElement|HTMLAnchorElement) {
|
||||||
// Browser prevent new tab by link with target='_blank'
|
// Browser prevent new tab by link with target='_blank'
|
||||||
if (!this.newTab && element.getAttribute('target') !== '_blank') {
|
if (!this.newTab && element.getAttribute('target') !== '_blank') {
|
||||||
element.click();
|
element.click();
|
||||||
|
@ -90,7 +116,7 @@ export default class Follow {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
countHints(sender, viewSize, framePosition) {
|
countHints(sender: any, viewSize: Size, framePosition: Point) {
|
||||||
this.targets = Follow.getTargetElements(this.win, viewSize, framePosition);
|
this.targets = Follow.getTargetElements(this.win, viewSize, framePosition);
|
||||||
sender.postMessage(JSON.stringify({
|
sender.postMessage(JSON.stringify({
|
||||||
type: messages.FOLLOW_RESPONSE_COUNT_TARGETS,
|
type: messages.FOLLOW_RESPONSE_COUNT_TARGETS,
|
||||||
|
@ -98,7 +124,7 @@ export default class Follow {
|
||||||
}), '*');
|
}), '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
createHints(keysArray, newTab, background) {
|
createHints(keysArray: string[], newTab: boolean, background: boolean) {
|
||||||
if (keysArray.length !== this.targets.length) {
|
if (keysArray.length !== this.targets.length) {
|
||||||
throw new Error('illegal hint count');
|
throw new Error('illegal hint count');
|
||||||
}
|
}
|
||||||
|
@ -113,7 +139,7 @@ export default class Follow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showHints(keys) {
|
showHints(keys: string) {
|
||||||
Object.keys(this.hints).filter(key => key.startsWith(keys))
|
Object.keys(this.hints).filter(key => key.startsWith(keys))
|
||||||
.forEach(key => this.hints[key].show());
|
.forEach(key => this.hints[key].show());
|
||||||
Object.keys(this.hints).filter(key => !key.startsWith(keys))
|
Object.keys(this.hints).filter(key => !key.startsWith(keys))
|
||||||
|
@ -128,18 +154,19 @@ export default class Follow {
|
||||||
this.targets = [];
|
this.targets = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
activateHints(keys) {
|
activateHints(keys: string) {
|
||||||
let hint = this.hints[keys];
|
let hint = this.hints[keys];
|
||||||
if (!hint) {
|
if (!hint) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let element = hint.target;
|
let element = hint.getTarget();
|
||||||
switch (element.tagName.toLowerCase()) {
|
switch (element.tagName.toLowerCase()) {
|
||||||
case 'a':
|
case 'a':
|
||||||
|
return this.openLink(element as HTMLAnchorElement);
|
||||||
case 'area':
|
case 'area':
|
||||||
return this.openLink(element);
|
return this.openLink(element as HTMLAreaElement);
|
||||||
case 'input':
|
case 'input':
|
||||||
switch (element.type) {
|
switch ((element as HTMLInputElement).type) {
|
||||||
case 'file':
|
case 'file':
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
case 'radio':
|
case 'radio':
|
||||||
|
@ -166,7 +193,7 @@ export default class Follow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message, sender) {
|
onMessage(message: messages.Message, sender: any) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.FOLLOW_REQUEST_COUNT_TARGETS:
|
case messages.FOLLOW_REQUEST_COUNT_TARGETS:
|
||||||
return this.countHints(sender, message.viewSize, message.framePosition);
|
return this.countHints(sender, message.viewSize, message.framePosition);
|
||||||
|
@ -178,19 +205,23 @@ export default class Follow {
|
||||||
case messages.FOLLOW_ACTIVATE:
|
case messages.FOLLOW_ACTIVATE:
|
||||||
return this.activateHints(message.keys);
|
return this.activateHints(message.keys);
|
||||||
case messages.FOLLOW_REMOVE_HINTS:
|
case messages.FOLLOW_REMOVE_HINTS:
|
||||||
return this.removeHints(message.keys);
|
return this.removeHints();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTargetElements(win, viewSize, framePosition) {
|
static getTargetElements(
|
||||||
|
win: Window,
|
||||||
|
viewSize:
|
||||||
|
Size, framePosition: Point,
|
||||||
|
): HTMLElement[] {
|
||||||
let all = win.document.querySelectorAll(TARGET_SELECTOR);
|
let all = win.document.querySelectorAll(TARGET_SELECTOR);
|
||||||
let filtered = Array.prototype.filter.call(all, (element) => {
|
let filtered = Array.prototype.filter.call(all, (element: HTMLElement) => {
|
||||||
let style = win.getComputedStyle(element);
|
let style = win.getComputedStyle(element);
|
||||||
|
|
||||||
// AREA's 'display' in Browser style is 'none'
|
// AREA's 'display' in Browser style is 'none'
|
||||||
return (element.tagName === 'AREA' || style.display !== 'none') &&
|
return (element.tagName === 'AREA' || style.display !== 'none') &&
|
||||||
style.visibility !== 'hidden' &&
|
style.visibility !== 'hidden' &&
|
||||||
element.type !== 'hidden' &&
|
(element as HTMLInputElement).type !== 'hidden' &&
|
||||||
element.offsetHeight > 0 &&
|
element.offsetHeight > 0 &&
|
||||||
!isAriaHiddenOrAriaDisabled(win, element) &&
|
!isAriaHiddenOrAriaDisabled(win, element) &&
|
||||||
inViewport(win, element, viewSize, framePosition);
|
inViewport(win, element, viewSize, framePosition);
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import * as dom from 'shared/utils/dom';
|
import * as dom from '../../../shared/utils/dom';
|
||||||
|
|
||||||
const hintPosition = (element) => {
|
interface Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hintPosition = (element: Element): Point => {
|
||||||
let { left, top, right, bottom } = dom.viewportRect(element);
|
let { left, top, right, bottom } = dom.viewportRect(element);
|
||||||
|
|
||||||
if (element.tagName !== 'AREA') {
|
if (element.tagName !== 'AREA') {
|
||||||
|
@ -14,17 +19,21 @@ const hintPosition = (element) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Hint {
|
export default class Hint {
|
||||||
constructor(target, tag) {
|
private target: HTMLElement;
|
||||||
if (!(document.body instanceof HTMLElement)) {
|
|
||||||
throw new TypeError('target is not an HTMLElement');
|
private element: HTMLElement;
|
||||||
|
|
||||||
|
constructor(target: HTMLElement, tag: string) {
|
||||||
|
let doc = target.ownerDocument;
|
||||||
|
if (doc === null) {
|
||||||
|
throw new TypeError('ownerDocument is null');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.target = target;
|
|
||||||
|
|
||||||
let doc = target.ownerDocument;
|
|
||||||
let { x, y } = hintPosition(target);
|
let { x, y } = hintPosition(target);
|
||||||
let { scrollX, scrollY } = window;
|
let { scrollX, scrollY } = window;
|
||||||
|
|
||||||
|
this.target = target;
|
||||||
|
|
||||||
this.element = doc.createElement('span');
|
this.element = doc.createElement('span');
|
||||||
this.element.className = 'vimvixen-hint';
|
this.element.className = 'vimvixen-hint';
|
||||||
this.element.textContent = tag;
|
this.element.textContent = tag;
|
||||||
|
@ -35,15 +44,19 @@ export default class Hint {
|
||||||
doc.body.append(this.element);
|
doc.body.append(this.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show(): void {
|
||||||
this.element.style.display = 'inline';
|
this.element.style.display = 'inline';
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide(): void {
|
||||||
this.element.style.display = 'none';
|
this.element.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
remove() {
|
remove(): void {
|
||||||
this.element.remove();
|
this.element.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTarget(): HTMLElement {
|
||||||
|
return this.target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,33 +2,37 @@ import InputComponent from './input';
|
||||||
import FollowComponent from './follow';
|
import FollowComponent from './follow';
|
||||||
import MarkComponent from './mark';
|
import MarkComponent from './mark';
|
||||||
import KeymapperComponent from './keymapper';
|
import KeymapperComponent from './keymapper';
|
||||||
import * as settingActions from 'content/actions/setting';
|
import * as settingActions from '../../actions/setting';
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../../shared/messages';
|
||||||
|
import MessageListener from '../../MessageListener';
|
||||||
import * as addonActions from '../../actions/addon';
|
import * as addonActions from '../../actions/addon';
|
||||||
import * as blacklists from 'shared/blacklists';
|
import * as blacklists from '../../../shared/blacklists';
|
||||||
|
import * as keys from '../../../shared/utils/keys';
|
||||||
|
|
||||||
export default class Common {
|
export default class Common {
|
||||||
constructor(win, store) {
|
private win: Window;
|
||||||
const input = new InputComponent(win.document.body, store);
|
|
||||||
const follow = new FollowComponent(win, store);
|
private store: any;
|
||||||
|
|
||||||
|
constructor(win: Window, store: any) {
|
||||||
|
const input = new InputComponent(win.document.body);
|
||||||
|
const follow = new FollowComponent(win);
|
||||||
const mark = new MarkComponent(win.document.body, store);
|
const mark = new MarkComponent(win.document.body, store);
|
||||||
const keymapper = new KeymapperComponent(store);
|
const keymapper = new KeymapperComponent(store);
|
||||||
|
|
||||||
input.onKey(key => follow.key(key));
|
input.onKey((key: keys.Key) => follow.key(key));
|
||||||
input.onKey(key => mark.key(key));
|
input.onKey((key: keys.Key) => mark.key(key));
|
||||||
input.onKey(key => keymapper.key(key));
|
input.onKey((key: keys.Key) => keymapper.key(key));
|
||||||
|
|
||||||
this.win = win;
|
this.win = win;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.prevEnabled = undefined;
|
|
||||||
this.prevBlacklist = undefined;
|
|
||||||
|
|
||||||
this.reloadSettings();
|
this.reloadSettings();
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this));
|
new MessageListener().onBackgroundMessage(this.onMessage.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message) {
|
onMessage(message: messages.Message) {
|
||||||
let { enabled } = this.store.getState().addon;
|
let { enabled } = this.store.getState().addon;
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.SETTINGS_CHANGED:
|
case messages.SETTINGS_CHANGED:
|
||||||
|
@ -40,7 +44,8 @@ export default class Common {
|
||||||
|
|
||||||
reloadSettings() {
|
reloadSettings() {
|
||||||
try {
|
try {
|
||||||
this.store.dispatch(settingActions.load()).then(({ value: settings }) => {
|
this.store.dispatch(settingActions.load())
|
||||||
|
.then(({ value: settings }: any) => {
|
||||||
let enabled = !blacklists.includes(
|
let enabled = !blacklists.includes(
|
||||||
settings.blacklist, this.win.location.href
|
settings.blacklist, this.win.location.href
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import * as dom from 'shared/utils/dom';
|
import * as dom from '../../../shared/utils/dom';
|
||||||
import * as keys from 'shared/utils/keys';
|
import * as keys from '../../../shared/utils/keys';
|
||||||
|
|
||||||
const cancelKey = (e) => {
|
const cancelKey = (e: KeyboardEvent): boolean => {
|
||||||
return e.key === 'Escape' || e.key === '[' && e.ctrlKey;
|
return e.key === 'Escape' || e.key === '[' && e.ctrlKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class InputComponent {
|
export default class InputComponent {
|
||||||
constructor(target) {
|
private pressed: {[key: string]: string} = {};
|
||||||
|
|
||||||
|
private onKeyListeners: ((key: keys.Key) => boolean)[] = [];
|
||||||
|
|
||||||
|
constructor(target: HTMLElement) {
|
||||||
this.pressed = {};
|
this.pressed = {};
|
||||||
this.onKeyListeners = [];
|
this.onKeyListeners = [];
|
||||||
|
|
||||||
|
@ -15,11 +19,11 @@ export default class InputComponent {
|
||||||
target.addEventListener('keyup', this.onKeyUp.bind(this));
|
target.addEventListener('keyup', this.onKeyUp.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onKey(cb) {
|
onKey(cb: (key: keys.Key) => boolean) {
|
||||||
this.onKeyListeners.push(cb);
|
this.onKeyListeners.push(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyPress(e) {
|
onKeyPress(e: KeyboardEvent) {
|
||||||
if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {
|
if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +31,7 @@ export default class InputComponent {
|
||||||
this.capture(e);
|
this.capture(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(e) {
|
onKeyDown(e: KeyboardEvent) {
|
||||||
if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {
|
if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -35,14 +39,19 @@ export default class InputComponent {
|
||||||
this.capture(e);
|
this.capture(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyUp(e) {
|
onKeyUp(e: KeyboardEvent) {
|
||||||
delete this.pressed[e.key];
|
delete this.pressed[e.key];
|
||||||
}
|
}
|
||||||
|
|
||||||
capture(e) {
|
// eslint-disable-next-line max-statements
|
||||||
if (this.fromInput(e)) {
|
capture(e: KeyboardEvent) {
|
||||||
if (cancelKey(e) && e.target.blur) {
|
let target = e.target;
|
||||||
e.target.blur();
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.fromInput(target)) {
|
||||||
|
if (cancelKey(e) && target.blur) {
|
||||||
|
target.blur();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -63,13 +72,10 @@ export default class InputComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fromInput(e) {
|
fromInput(e: Element) {
|
||||||
if (!e.target) {
|
return e instanceof HTMLInputElement ||
|
||||||
return false;
|
e instanceof HTMLTextAreaElement ||
|
||||||
}
|
e instanceof HTMLSelectElement ||
|
||||||
return e.target instanceof HTMLInputElement ||
|
dom.isContentEditable(e);
|
||||||
e.target instanceof HTMLTextAreaElement ||
|
|
||||||
e.target instanceof HTMLSelectElement ||
|
|
||||||
dom.isContentEditable(e.target);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as inputActions from 'content/actions/input';
|
import * as inputActions from '../../actions/input';
|
||||||
import * as operationActions from 'content/actions/operation';
|
import * as operationActions from '../../actions/operation';
|
||||||
import operations from 'shared/operations';
|
import * as operations from '../../../shared/operations';
|
||||||
import * as keyUtils from 'shared/utils/keys';
|
import * as keyUtils from '../../../shared/utils/keys';
|
||||||
|
|
||||||
const mapStartsWith = (mapping, keys) => {
|
const mapStartsWith = (mapping, keys) => {
|
||||||
if (mapping.length < keys.length) {
|
if (mapping.length < keys.length) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as scrolls from 'content/scrolls';
|
||||||
import * as consoleFrames from 'content/console-frames';
|
import * as consoleFrames from 'content/console-frames';
|
||||||
import * as properties from 'shared/settings/properties';
|
import * as properties from 'shared/settings/properties';
|
||||||
|
|
||||||
const cancelKey = (key) => {
|
const cancelKey = (key): boolean => {
|
||||||
return key.key === 'Esc' || key.key === '[' && key.ctrlKey;
|
return key.key === 'Esc' || key.key === '[' && key.ctrlKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import * as findActions from 'content/actions/find';
|
import * as findActions from '../../actions/find';
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../../shared/messages';
|
||||||
|
import MessageListener from '../../MessageListener';
|
||||||
|
|
||||||
export default class FindComponent {
|
export default class FindComponent {
|
||||||
constructor(win, store) {
|
private store: any;
|
||||||
this.win = win;
|
|
||||||
|
constructor(store: any) {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this));
|
new MessageListener().onWebMessage(this.onMessage.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message) {
|
onMessage(message: messages.Message) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.CONSOLE_ENTER_FIND:
|
case messages.CONSOLE_ENTER_FIND:
|
||||||
return this.start(message.text);
|
return this.start(message.text);
|
||||||
|
@ -20,22 +22,25 @@ export default class FindComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start(text) {
|
start(text: string) {
|
||||||
let state = this.store.getState().find;
|
let state = this.store.getState().find;
|
||||||
|
|
||||||
if (text.length === 0) {
|
if (text.length === 0) {
|
||||||
return this.store.dispatch(findActions.next(state.keyword, true));
|
return this.store.dispatch(
|
||||||
|
findActions.next(state.keyword as string, true));
|
||||||
}
|
}
|
||||||
return this.store.dispatch(findActions.next(text, true));
|
return this.store.dispatch(findActions.next(text, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
next() {
|
next() {
|
||||||
let state = this.store.getState().find;
|
let state = this.store.getState().find;
|
||||||
return this.store.dispatch(findActions.next(state.keyword, false));
|
return this.store.dispatch(
|
||||||
|
findActions.next(state.keyword as string, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
prev() {
|
prev() {
|
||||||
let state = this.store.getState().find;
|
let state = this.store.getState().find;
|
||||||
return this.store.dispatch(findActions.prev(state.keyword, false));
|
return this.store.dispatch(
|
||||||
|
findActions.prev(state.keyword as string, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,46 @@
|
||||||
import * as followControllerActions from 'content/actions/follow-controller';
|
import * as followControllerActions from '../../actions/follow-controller';
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../../shared/messages';
|
||||||
import HintKeyProducer from 'content/hint-key-producer';
|
import MessageListener, { WebMessageSender } from '../../MessageListener';
|
||||||
import * as properties from 'shared/settings/properties';
|
import HintKeyProducer from '../../hint-key-producer';
|
||||||
|
import * as properties from '../../../shared/settings/properties';
|
||||||
|
|
||||||
const broadcastMessage = (win, message) => {
|
const broadcastMessage = (win: Window, message: messages.Message): void => {
|
||||||
let json = JSON.stringify(message);
|
let json = JSON.stringify(message);
|
||||||
let frames = [window.self].concat(Array.from(window.frames));
|
let frames = [win.self].concat(Array.from(win.frames as any));
|
||||||
frames.forEach(frame => frame.postMessage(json, '*'));
|
frames.forEach(frame => frame.postMessage(json, '*'));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class FollowController {
|
export default class FollowController {
|
||||||
constructor(win, store) {
|
private win: Window;
|
||||||
|
|
||||||
|
private store: any;
|
||||||
|
|
||||||
|
private state: {
|
||||||
|
enabled?: boolean;
|
||||||
|
newTab?: boolean;
|
||||||
|
background?: boolean;
|
||||||
|
keys?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
private keys: string[];
|
||||||
|
|
||||||
|
private producer: HintKeyProducer | null;
|
||||||
|
|
||||||
|
constructor(win: Window, store: any) {
|
||||||
this.win = win;
|
this.win = win;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.state = {};
|
this.state = {};
|
||||||
this.keys = [];
|
this.keys = [];
|
||||||
this.producer = null;
|
this.producer = null;
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this));
|
new MessageListener().onWebMessage(this.onMessage.bind(this));
|
||||||
|
|
||||||
store.subscribe(() => {
|
store.subscribe(() => {
|
||||||
this.update();
|
this.update();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message, sender) {
|
onMessage(message: messages.Message, sender: WebMessageSender) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.FOLLOW_START:
|
case messages.FOLLOW_START:
|
||||||
return this.store.dispatch(
|
return this.store.dispatch(
|
||||||
|
@ -36,7 +52,7 @@ export default class FollowController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update(): void {
|
||||||
let prevState = this.state;
|
let prevState = this.state;
|
||||||
this.state = this.store.getState().followController;
|
this.state = this.store.getState().followController;
|
||||||
|
|
||||||
|
@ -49,8 +65,10 @@ export default class FollowController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHints() {
|
updateHints(): void {
|
||||||
let shown = this.keys.filter(key => key.startsWith(this.state.keys));
|
let shown = this.keys.filter((key) => {
|
||||||
|
return key.startsWith(this.state.keys as string);
|
||||||
|
});
|
||||||
if (shown.length === 1) {
|
if (shown.length === 1) {
|
||||||
this.activate();
|
this.activate();
|
||||||
this.store.dispatch(followControllerActions.disable());
|
this.store.dispatch(followControllerActions.disable());
|
||||||
|
@ -58,18 +76,18 @@ export default class FollowController {
|
||||||
|
|
||||||
broadcastMessage(this.win, {
|
broadcastMessage(this.win, {
|
||||||
type: messages.FOLLOW_SHOW_HINTS,
|
type: messages.FOLLOW_SHOW_HINTS,
|
||||||
keys: this.state.keys,
|
keys: this.state.keys as string,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
activate() {
|
activate(): void {
|
||||||
broadcastMessage(this.win, {
|
broadcastMessage(this.win, {
|
||||||
type: messages.FOLLOW_ACTIVATE,
|
type: messages.FOLLOW_ACTIVATE,
|
||||||
keys: this.state.keys,
|
keys: this.state.keys as string,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
keyPress(key, ctrlKey) {
|
keyPress(key: string, ctrlKey: boolean): boolean {
|
||||||
if (key === '[' && ctrlKey) {
|
if (key === '[' && ctrlKey) {
|
||||||
this.store.dispatch(followControllerActions.disable());
|
this.store.dispatch(followControllerActions.disable());
|
||||||
return true;
|
return true;
|
||||||
|
@ -107,25 +125,28 @@ export default class FollowController {
|
||||||
viewSize: { width: viewWidth, height: viewHeight },
|
viewSize: { width: viewWidth, height: viewHeight },
|
||||||
framePosition: { x: 0, y: 0 },
|
framePosition: { x: 0, y: 0 },
|
||||||
}), '*');
|
}), '*');
|
||||||
frameElements.forEach((element) => {
|
frameElements.forEach((ele) => {
|
||||||
let { left: frameX, top: frameY } = element.getBoundingClientRect();
|
let { left: frameX, top: frameY } = ele.getBoundingClientRect();
|
||||||
let message = JSON.stringify({
|
let message = JSON.stringify({
|
||||||
type: messages.FOLLOW_REQUEST_COUNT_TARGETS,
|
type: messages.FOLLOW_REQUEST_COUNT_TARGETS,
|
||||||
viewSize: { width: viewWidth, height: viewHeight },
|
viewSize: { width: viewWidth, height: viewHeight },
|
||||||
framePosition: { x: frameX, y: frameY },
|
framePosition: { x: frameX, y: frameY },
|
||||||
});
|
});
|
||||||
element.contentWindow.postMessage(message, '*');
|
if (ele instanceof HTMLFrameElement && ele.contentWindow ||
|
||||||
|
ele instanceof HTMLIFrameElement && ele.contentWindow) {
|
||||||
|
ele.contentWindow.postMessage(message, '*');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
create(count, sender) {
|
create(count: number, sender: WebMessageSender) {
|
||||||
let produced = [];
|
let produced = [];
|
||||||
for (let i = 0; i < count; ++i) {
|
for (let i = 0; i < count; ++i) {
|
||||||
produced.push(this.producer.produce());
|
produced.push((this.producer as HintKeyProducer).produce());
|
||||||
}
|
}
|
||||||
this.keys = this.keys.concat(produced);
|
this.keys = this.keys.concat(produced);
|
||||||
|
|
||||||
sender.postMessage(JSON.stringify({
|
(sender as Window).postMessage(JSON.stringify({
|
||||||
type: messages.FOLLOW_CREATE_HINTS,
|
type: messages.FOLLOW_CREATE_HINTS,
|
||||||
keysArray: produced,
|
keysArray: produced,
|
||||||
newTab: this.state.newTab,
|
newTab: this.state.newTab,
|
||||||
|
|
|
@ -2,33 +2,43 @@ import CommonComponent from '../common';
|
||||||
import FollowController from './follow-controller';
|
import FollowController from './follow-controller';
|
||||||
import FindComponent from './find';
|
import FindComponent from './find';
|
||||||
import * as consoleFrames from '../../console-frames';
|
import * as consoleFrames from '../../console-frames';
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../../shared/messages';
|
||||||
import * as scrolls from 'content/scrolls';
|
import MessageListener from '../../MessageListener';
|
||||||
|
import * as scrolls from '../../scrolls';
|
||||||
|
|
||||||
export default class TopContent {
|
export default class TopContent {
|
||||||
|
private win: Window;
|
||||||
|
|
||||||
constructor(win, store) {
|
private store: any;
|
||||||
|
|
||||||
|
constructor(win: Window, store: any) {
|
||||||
this.win = win;
|
this.win = win;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
|
|
||||||
new CommonComponent(win, store); // eslint-disable-line no-new
|
new CommonComponent(win, store); // eslint-disable-line no-new
|
||||||
new FollowController(win, store); // eslint-disable-line no-new
|
new FollowController(win, store); // eslint-disable-line no-new
|
||||||
new FindComponent(win, store); // eslint-disable-line no-new
|
new FindComponent(store); // eslint-disable-line no-new
|
||||||
|
|
||||||
// TODO make component
|
// TODO make component
|
||||||
consoleFrames.initialize(this.win.document);
|
consoleFrames.initialize(this.win.document);
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this));
|
new MessageListener().onWebMessage(this.onWebMessage.bind(this));
|
||||||
|
new MessageListener().onBackgroundMessage(
|
||||||
|
this.onBackgroundMessage.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message) {
|
onWebMessage(message: messages.Message) {
|
||||||
let addonState = this.store.getState().addon;
|
|
||||||
|
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.CONSOLE_UNFOCUS:
|
case messages.CONSOLE_UNFOCUS:
|
||||||
this.win.focus();
|
this.win.focus();
|
||||||
consoleFrames.blur(window.document);
|
consoleFrames.blur(window.document);
|
||||||
return Promise.resolve();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackgroundMessage(message: messages.Message) {
|
||||||
|
let addonState = this.store.getState().addon;
|
||||||
|
|
||||||
|
switch (message.type) {
|
||||||
case messages.ADDON_ENABLED_QUERY:
|
case messages.ADDON_ENABLED_QUERY:
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
type: messages.ADDON_ENABLED_RESPONSE,
|
type: messages.ADDON_ENABLED_RESPONSE,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../shared/messages';
|
||||||
|
|
||||||
const initialize = (doc) => {
|
const initialize = (doc: Document): HTMLIFrameElement => {
|
||||||
let iframe = doc.createElement('iframe');
|
let iframe = doc.createElement('iframe');
|
||||||
iframe.src = browser.runtime.getURL('build/console.html');
|
iframe.src = browser.runtime.getURL('build/console.html');
|
||||||
iframe.id = 'vimvixen-console-frame';
|
iframe.id = 'vimvixen-console-frame';
|
||||||
|
@ -10,13 +10,13 @@ const initialize = (doc) => {
|
||||||
return iframe;
|
return iframe;
|
||||||
};
|
};
|
||||||
|
|
||||||
const blur = (doc) => {
|
const blur = (doc: Document) => {
|
||||||
let iframe = doc.getElementById('vimvixen-console-frame');
|
let ele = doc.getElementById('vimvixen-console-frame') as HTMLIFrameElement;
|
||||||
iframe.blur();
|
ele.blur();
|
||||||
};
|
};
|
||||||
|
|
||||||
const postError = (text) => {
|
const postError = (text: string): Promise<any> => {
|
||||||
browser.runtime.sendMessage({
|
return browser.runtime.sendMessage({
|
||||||
type: messages.CONSOLE_FRAME_MESSAGE,
|
type: messages.CONSOLE_FRAME_MESSAGE,
|
||||||
message: {
|
message: {
|
||||||
type: messages.CONSOLE_SHOW_ERROR,
|
type: messages.CONSOLE_SHOW_ERROR,
|
||||||
|
@ -25,8 +25,8 @@ const postError = (text) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const postInfo = (text) => {
|
const postInfo = (text: string): Promise<any> => {
|
||||||
browser.runtime.sendMessage({
|
return browser.runtime.sendMessage({
|
||||||
type: messages.CONSOLE_FRAME_MESSAGE,
|
type: messages.CONSOLE_FRAME_MESSAGE,
|
||||||
message: {
|
message: {
|
||||||
type: messages.CONSOLE_SHOW_INFO,
|
type: messages.CONSOLE_SHOW_INFO,
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import * as doms from 'shared/utils/dom';
|
import * as doms from '../shared/utils/dom';
|
||||||
|
|
||||||
const focusInput = () => {
|
const focusInput = (): void => {
|
||||||
let inputTypes = ['email', 'number', 'search', 'tel', 'text', 'url'];
|
let inputTypes = ['email', 'number', 'search', 'tel', 'text', 'url'];
|
||||||
let inputSelector = inputTypes.map(type => `input[type=${type}]`).join(',');
|
let inputSelector = inputTypes.map(type => `input[type=${type}]`).join(',');
|
||||||
let targets = window.document.querySelectorAll(inputSelector + ',textarea');
|
let targets = window.document.querySelectorAll(inputSelector + ',textarea');
|
||||||
let target = Array.from(targets).find(doms.isVisible);
|
let target = Array.from(targets).find(doms.isVisible);
|
||||||
if (target) {
|
if (target instanceof HTMLInputElement) {
|
||||||
|
target.focus();
|
||||||
|
} else if (target instanceof HTMLTextAreaElement) {
|
||||||
target.focus();
|
target.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
export default class HintKeyProducer {
|
export default class HintKeyProducer {
|
||||||
constructor(charset) {
|
private charset: string;
|
||||||
|
|
||||||
|
private counter: number[];
|
||||||
|
|
||||||
|
constructor(charset: string) {
|
||||||
if (charset.length === 0) {
|
if (charset.length === 0) {
|
||||||
throw new TypeError('charset is empty');
|
throw new TypeError('charset is empty');
|
||||||
}
|
}
|
||||||
|
@ -8,13 +12,13 @@ export default class HintKeyProducer {
|
||||||
this.counter = [];
|
this.counter = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
produce() {
|
produce(): string {
|
||||||
this.increment();
|
this.increment();
|
||||||
|
|
||||||
return this.counter.map(x => this.charset[x]).join('');
|
return this.counter.map(x => this.charset[x]).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
increment() {
|
private increment(): void {
|
||||||
let max = this.charset.length - 1;
|
let max = this.charset.length - 1;
|
||||||
if (this.counter.every(x => x === max)) {
|
if (this.counter.every(x => x === max)) {
|
||||||
this.counter = new Array(this.counter.length + 1).fill(0);
|
this.counter = new Array(this.counter.length + 1).fill(0);
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
|
||||||
import promise from 'redux-promise';
|
|
||||||
import reducers from 'content/reducers';
|
|
||||||
import TopContentComponent from './components/top-content';
|
import TopContentComponent from './components/top-content';
|
||||||
import FrameContentComponent from './components/frame-content';
|
import FrameContentComponent from './components/frame-content';
|
||||||
import consoleFrameStyle from './site-style';
|
import consoleFrameStyle from './site-style';
|
||||||
|
import { newStore } from './store';
|
||||||
|
|
||||||
const store = createStore(
|
const store = newStore();
|
||||||
reducers,
|
|
||||||
applyMiddleware(promise),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (window.self === window.top) {
|
if (window.self === window.top) {
|
||||||
new TopContentComponent(window, store); // eslint-disable-line no-new
|
new TopContentComponent(window, store); // eslint-disable-line no-new
|
||||||
|
|
|
@ -1,58 +1,63 @@
|
||||||
const REL_PATTERN = {
|
const REL_PATTERN: {[key: string]: RegExp} = {
|
||||||
prev: /^(?:prev(?:ious)?|older)\b|\u2039|\u2190|\xab|\u226a|<</i,
|
prev: /^(?:prev(?:ious)?|older)\b|\u2039|\u2190|\xab|\u226a|<</i,
|
||||||
next: /^(?:next|newer)\b|\u203a|\u2192|\xbb|\u226b|>>/i,
|
next: /^(?:next|newer)\b|\u203a|\u2192|\xbb|\u226b|>>/i,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the last element in the document matching the supplied selector
|
// Return the last element in the document matching the supplied selector
|
||||||
// and the optional filter, or null if there are no matches.
|
// and the optional filter, or null if there are no matches.
|
||||||
const selectLast = (win, selector, filter) => {
|
// eslint-disable-next-line func-style
|
||||||
let nodes = win.document.querySelectorAll(selector);
|
function selectLast<E extends Element>(
|
||||||
|
win: Window,
|
||||||
|
selector: string,
|
||||||
|
filter?: (e: E) => boolean,
|
||||||
|
): E | null {
|
||||||
|
let nodes = Array.from(
|
||||||
|
win.document.querySelectorAll(selector) as NodeListOf<E>
|
||||||
|
);
|
||||||
|
|
||||||
if (filter) {
|
if (filter) {
|
||||||
nodes = Array.from(nodes).filter(filter);
|
nodes = nodes.filter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes.length ? nodes[nodes.length - 1] : null;
|
return nodes.length ? nodes[nodes.length - 1] : null;
|
||||||
};
|
}
|
||||||
|
|
||||||
const historyPrev = (win) => {
|
const historyPrev = (win: Window): void => {
|
||||||
win.history.back();
|
win.history.back();
|
||||||
};
|
};
|
||||||
|
|
||||||
const historyNext = (win) => {
|
const historyNext = (win: Window): void => {
|
||||||
win.history.forward();
|
win.history.forward();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Code common to linkPrev and linkNext which navigates to the specified page.
|
// Code common to linkPrev and linkNext which navigates to the specified page.
|
||||||
const linkRel = (win, rel) => {
|
const linkRel = (win: Window, rel: string): void => {
|
||||||
let link = selectLast(win, `link[rel~=${rel}][href]`);
|
let link = selectLast<HTMLLinkElement>(win, `link[rel~=${rel}][href]`);
|
||||||
|
|
||||||
if (link) {
|
if (link) {
|
||||||
win.location = link.href;
|
win.location.href = link.href;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pattern = REL_PATTERN[rel];
|
const pattern = REL_PATTERN[rel];
|
||||||
|
|
||||||
link = selectLast(win, `a[rel~=${rel}][href]`) ||
|
let a = selectLast<HTMLAnchorElement>(win, `a[rel~=${rel}][href]`) ||
|
||||||
// `innerText` is much slower than `textContent`, but produces much better
|
// `innerText` is much slower than `textContent`, but produces much better
|
||||||
// (i.e. less unexpected) results
|
// (i.e. less unexpected) results
|
||||||
selectLast(win, 'a[href]', lnk => pattern.test(lnk.innerText));
|
selectLast(win, 'a[href]', lnk => pattern.test(lnk.innerText));
|
||||||
|
|
||||||
if (link) {
|
if (a) {
|
||||||
link.click();
|
a.click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const linkPrev = (win) => {
|
const linkPrev = (win: Window): void => {
|
||||||
linkRel(win, 'prev');
|
linkRel(win, 'prev');
|
||||||
};
|
};
|
||||||
|
|
||||||
const linkNext = (win) => {
|
const linkNext = (win: Window): void => {
|
||||||
linkRel(win, 'next');
|
linkRel(win, 'next');
|
||||||
};
|
};
|
||||||
|
|
||||||
const parent = (win) => {
|
const parent = (win: Window): void => {
|
||||||
const loc = win.location;
|
const loc = win.location;
|
||||||
if (loc.hash !== '') {
|
if (loc.hash !== '') {
|
||||||
loc.hash = '';
|
loc.hash = '';
|
||||||
|
@ -71,8 +76,8 @@ const parent = (win) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = (win) => {
|
const root = (win: Window): void => {
|
||||||
win.location = win.location.origin;
|
win.location.href = win.location.origin;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { historyPrev, historyNext, linkPrev, linkNext, parent, root };
|
export { historyPrev, historyNext, linkPrev, linkNext, parent, root };
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
const defaultState = {
|
export interface State {
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultState: State = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) {
|
export default function reducer(
|
||||||
|
state: State = defaultState,
|
||||||
|
action: actions.AddonAction,
|
||||||
|
): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actions.ADDON_SET_ENABLED:
|
case actions.ADDON_SET_ENABLED:
|
||||||
return { ...state,
|
return { ...state,
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
const defaultState = {
|
export interface State {
|
||||||
|
keyword: string | null;
|
||||||
|
found: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultState: State = {
|
||||||
keyword: null,
|
keyword: null,
|
||||||
found: false,
|
found: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) {
|
export default function reducer(
|
||||||
|
state: State = defaultState,
|
||||||
|
action: actions.FindAction,
|
||||||
|
): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actions.FIND_SET_KEYWORD:
|
case actions.FIND_SET_KEYWORD:
|
||||||
return { ...state,
|
return { ...state,
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
const defaultState = {
|
export interface State {
|
||||||
|
enabled: boolean;
|
||||||
|
newTab: boolean;
|
||||||
|
background: boolean;
|
||||||
|
keys: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultState: State = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
newTab: false,
|
newTab: false,
|
||||||
background: false,
|
background: false,
|
||||||
keys: '',
|
keys: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) {
|
export default function reducer(
|
||||||
|
state: State = defaultState,
|
||||||
|
action: actions.FollowAction,
|
||||||
|
): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actions.FOLLOW_CONTROLLER_ENABLE:
|
case actions.FOLLOW_CONTROLLER_ENABLE:
|
||||||
return { ...state,
|
return { ...state,
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import addon from './addon';
|
import addon, { State as AddonState } from './addon';
|
||||||
import find from './find';
|
import find, { State as FindState } from './find';
|
||||||
import setting from './setting';
|
import setting, { State as SettingState } from './setting';
|
||||||
import input from './input';
|
import input, { State as InputState } from './input';
|
||||||
import followController from './follow-controller';
|
import followController, { State as FollowControllerState }
|
||||||
import mark from './mark';
|
from './follow-controller';
|
||||||
|
import mark, { State as MarkState } from './mark';
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
addon: AddonState;
|
||||||
|
find: FindState;
|
||||||
|
setting: SettingState;
|
||||||
|
input: InputState;
|
||||||
|
followController: FollowControllerState;
|
||||||
|
mark: MarkState;
|
||||||
|
}
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
addon, find, setting, input, followController, mark,
|
addon, find, setting, input, followController, mark,
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
const defaultState = {
|
export interface State {
|
||||||
|
keys: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultState: State = {
|
||||||
keys: []
|
keys: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) {
|
export default function reducer(
|
||||||
|
state: State = defaultState,
|
||||||
|
action: actions.InputAction,
|
||||||
|
): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actions.INPUT_KEY_PRESS:
|
case actions.INPUT_KEY_PRESS:
|
||||||
return { ...state,
|
return { ...state,
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
const defaultState = {
|
interface Mark {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
setMode: boolean;
|
||||||
|
jumpMode: boolean;
|
||||||
|
marks: { [key: string]: Mark };
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultState: State = {
|
||||||
setMode: false,
|
setMode: false,
|
||||||
jumpMode: false,
|
jumpMode: false,
|
||||||
marks: {},
|
marks: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) {
|
export default function reducer(
|
||||||
|
state: State = defaultState,
|
||||||
|
action: actions.MarkAction,
|
||||||
|
): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actions.MARK_START_SET:
|
case actions.MARK_START_SET:
|
||||||
return { ...state, setMode: true };
|
return { ...state, setMode: true };
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
keymaps: any[];
|
||||||
|
}
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
// keymaps is and arrays of key-binding pairs, which is entries of Map
|
// keymaps is and arrays of key-binding pairs, which is entries of Map
|
||||||
keymaps: [],
|
keymaps: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) {
|
export default function reducer(
|
||||||
|
state: State = defaultState,
|
||||||
|
action: actions.SettingAction,
|
||||||
|
): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actions.SETTING_SET:
|
case actions.SETTING_SET:
|
||||||
return { ...action.value };
|
return { ...action.value };
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import * as doms from 'shared/utils/dom';
|
import * as doms from '../shared/utils/dom';
|
||||||
|
|
||||||
const SCROLL_DELTA_X = 64;
|
const SCROLL_DELTA_X = 64;
|
||||||
const SCROLL_DELTA_Y = 64;
|
const SCROLL_DELTA_Y = 64;
|
||||||
|
|
||||||
// dirty way to store scrolling state on globally
|
// dirty way to store scrolling state on globally
|
||||||
let scrolling = false;
|
let scrolling = false;
|
||||||
let lastTimeoutId = null;
|
let lastTimeoutId: number | null = null;
|
||||||
|
|
||||||
const isScrollableStyle = (element) => {
|
const isScrollableStyle = (element: Element): boolean => {
|
||||||
let { overflowX, overflowY } = window.getComputedStyle(element);
|
let { overflowX, overflowY } = window.getComputedStyle(element);
|
||||||
return !(overflowX !== 'scroll' && overflowX !== 'auto' &&
|
return !(overflowX !== 'scroll' && overflowX !== 'auto' &&
|
||||||
overflowY !== 'scroll' && overflowY !== 'auto');
|
overflowY !== 'scroll' && overflowY !== 'auto');
|
||||||
};
|
};
|
||||||
|
|
||||||
const isOverflowed = (element) => {
|
const isOverflowed = (element: Element): boolean => {
|
||||||
return element.scrollWidth > element.clientWidth ||
|
return element.scrollWidth > element.clientWidth ||
|
||||||
element.scrollHeight > element.clientHeight;
|
element.scrollHeight > element.clientHeight;
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@ const isOverflowed = (element) => {
|
||||||
// this method is called by each scrolling, and the returned value of this
|
// this method is called by each scrolling, and the returned value of this
|
||||||
// method is not cached. That does not cause performance issue because in the
|
// method is not cached. That does not cause performance issue because in the
|
||||||
// most pages, the window is root element i,e, documentElement.
|
// most pages, the window is root element i,e, documentElement.
|
||||||
const findScrollable = (element) => {
|
const findScrollable = (element: Element): Element | null => {
|
||||||
if (isScrollableStyle(element) && isOverflowed(element)) {
|
if (isScrollableStyle(element) && isOverflowed(element)) {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
@ -56,12 +56,16 @@ const resetScrolling = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
class Scroller {
|
class Scroller {
|
||||||
constructor(element, smooth) {
|
private element: Element;
|
||||||
|
|
||||||
|
private smooth: boolean;
|
||||||
|
|
||||||
|
constructor(element: Element, smooth: boolean) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.smooth = smooth;
|
this.smooth = smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTo(x, y) {
|
scrollTo(x: number, y: number): void {
|
||||||
if (!this.smooth) {
|
if (!this.smooth) {
|
||||||
this.element.scrollTo(x, y);
|
this.element.scrollTo(x, y);
|
||||||
return;
|
return;
|
||||||
|
@ -74,13 +78,13 @@ class Scroller {
|
||||||
this.prepareReset();
|
this.prepareReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollBy(x, y) {
|
scrollBy(x: number, y: number): void {
|
||||||
let left = this.element.scrollLeft + x;
|
let left = this.element.scrollLeft + x;
|
||||||
let top = this.element.scrollTop + y;
|
let top = this.element.scrollTop + y;
|
||||||
this.scrollTo(left, top);
|
this.scrollTo(left, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareReset() {
|
prepareReset(): void {
|
||||||
scrolling = true;
|
scrolling = true;
|
||||||
if (lastTimeoutId) {
|
if (lastTimeoutId) {
|
||||||
clearTimeout(lastTimeoutId);
|
clearTimeout(lastTimeoutId);
|
||||||
|
@ -95,7 +99,7 @@ const getScroll = () => {
|
||||||
return { x: target.scrollLeft, y: target.scrollTop };
|
return { x: target.scrollLeft, y: target.scrollTop };
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollVertically = (count, smooth) => {
|
const scrollVertically = (count: number, smooth: boolean): void => {
|
||||||
let target = scrollTarget();
|
let target = scrollTarget();
|
||||||
let delta = SCROLL_DELTA_Y * count;
|
let delta = SCROLL_DELTA_Y * count;
|
||||||
if (scrolling) {
|
if (scrolling) {
|
||||||
|
@ -104,7 +108,7 @@ const scrollVertically = (count, smooth) => {
|
||||||
new Scroller(target, smooth).scrollBy(0, delta);
|
new Scroller(target, smooth).scrollBy(0, delta);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollHorizonally = (count, smooth) => {
|
const scrollHorizonally = (count: number, smooth: boolean): void => {
|
||||||
let target = scrollTarget();
|
let target = scrollTarget();
|
||||||
let delta = SCROLL_DELTA_X * count;
|
let delta = SCROLL_DELTA_X * count;
|
||||||
if (scrolling) {
|
if (scrolling) {
|
||||||
|
@ -113,7 +117,7 @@ const scrollHorizonally = (count, smooth) => {
|
||||||
new Scroller(target, smooth).scrollBy(delta, 0);
|
new Scroller(target, smooth).scrollBy(delta, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollPages = (count, smooth) => {
|
const scrollPages = (count: number, smooth: boolean): void => {
|
||||||
let target = scrollTarget();
|
let target = scrollTarget();
|
||||||
let height = target.clientHeight;
|
let height = target.clientHeight;
|
||||||
let delta = height * count;
|
let delta = height * count;
|
||||||
|
@ -123,33 +127,33 @@ const scrollPages = (count, smooth) => {
|
||||||
new Scroller(target, smooth).scrollBy(0, delta);
|
new Scroller(target, smooth).scrollBy(0, delta);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollTo = (x, y, smooth) => {
|
const scrollTo = (x: number, y: number, smooth: boolean): void => {
|
||||||
let target = scrollTarget();
|
let target = scrollTarget();
|
||||||
new Scroller(target, smooth).scrollTo(x, y);
|
new Scroller(target, smooth).scrollTo(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToTop = (smooth) => {
|
const scrollToTop = (smooth: boolean): void => {
|
||||||
let target = scrollTarget();
|
let target = scrollTarget();
|
||||||
let x = target.scrollLeft;
|
let x = target.scrollLeft;
|
||||||
let y = 0;
|
let y = 0;
|
||||||
new Scroller(target, smooth).scrollTo(x, y);
|
new Scroller(target, smooth).scrollTo(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToBottom = (smooth) => {
|
const scrollToBottom = (smooth: boolean): void => {
|
||||||
let target = scrollTarget();
|
let target = scrollTarget();
|
||||||
let x = target.scrollLeft;
|
let x = target.scrollLeft;
|
||||||
let y = target.scrollHeight;
|
let y = target.scrollHeight;
|
||||||
new Scroller(target, smooth).scrollTo(x, y);
|
new Scroller(target, smooth).scrollTo(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToHome = (smooth) => {
|
const scrollToHome = (smooth: boolean): void => {
|
||||||
let target = scrollTarget();
|
let target = scrollTarget();
|
||||||
let x = 0;
|
let x = 0;
|
||||||
let y = target.scrollTop;
|
let y = target.scrollTop;
|
||||||
new Scroller(target, smooth).scrollTo(x, y);
|
new Scroller(target, smooth).scrollTo(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToEnd = (smooth) => {
|
const scrollToEnd = (smooth: boolean): void => {
|
||||||
let target = scrollTarget();
|
let target = scrollTarget();
|
||||||
let x = target.scrollWidth;
|
let x = target.scrollWidth;
|
||||||
let y = target.scrollTop;
|
let y = target.scrollTop;
|
||||||
|
|
8
src/content/store/index.ts
Normal file
8
src/content/store/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import promise from 'redux-promise';
|
||||||
|
import reducers from '../reducers';
|
||||||
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
|
|
||||||
|
export const newStore = () => createStore(
|
||||||
|
reducers,
|
||||||
|
applyMiddleware(promise),
|
||||||
|
);
|
|
@ -1,7 +1,7 @@
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../shared/messages';
|
||||||
import * as urls from '../shared/urls';
|
import * as urls from '../shared/urls';
|
||||||
|
|
||||||
const yank = (win) => {
|
const yank = (win: Window) => {
|
||||||
let input = win.document.createElement('input');
|
let input = win.document.createElement('input');
|
||||||
win.document.body.append(input);
|
win.document.body.append(input);
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ const yank = (win) => {
|
||||||
input.remove();
|
input.remove();
|
||||||
};
|
};
|
||||||
|
|
||||||
const paste = (win, newTab, searchSettings) => {
|
const paste = (win: Window, newTab: boolean, searchSettings: any) => {
|
||||||
let textarea = win.document.createElement('textarea');
|
let textarea = win.document.createElement('textarea');
|
||||||
win.document.body.append(textarea);
|
win.document.body.append(textarea);
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ const paste = (win, newTab, searchSettings) => {
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
|
|
||||||
if (win.document.execCommand('paste')) {
|
if (win.document.execCommand('paste')) {
|
||||||
let value = textarea.textContent;
|
let value = textarea.textContent as string;
|
||||||
let url = urls.searchUrl(value, searchSettings);
|
let url = urls.searchUrl(value, searchSettings);
|
||||||
browser.runtime.sendMessage({
|
browser.runtime.sendMessage({
|
||||||
type: messages.OPEN_URL,
|
type: messages.OPEN_URL,
|
||||||
|
|
|
@ -1,78 +1,276 @@
|
||||||
type WebMessageSender = Window | MessagePort | ServiceWorker | null;
|
import * as operations from './operations';
|
||||||
type WebMessageListener = (msg: any, sender: WebMessageSender | null) => void;
|
|
||||||
|
|
||||||
const onWebMessage = (listener: WebMessageListener) => {
|
export const BACKGROUND_OPERATION = 'background.operation';
|
||||||
window.addEventListener('message', (event: MessageEvent) => {
|
|
||||||
let sender = event.source;
|
export const CONSOLE_UNFOCUS = 'console.unfocus';
|
||||||
let message = null;
|
export const CONSOLE_ENTER_COMMAND = 'console.enter.command';
|
||||||
try {
|
export const CONSOLE_ENTER_FIND = 'console.enter.find';
|
||||||
message = JSON.parse(event.data);
|
export const CONSOLE_QUERY_COMPLETIONS = 'console.query.completions';
|
||||||
} catch (e) {
|
export const CONSOLE_SHOW_COMMAND = 'console.show.command';
|
||||||
// ignore unexpected message
|
export const CONSOLE_SHOW_ERROR = 'console.show.error';
|
||||||
return;
|
export const CONSOLE_SHOW_INFO = 'console.show.info';
|
||||||
|
export const CONSOLE_SHOW_FIND = 'console.show.find';
|
||||||
|
export const CONSOLE_HIDE = 'console.hide';
|
||||||
|
|
||||||
|
export const FOLLOW_START = 'follow.start';
|
||||||
|
export const FOLLOW_REQUEST_COUNT_TARGETS = 'follow.request.count.targets';
|
||||||
|
export const FOLLOW_RESPONSE_COUNT_TARGETS = 'follow.response.count.targets';
|
||||||
|
export const FOLLOW_CREATE_HINTS = 'follow.create.hints';
|
||||||
|
export const FOLLOW_SHOW_HINTS = 'follow.update.hints';
|
||||||
|
export const FOLLOW_REMOVE_HINTS = 'follow.remove.hints';
|
||||||
|
export const FOLLOW_ACTIVATE = 'follow.activate';
|
||||||
|
export const FOLLOW_KEY_PRESS = 'follow.key.press';
|
||||||
|
|
||||||
|
export const MARK_SET_GLOBAL = 'mark.set.global';
|
||||||
|
export const MARK_JUMP_GLOBAL = 'mark.jump.global';
|
||||||
|
|
||||||
|
export const TAB_SCROLL_TO = 'tab.scroll.to';
|
||||||
|
|
||||||
|
export const FIND_NEXT = 'find.next';
|
||||||
|
export const FIND_PREV = 'find.prev';
|
||||||
|
export const FIND_GET_KEYWORD = 'find.get.keyword';
|
||||||
|
export const FIND_SET_KEYWORD = 'find.set.keyword';
|
||||||
|
|
||||||
|
export const ADDON_ENABLED_QUERY = 'addon.enabled.query';
|
||||||
|
export const ADDON_ENABLED_RESPONSE = 'addon.enabled.response';
|
||||||
|
export const ADDON_TOGGLE_ENABLED = 'addon.toggle.enabled';
|
||||||
|
|
||||||
|
export const OPEN_URL = 'open.url';
|
||||||
|
|
||||||
|
export const SETTINGS_CHANGED = 'settings.changed';
|
||||||
|
export const SETTINGS_QUERY = 'settings.query';
|
||||||
|
|
||||||
|
export const CONSOLE_FRAME_MESSAGE = 'console.frame.message';
|
||||||
|
|
||||||
|
interface BackgroundOperationMessage {
|
||||||
|
type: typeof BACKGROUND_OPERATION;
|
||||||
|
operation: operations.Operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleUnfocusMessage {
|
||||||
|
type: typeof CONSOLE_UNFOCUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleEnterCommandMessage {
|
||||||
|
type: typeof CONSOLE_ENTER_COMMAND;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleEnterFindMessage {
|
||||||
|
type: typeof CONSOLE_ENTER_FIND;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleQueryCompletionsMessage {
|
||||||
|
type: typeof CONSOLE_QUERY_COMPLETIONS;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleShowCommandMessage {
|
||||||
|
type: typeof CONSOLE_SHOW_COMMAND;
|
||||||
|
command: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleShowErrorMessage {
|
||||||
|
type: typeof CONSOLE_SHOW_ERROR;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleShowInfoMessage {
|
||||||
|
type: typeof CONSOLE_SHOW_INFO;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleShowFindMessage {
|
||||||
|
type: typeof CONSOLE_SHOW_FIND;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleHideMessage {
|
||||||
|
type: typeof CONSOLE_HIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FollowStartMessage {
|
||||||
|
type: typeof FOLLOW_START;
|
||||||
|
newTab: boolean;
|
||||||
|
background: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FollowRequestCountTargetsMessage {
|
||||||
|
type: typeof FOLLOW_REQUEST_COUNT_TARGETS;
|
||||||
|
viewSize: { width: number, height: number };
|
||||||
|
framePosition: { x: number, y: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FollowResponseCountTargetsMessage {
|
||||||
|
type: typeof FOLLOW_RESPONSE_COUNT_TARGETS;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FollowCreateHintsMessage {
|
||||||
|
type: typeof FOLLOW_CREATE_HINTS;
|
||||||
|
keysArray: string[];
|
||||||
|
newTab: boolean;
|
||||||
|
background: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FollowShowHintsMessage {
|
||||||
|
type: typeof FOLLOW_SHOW_HINTS;
|
||||||
|
keys: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FollowRemoveHintsMessage {
|
||||||
|
type: typeof FOLLOW_REMOVE_HINTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FollowActivateMessage {
|
||||||
|
type: typeof FOLLOW_ACTIVATE;
|
||||||
|
keys: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FollowKeyPressMessage {
|
||||||
|
type: typeof FOLLOW_KEY_PRESS;
|
||||||
|
key: string;
|
||||||
|
ctrlKey: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MarkSetGlobalMessage {
|
||||||
|
type: typeof MARK_SET_GLOBAL;
|
||||||
|
key: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MarkJumpGlobalMessage {
|
||||||
|
type: typeof MARK_JUMP_GLOBAL;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TabScrollToMessage {
|
||||||
|
type: typeof TAB_SCROLL_TO;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FindNextMessage {
|
||||||
|
type: typeof FIND_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FindPrevMessage {
|
||||||
|
type: typeof FIND_PREV;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FindGetKeywordMessage {
|
||||||
|
type: typeof FIND_GET_KEYWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FindSetKeywordMessage {
|
||||||
|
type: typeof FIND_SET_KEYWORD;
|
||||||
|
keyword: string;
|
||||||
|
found: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddonEnabledQueryMessage {
|
||||||
|
type: typeof ADDON_ENABLED_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddonEnabledResponseMessage {
|
||||||
|
type: typeof ADDON_ENABLED_RESPONSE;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddonToggleEnabledMessage {
|
||||||
|
type: typeof ADDON_TOGGLE_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenUrlMessage {
|
||||||
|
type: typeof OPEN_URL;
|
||||||
|
url: string;
|
||||||
|
newTab: boolean;
|
||||||
|
background: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SettingsChangedMessage {
|
||||||
|
type: typeof SETTINGS_CHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SettingsQueryMessage {
|
||||||
|
type: typeof SETTINGS_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleFrameMessageMessage {
|
||||||
|
type: typeof CONSOLE_FRAME_MESSAGE;
|
||||||
|
message: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Message =
|
||||||
|
BackgroundOperationMessage |
|
||||||
|
ConsoleUnfocusMessage |
|
||||||
|
ConsoleEnterCommandMessage |
|
||||||
|
ConsoleEnterFindMessage |
|
||||||
|
ConsoleQueryCompletionsMessage |
|
||||||
|
ConsoleShowCommandMessage |
|
||||||
|
ConsoleShowErrorMessage |
|
||||||
|
ConsoleShowInfoMessage |
|
||||||
|
ConsoleShowFindMessage |
|
||||||
|
ConsoleHideMessage |
|
||||||
|
FollowStartMessage |
|
||||||
|
FollowRequestCountTargetsMessage |
|
||||||
|
FollowResponseCountTargetsMessage |
|
||||||
|
FollowCreateHintsMessage |
|
||||||
|
FollowShowHintsMessage |
|
||||||
|
FollowRemoveHintsMessage |
|
||||||
|
FollowActivateMessage |
|
||||||
|
FollowKeyPressMessage |
|
||||||
|
MarkSetGlobalMessage |
|
||||||
|
MarkJumpGlobalMessage |
|
||||||
|
TabScrollToMessage |
|
||||||
|
FindNextMessage |
|
||||||
|
FindPrevMessage |
|
||||||
|
FindGetKeywordMessage |
|
||||||
|
FindSetKeywordMessage |
|
||||||
|
AddonEnabledQueryMessage |
|
||||||
|
AddonEnabledResponseMessage |
|
||||||
|
AddonToggleEnabledMessage |
|
||||||
|
OpenUrlMessage |
|
||||||
|
SettingsChangedMessage |
|
||||||
|
SettingsQueryMessage |
|
||||||
|
ConsoleFrameMessageMessage;
|
||||||
|
|
||||||
|
// eslint-disable-next-line complexity
|
||||||
|
export const valueOf = (o: any): Message => {
|
||||||
|
switch (o.type) {
|
||||||
|
case CONSOLE_UNFOCUS:
|
||||||
|
case CONSOLE_ENTER_COMMAND:
|
||||||
|
case CONSOLE_ENTER_FIND:
|
||||||
|
case CONSOLE_QUERY_COMPLETIONS:
|
||||||
|
case CONSOLE_SHOW_COMMAND:
|
||||||
|
case CONSOLE_SHOW_ERROR:
|
||||||
|
case CONSOLE_SHOW_INFO:
|
||||||
|
case CONSOLE_SHOW_FIND:
|
||||||
|
case CONSOLE_HIDE:
|
||||||
|
case FOLLOW_START:
|
||||||
|
case FOLLOW_REQUEST_COUNT_TARGETS:
|
||||||
|
case FOLLOW_RESPONSE_COUNT_TARGETS:
|
||||||
|
case FOLLOW_CREATE_HINTS:
|
||||||
|
case FOLLOW_SHOW_HINTS:
|
||||||
|
case FOLLOW_REMOVE_HINTS:
|
||||||
|
case FOLLOW_ACTIVATE:
|
||||||
|
case FOLLOW_KEY_PRESS:
|
||||||
|
case MARK_SET_GLOBAL:
|
||||||
|
case MARK_JUMP_GLOBAL:
|
||||||
|
case TAB_SCROLL_TO:
|
||||||
|
case FIND_NEXT:
|
||||||
|
case FIND_PREV:
|
||||||
|
case FIND_GET_KEYWORD:
|
||||||
|
case FIND_SET_KEYWORD:
|
||||||
|
case ADDON_ENABLED_QUERY:
|
||||||
|
case ADDON_ENABLED_RESPONSE:
|
||||||
|
case ADDON_TOGGLE_ENABLED:
|
||||||
|
case OPEN_URL:
|
||||||
|
case SETTINGS_CHANGED:
|
||||||
|
case SETTINGS_QUERY:
|
||||||
|
case CONSOLE_FRAME_MESSAGE:
|
||||||
|
return o;
|
||||||
}
|
}
|
||||||
listener(message, sender);
|
throw new Error('unknown operation type: ' + o.type);
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onBackgroundMessage = (
|
|
||||||
listener: (msg: any, sender: browser.runtime.MessageSender,
|
|
||||||
) => void) => {
|
|
||||||
browser.runtime.onMessage.addListener(listener);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMessage = (
|
|
||||||
listener: (msg: any, sender: WebMessageSender | browser.runtime.MessageSender,
|
|
||||||
) => void) => {
|
|
||||||
onWebMessage(listener);
|
|
||||||
onBackgroundMessage(listener);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
BACKGROUND_OPERATION: 'background.operation',
|
|
||||||
|
|
||||||
CONSOLE_UNFOCUS: 'console.unfocus',
|
|
||||||
CONSOLE_ENTER_COMMAND: 'console.enter.command',
|
|
||||||
CONSOLE_ENTER_FIND: 'console.enter.find',
|
|
||||||
CONSOLE_QUERY_COMPLETIONS: 'console.query.completions',
|
|
||||||
CONSOLE_SHOW_COMMAND: 'console.show.command',
|
|
||||||
CONSOLE_SHOW_ERROR: 'console.show.error',
|
|
||||||
CONSOLE_SHOW_INFO: 'console.show.info',
|
|
||||||
CONSOLE_SHOW_FIND: 'console.show.find',
|
|
||||||
CONSOLE_HIDE: 'console.hide',
|
|
||||||
|
|
||||||
FOLLOW_START: 'follow.start',
|
|
||||||
FOLLOW_REQUEST_COUNT_TARGETS: 'follow.request.count.targets',
|
|
||||||
FOLLOW_RESPONSE_COUNT_TARGETS: 'follow.response.count.targets',
|
|
||||||
FOLLOW_CREATE_HINTS: 'follow.create.hints',
|
|
||||||
FOLLOW_SHOW_HINTS: 'follow.update.hints',
|
|
||||||
FOLLOW_REMOVE_HINTS: 'follow.remove.hints',
|
|
||||||
FOLLOW_ACTIVATE: 'follow.activate',
|
|
||||||
FOLLOW_KEY_PRESS: 'follow.key.press',
|
|
||||||
|
|
||||||
MARK_SET_GLOBAL: 'mark.set.global',
|
|
||||||
MARK_JUMP_GLOBAL: 'mark.jump.global',
|
|
||||||
|
|
||||||
TAB_SCROLL_TO: 'tab.scroll.to',
|
|
||||||
|
|
||||||
FIND_NEXT: 'find.next',
|
|
||||||
FIND_PREV: 'find.prev',
|
|
||||||
FIND_GET_KEYWORD: 'find.get.keyword',
|
|
||||||
FIND_SET_KEYWORD: 'find.set.keyword',
|
|
||||||
|
|
||||||
ADDON_ENABLED_QUERY: 'addon.enabled.query',
|
|
||||||
ADDON_ENABLED_RESPONSE: 'addon.enabled.response',
|
|
||||||
ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled',
|
|
||||||
|
|
||||||
OPEN_URL: 'open.url',
|
|
||||||
|
|
||||||
SETTINGS_CHANGED: 'settings.changed',
|
|
||||||
SETTINGS_QUERY: 'settings.query',
|
|
||||||
|
|
||||||
WINDOW_TOP_MESSAGE: 'window.top.message',
|
|
||||||
CONSOLE_FRAME_MESSAGE: 'console.frame.message',
|
|
||||||
|
|
||||||
onWebMessage,
|
|
||||||
onBackgroundMessage,
|
|
||||||
onMessage,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,80 +1,447 @@
|
||||||
const operations: { [key: string]: string } = {
|
// Hide console; or cancel some user actions
|
||||||
// Hide console, or cancel some user actions
|
export const CANCEL = 'cancel';
|
||||||
CANCEL: 'cancel',
|
|
||||||
|
|
||||||
// Addons
|
// Addons
|
||||||
ADDON_ENABLE: 'addon.enable',
|
export const ADDON_ENABLE = 'addon.enable';
|
||||||
ADDON_DISABLE: 'addon.disable',
|
export const ADDON_DISABLE = 'addon.disable';
|
||||||
ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled',
|
export const ADDON_TOGGLE_ENABLED = 'addon.toggle.enabled';
|
||||||
|
|
||||||
// Command
|
// Command
|
||||||
COMMAND_SHOW: 'command.show',
|
export const COMMAND_SHOW = 'command.show';
|
||||||
COMMAND_SHOW_OPEN: 'command.show.open',
|
export const COMMAND_SHOW_OPEN = 'command.show.open';
|
||||||
COMMAND_SHOW_TABOPEN: 'command.show.tabopen',
|
export const COMMAND_SHOW_TABOPEN = 'command.show.tabopen';
|
||||||
COMMAND_SHOW_WINOPEN: 'command.show.winopen',
|
export const COMMAND_SHOW_WINOPEN = 'command.show.winopen';
|
||||||
COMMAND_SHOW_BUFFER: 'command.show.buffer',
|
export const COMMAND_SHOW_BUFFER = 'command.show.buffer';
|
||||||
COMMAND_SHOW_ADDBOOKMARK: 'command.show.addbookmark',
|
export const COMMAND_SHOW_ADDBOOKMARK = 'command.show.addbookmark';
|
||||||
|
|
||||||
// Scrolls
|
// Scrolls
|
||||||
SCROLL_VERTICALLY: 'scroll.vertically',
|
export const SCROLL_VERTICALLY = 'scroll.vertically';
|
||||||
SCROLL_HORIZONALLY: 'scroll.horizonally',
|
export const SCROLL_HORIZONALLY = 'scroll.horizonally';
|
||||||
SCROLL_PAGES: 'scroll.pages',
|
export const SCROLL_PAGES = 'scroll.pages';
|
||||||
SCROLL_TOP: 'scroll.top',
|
export const SCROLL_TOP = 'scroll.top';
|
||||||
SCROLL_BOTTOM: 'scroll.bottom',
|
export const SCROLL_BOTTOM = 'scroll.bottom';
|
||||||
SCROLL_HOME: 'scroll.home',
|
export const SCROLL_HOME = 'scroll.home';
|
||||||
SCROLL_END: 'scroll.end',
|
export const SCROLL_END = 'scroll.end';
|
||||||
|
|
||||||
// Follows
|
// Follows
|
||||||
FOLLOW_START: 'follow.start',
|
export const FOLLOW_START = 'follow.start';
|
||||||
|
|
||||||
// Navigations
|
// Navigations
|
||||||
NAVIGATE_HISTORY_PREV: 'navigate.history.prev',
|
export const NAVIGATE_HISTORY_PREV = 'navigate.history.prev';
|
||||||
NAVIGATE_HISTORY_NEXT: 'navigate.history.next',
|
export const NAVIGATE_HISTORY_NEXT = 'navigate.history.next';
|
||||||
NAVIGATE_LINK_PREV: 'navigate.link.prev',
|
export const NAVIGATE_LINK_PREV = 'navigate.link.prev';
|
||||||
NAVIGATE_LINK_NEXT: 'navigate.link.next',
|
export const NAVIGATE_LINK_NEXT = 'navigate.link.next';
|
||||||
NAVIGATE_PARENT: 'navigate.parent',
|
export const NAVIGATE_PARENT = 'navigate.parent';
|
||||||
NAVIGATE_ROOT: 'navigate.root',
|
export const NAVIGATE_ROOT = 'navigate.root';
|
||||||
|
|
||||||
// Focus
|
// Focus
|
||||||
FOCUS_INPUT: 'focus.input',
|
export const FOCUS_INPUT = 'focus.input';
|
||||||
|
|
||||||
// Page
|
// Page
|
||||||
PAGE_SOURCE: 'page.source',
|
export const PAGE_SOURCE = 'page.source';
|
||||||
PAGE_HOME: 'page.home',
|
export const PAGE_HOME = 'page.home';
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
TAB_CLOSE: 'tabs.close',
|
export const TAB_CLOSE = 'tabs.close';
|
||||||
TAB_CLOSE_FORCE: 'tabs.close.force',
|
export const TAB_CLOSE_FORCE = 'tabs.close.force';
|
||||||
TAB_CLOSE_RIGHT: 'tabs.close.right',
|
export const TAB_CLOSE_RIGHT = 'tabs.close.right';
|
||||||
TAB_REOPEN: 'tabs.reopen',
|
export const TAB_REOPEN = 'tabs.reopen';
|
||||||
TAB_PREV: 'tabs.prev',
|
export const TAB_PREV = 'tabs.prev';
|
||||||
TAB_NEXT: 'tabs.next',
|
export const TAB_NEXT = 'tabs.next';
|
||||||
TAB_FIRST: 'tabs.first',
|
export const TAB_FIRST = 'tabs.first';
|
||||||
TAB_LAST: 'tabs.last',
|
export const TAB_LAST = 'tabs.last';
|
||||||
TAB_PREV_SEL: 'tabs.prevsel',
|
export const TAB_PREV_SEL = 'tabs.prevsel';
|
||||||
TAB_RELOAD: 'tabs.reload',
|
export const TAB_RELOAD = 'tabs.reload';
|
||||||
TAB_PIN: 'tabs.pin',
|
export const TAB_PIN = 'tabs.pin';
|
||||||
TAB_UNPIN: 'tabs.unpin',
|
export const TAB_UNPIN = 'tabs.unpin';
|
||||||
TAB_TOGGLE_PINNED: 'tabs.pin.toggle',
|
export const TAB_TOGGLE_PINNED = 'tabs.pin.toggle';
|
||||||
TAB_DUPLICATE: 'tabs.duplicate',
|
export const TAB_DUPLICATE = 'tabs.duplicate';
|
||||||
|
|
||||||
// Zooms
|
// Zooms
|
||||||
ZOOM_IN: 'zoom.in',
|
export const ZOOM_IN = 'zoom.in';
|
||||||
ZOOM_OUT: 'zoom.out',
|
export const ZOOM_OUT = 'zoom.out';
|
||||||
ZOOM_NEUTRAL: 'zoom.neutral',
|
export const ZOOM_NEUTRAL = 'zoom.neutral';
|
||||||
|
|
||||||
// Url yank/paste
|
// Url yank/paste
|
||||||
URLS_YANK: 'urls.yank',
|
export const URLS_YANK = 'urls.yank';
|
||||||
URLS_PASTE: 'urls.paste',
|
export const URLS_PASTE = 'urls.paste';
|
||||||
|
|
||||||
// Find
|
// Find
|
||||||
FIND_START: 'find.start',
|
export const FIND_START = 'find.start';
|
||||||
FIND_NEXT: 'find.next',
|
export const FIND_NEXT = 'find.next';
|
||||||
FIND_PREV: 'find.prev',
|
export const FIND_PREV = 'find.prev';
|
||||||
|
|
||||||
// Mark
|
// Mark
|
||||||
MARK_SET_PREFIX: 'mark.set.prefix',
|
export const MARK_SET_PREFIX = 'mark.set.prefix';
|
||||||
MARK_JUMP_PREFIX: 'mark.jump.prefix',
|
export const MARK_JUMP_PREFIX = 'mark.jump.prefix';
|
||||||
|
|
||||||
|
export interface CancelOperation {
|
||||||
|
type: typeof CANCEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddonEnableOperation {
|
||||||
|
type: typeof ADDON_ENABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddonDisableOperation {
|
||||||
|
type: typeof ADDON_DISABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddonToggleEnabledOperation {
|
||||||
|
type: typeof ADDON_TOGGLE_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandShowOperation {
|
||||||
|
type: typeof COMMAND_SHOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandShowOpenOperation {
|
||||||
|
type: typeof COMMAND_SHOW_OPEN;
|
||||||
|
alter: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandShowTabopenOperation {
|
||||||
|
type: typeof COMMAND_SHOW_TABOPEN;
|
||||||
|
alter: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandShowWinopenOperation {
|
||||||
|
type: typeof COMMAND_SHOW_WINOPEN;
|
||||||
|
alter: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandShowBufferOperation {
|
||||||
|
type: typeof COMMAND_SHOW_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandShowAddbookmarkOperation {
|
||||||
|
type: typeof COMMAND_SHOW_ADDBOOKMARK;
|
||||||
|
alter: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrollVerticallyOperation {
|
||||||
|
type: typeof SCROLL_VERTICALLY;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrollHorizonallyOperation {
|
||||||
|
type: typeof SCROLL_HORIZONALLY;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrollPagesOperation {
|
||||||
|
type: typeof SCROLL_PAGES;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrollTopOperation {
|
||||||
|
type: typeof SCROLL_TOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrollBottomOperation {
|
||||||
|
type: typeof SCROLL_BOTTOM;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrollHomeOperation {
|
||||||
|
type: typeof SCROLL_HOME;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrollEndOperation {
|
||||||
|
type: typeof SCROLL_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowStartOperation {
|
||||||
|
type: typeof FOLLOW_START;
|
||||||
|
newTab: boolean;
|
||||||
|
background: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavigateHistoryPrevOperation {
|
||||||
|
type: typeof NAVIGATE_HISTORY_PREV;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavigateHistoryNextOperation {
|
||||||
|
type: typeof NAVIGATE_HISTORY_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavigateLinkPrevOperation {
|
||||||
|
type: typeof NAVIGATE_LINK_PREV;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavigateLinkNextOperation {
|
||||||
|
type: typeof NAVIGATE_LINK_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavigateParentOperation {
|
||||||
|
type: typeof NAVIGATE_PARENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavigateRootOperation {
|
||||||
|
type: typeof NAVIGATE_ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FocusInputOperation {
|
||||||
|
type: typeof FOCUS_INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageSourceOperation {
|
||||||
|
type: typeof PAGE_SOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageHomeOperation {
|
||||||
|
type: typeof PAGE_HOME;
|
||||||
|
newTab: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabCloseOperation {
|
||||||
|
type: typeof TAB_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabCloseForceOperation {
|
||||||
|
type: typeof TAB_CLOSE_FORCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabCloseRightOperation {
|
||||||
|
type: typeof TAB_CLOSE_RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabReopenOperation {
|
||||||
|
type: typeof TAB_REOPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabPrevOperation {
|
||||||
|
type: typeof TAB_PREV;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabNextOperation {
|
||||||
|
type: typeof TAB_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabFirstOperation {
|
||||||
|
type: typeof TAB_FIRST;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabLastOperation {
|
||||||
|
type: typeof TAB_LAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabPrevSelOperation {
|
||||||
|
type: typeof TAB_PREV_SEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabReloadOperation {
|
||||||
|
type: typeof TAB_RELOAD;
|
||||||
|
cache: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabPinOperation {
|
||||||
|
type: typeof TAB_PIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabUnpinOperation {
|
||||||
|
type: typeof TAB_UNPIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabTogglePinnedOperation {
|
||||||
|
type: typeof TAB_TOGGLE_PINNED;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabDuplicateOperation {
|
||||||
|
type: typeof TAB_DUPLICATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ZoomInOperation {
|
||||||
|
type: typeof ZOOM_IN;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ZoomOutOperation {
|
||||||
|
type: typeof ZOOM_OUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ZoomNeutralOperation {
|
||||||
|
type: typeof ZOOM_NEUTRAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UrlsYankOperation {
|
||||||
|
type: typeof URLS_YANK;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UrlsPasteOperation {
|
||||||
|
type: typeof URLS_PASTE;
|
||||||
|
newTab: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FindStartOperation {
|
||||||
|
type: typeof FIND_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FindNextOperation {
|
||||||
|
type: typeof FIND_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FindPrevOperation {
|
||||||
|
type: typeof FIND_PREV;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkSetPrefixOperation {
|
||||||
|
type: typeof MARK_SET_PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkJumpPrefixOperation {
|
||||||
|
type: typeof MARK_JUMP_PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Operation =
|
||||||
|
CancelOperation |
|
||||||
|
AddonEnableOperation |
|
||||||
|
AddonDisableOperation |
|
||||||
|
AddonToggleEnabledOperation |
|
||||||
|
CommandShowOperation |
|
||||||
|
CommandShowOpenOperation |
|
||||||
|
CommandShowTabopenOperation |
|
||||||
|
CommandShowWinopenOperation |
|
||||||
|
CommandShowBufferOperation |
|
||||||
|
CommandShowAddbookmarkOperation |
|
||||||
|
ScrollVerticallyOperation |
|
||||||
|
ScrollHorizonallyOperation |
|
||||||
|
ScrollPagesOperation |
|
||||||
|
ScrollTopOperation |
|
||||||
|
ScrollBottomOperation |
|
||||||
|
ScrollHomeOperation |
|
||||||
|
ScrollEndOperation |
|
||||||
|
FollowStartOperation |
|
||||||
|
NavigateHistoryPrevOperation |
|
||||||
|
NavigateHistoryNextOperation |
|
||||||
|
NavigateLinkPrevOperation |
|
||||||
|
NavigateLinkNextOperation |
|
||||||
|
NavigateParentOperation |
|
||||||
|
NavigateRootOperation |
|
||||||
|
FocusInputOperation |
|
||||||
|
PageSourceOperation |
|
||||||
|
PageHomeOperation |
|
||||||
|
TabCloseOperation |
|
||||||
|
TabCloseForceOperation |
|
||||||
|
TabCloseRightOperation |
|
||||||
|
TabReopenOperation |
|
||||||
|
TabPrevOperation |
|
||||||
|
TabNextOperation |
|
||||||
|
TabFirstOperation |
|
||||||
|
TabLastOperation |
|
||||||
|
TabPrevSelOperation |
|
||||||
|
TabReloadOperation |
|
||||||
|
TabPinOperation |
|
||||||
|
TabUnpinOperation |
|
||||||
|
TabTogglePinnedOperation |
|
||||||
|
TabDuplicateOperation |
|
||||||
|
ZoomInOperation |
|
||||||
|
ZoomOutOperation |
|
||||||
|
ZoomNeutralOperation |
|
||||||
|
UrlsYankOperation |
|
||||||
|
UrlsPasteOperation |
|
||||||
|
FindStartOperation |
|
||||||
|
FindNextOperation |
|
||||||
|
FindPrevOperation |
|
||||||
|
MarkSetPrefixOperation |
|
||||||
|
MarkJumpPrefixOperation;
|
||||||
|
|
||||||
|
const assertOptionalBoolean = (obj: any, name: string) => {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, name) &&
|
||||||
|
typeof obj[name] !== 'boolean') {
|
||||||
|
throw new TypeError(`Not a boolean parameter '${name}'`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default operations;
|
const assertRequiredNumber = (obj: any, name: string) => {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(obj, name) ||
|
||||||
|
typeof obj[name] !== 'number') {
|
||||||
|
throw new TypeError(`Missing number parameter '${name}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line complexity, max-lines-per-function
|
||||||
|
export const valueOf = (o: any): Operation => {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(o, 'type')) {
|
||||||
|
throw new TypeError(`missing 'type' field`);
|
||||||
|
}
|
||||||
|
switch (o.type) {
|
||||||
|
case COMMAND_SHOW_OPEN:
|
||||||
|
case COMMAND_SHOW_TABOPEN:
|
||||||
|
case COMMAND_SHOW_WINOPEN:
|
||||||
|
case COMMAND_SHOW_ADDBOOKMARK:
|
||||||
|
assertOptionalBoolean(o, 'alter');
|
||||||
|
return { type: o.type, alter: Boolean(o.alter) };
|
||||||
|
case SCROLL_VERTICALLY:
|
||||||
|
case SCROLL_HORIZONALLY:
|
||||||
|
case SCROLL_PAGES:
|
||||||
|
assertRequiredNumber(o, 'count');
|
||||||
|
return { type: o.type, count: Number(o.count) };
|
||||||
|
case FOLLOW_START:
|
||||||
|
assertOptionalBoolean(o, 'newTab');
|
||||||
|
assertOptionalBoolean(o, 'background');
|
||||||
|
return {
|
||||||
|
type: FOLLOW_START,
|
||||||
|
newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
|
||||||
|
background: Boolean(typeof o.background === undefined ? true : o.background), // eslint-disable-line max-len
|
||||||
|
};
|
||||||
|
case PAGE_HOME:
|
||||||
|
assertOptionalBoolean(o, 'newTab');
|
||||||
|
return {
|
||||||
|
type: PAGE_HOME,
|
||||||
|
newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
|
||||||
|
};
|
||||||
|
case TAB_RELOAD:
|
||||||
|
assertOptionalBoolean(o, 'cache');
|
||||||
|
return {
|
||||||
|
type: TAB_RELOAD,
|
||||||
|
cache: Boolean(typeof o.cache === undefined ? false : o.cache),
|
||||||
|
};
|
||||||
|
case URLS_PASTE:
|
||||||
|
assertOptionalBoolean(o, 'newTab');
|
||||||
|
return {
|
||||||
|
type: URLS_PASTE,
|
||||||
|
newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
|
||||||
|
};
|
||||||
|
case CANCEL:
|
||||||
|
case ADDON_ENABLE:
|
||||||
|
case ADDON_DISABLE:
|
||||||
|
case ADDON_TOGGLE_ENABLED:
|
||||||
|
case COMMAND_SHOW:
|
||||||
|
case COMMAND_SHOW_BUFFER:
|
||||||
|
case SCROLL_TOP:
|
||||||
|
case SCROLL_BOTTOM:
|
||||||
|
case SCROLL_HOME:
|
||||||
|
case SCROLL_END:
|
||||||
|
case NAVIGATE_HISTORY_PREV:
|
||||||
|
case NAVIGATE_HISTORY_NEXT:
|
||||||
|
case NAVIGATE_LINK_PREV:
|
||||||
|
case NAVIGATE_LINK_NEXT:
|
||||||
|
case NAVIGATE_PARENT:
|
||||||
|
case NAVIGATE_ROOT:
|
||||||
|
case FOCUS_INPUT:
|
||||||
|
case PAGE_SOURCE:
|
||||||
|
case TAB_CLOSE:
|
||||||
|
case TAB_CLOSE_FORCE:
|
||||||
|
case TAB_CLOSE_RIGHT:
|
||||||
|
case TAB_REOPEN:
|
||||||
|
case TAB_PREV:
|
||||||
|
case TAB_NEXT:
|
||||||
|
case TAB_FIRST:
|
||||||
|
case TAB_LAST:
|
||||||
|
case TAB_PREV_SEL:
|
||||||
|
case TAB_PIN:
|
||||||
|
case TAB_UNPIN:
|
||||||
|
case TAB_TOGGLE_PINNED:
|
||||||
|
case TAB_DUPLICATE:
|
||||||
|
case ZOOM_IN:
|
||||||
|
case ZOOM_OUT:
|
||||||
|
case ZOOM_NEUTRAL:
|
||||||
|
case URLS_YANK:
|
||||||
|
case FIND_START:
|
||||||
|
case FIND_NEXT:
|
||||||
|
case FIND_PREV:
|
||||||
|
case MARK_SET_PREFIX:
|
||||||
|
case MARK_JUMP_PREFIX:
|
||||||
|
return { type: o.type };
|
||||||
|
}
|
||||||
|
throw new Error('unknown operation type: ' + o.type);
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import operations from '../operations';
|
import * as operations from '../operations';
|
||||||
import * as properties from './properties';
|
import * as properties from './properties';
|
||||||
|
|
||||||
const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties'];
|
const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties'];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
interface Key {
|
export interface Key {
|
||||||
key: string;
|
key: string;
|
||||||
shiftKey: boolean | undefined;
|
shiftKey: boolean | undefined;
|
||||||
ctrlKey: boolean | undefined;
|
ctrlKey: boolean | undefined;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import * as followControllerActions from 'content/actions/follow-controller';
|
import * as followControllerActions from 'content/actions/follow-controller';
|
||||||
|
|
||||||
describe('follow-controller actions', () => {
|
describe('follow-controller actions', () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import * as inputActions from 'content/actions/input';
|
import * as inputActions from 'content/actions/input';
|
||||||
|
|
||||||
describe("input actions", () => {
|
describe("input actions", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import * as markActions from 'content/actions/mark';
|
import * as markActions from 'content/actions/mark';
|
||||||
|
|
||||||
describe('mark actions', () => {
|
describe('mark actions', () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import * as settingActions from 'content/actions/setting';
|
import * as settingActions from 'content/actions/setting';
|
||||||
|
|
||||||
describe("setting actions", () => {
|
describe("setting actions", () => {
|
||||||
|
|
|
@ -21,12 +21,14 @@ describe('InputComponent', () => {
|
||||||
++b;
|
++b;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
component.onKeyDown({ key: 'a' });
|
|
||||||
component.onKeyDown({ key: 'b' });
|
let elem = document.body;
|
||||||
component.onKeyPress({ key: 'a' });
|
component.onKeyDown({ key: 'a', target: elem });
|
||||||
component.onKeyUp({ key: 'a' });
|
component.onKeyDown({ key: 'b', target: elem });
|
||||||
component.onKeyPress({ key: 'b' });
|
component.onKeyPress({ key: 'a', target: elem });
|
||||||
component.onKeyUp({ key: 'b' });
|
component.onKeyUp({ key: 'a', target: elem });
|
||||||
|
component.onKeyPress({ key: 'b', target: elem });
|
||||||
|
component.onKeyUp({ key: 'b', target: elem });
|
||||||
|
|
||||||
expect(a).is.equals(1);
|
expect(a).is.equals(1);
|
||||||
expect(b).is.equals(1);
|
expect(b).is.equals(1);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import addonReducer from 'content/reducers/addon';
|
import addonReducer from 'content/reducers/addon';
|
||||||
|
|
||||||
describe("addon reducer", () => {
|
describe("addon reducer", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import findReducer from 'content/reducers/find';
|
import findReducer from 'content/reducers/find';
|
||||||
|
|
||||||
describe("find reducer", () => {
|
describe("find reducer", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import followControllerReducer from 'content/reducers/follow-controller';
|
import followControllerReducer from 'content/reducers/follow-controller';
|
||||||
|
|
||||||
describe('follow-controller reducer', () => {
|
describe('follow-controller reducer', () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import inputReducer from 'content/reducers/input';
|
import inputReducer from 'content/reducers/input';
|
||||||
|
|
||||||
describe("input reducer", () => {
|
describe("input reducer", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import reducer from 'content/reducers/mark';
|
import reducer from 'content/reducers/mark';
|
||||||
|
|
||||||
describe("mark reducer", () => {
|
describe("mark reducer", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from 'content/actions';
|
||||||
import settingReducer from 'content/reducers/setting';
|
import settingReducer from 'content/reducers/setting';
|
||||||
|
|
||||||
describe("content setting reducer", () => {
|
describe("content setting reducer", () => {
|
||||||
|
|
41
test/shared/operations.test.ts
Normal file
41
test/shared/operations.test.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import * as operations from 'shared/operations';
|
||||||
|
|
||||||
|
describe('operations', () => {
|
||||||
|
describe('#valueOf', () => {
|
||||||
|
it('returns an Operation', () => {
|
||||||
|
let op: operations.Operation = operations.valueOf({
|
||||||
|
type: operations.SCROLL_VERTICALLY,
|
||||||
|
count: 10,
|
||||||
|
});
|
||||||
|
expect(op.type).to.equal(operations.SCROLL_VERTICALLY);
|
||||||
|
expect(op.count).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an Error on missing required parameter', () => {
|
||||||
|
expect(() => operations.valueOf({
|
||||||
|
type: operations.SCROLL_VERTICALLY,
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fills default valus of optional parameter', () => {
|
||||||
|
let op: operations.Operation = operations.valueOf({
|
||||||
|
type: operations.COMMAND_SHOW_OPEN,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(op.type).to.equal(operations.COMMAND_SHOW_OPEN)
|
||||||
|
expect(op.alter).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an Error on mismatch of parameter', () => {
|
||||||
|
expect(() => operations.valueOf({
|
||||||
|
type: operations.SCROLL_VERTICALLY,
|
||||||
|
count: '10',
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
|
||||||
|
expect(() => valueOf({
|
||||||
|
type: operations.COMMAND_SHOW_OPEN,
|
||||||
|
alter: 'true',
|
||||||
|
})).to.throw(TypeError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
|
@ -2,11 +2,11 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
"lib": ["es6", "dom", "es2017"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
|
"noEmit": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
|
@ -29,5 +29,8 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
||||||
"typeRoots": ["node_modules/@types", "node_modules/web-ext-types"]
|
"typeRoots": ["node_modules/@types", "node_modules/web-ext-types"]
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue