diff options
| author | Shin'ya Ueoka <ueokande@i-beam.org> | 2019-05-02 14:08:51 +0900 | 
|---|---|---|
| committer | Shin'ya Ueoka <ueokande@i-beam.org> | 2019-05-05 23:59:59 +0900 | 
| commit | d01db82c0dca352de2d7644c383d388fc3ec0366 (patch) | |
| tree | ff09ebc545ce00192dff9e1119b0c9e2cf92fdef /src | |
| parent | 992b3ac65d7fe86ac7bc3b560960c8bcf86bec69 (diff) | |
Types src/content
Diffstat (limited to 'src')
47 files changed, 1358 insertions, 464 deletions
diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts index 4e9c106..fa09512 100644 --- a/src/background/controllers/OperationController.ts +++ b/src/background/controllers/OperationController.ts @@ -1,4 +1,4 @@ -import operations from '../../shared/operations'; +import * as operations from '../../shared/operations';  import FindUseCase from '../usecases/FindUseCase';  import ConsoleUseCase from '../usecases/ConsoleUseCase';  import TabUseCase from '../usecases/TabUseCase'; @@ -25,7 +25,7 @@ export default class OperationController {    }    // eslint-disable-next-line complexity, max-lines-per-function -  exec(operation: any): Promise<any> { +  exec(operation: operations.Operation): Promise<any> {      switch (operation.type) {      case operations.TAB_CLOSE:        return this.tabUseCase.close(false); diff --git a/src/background/controllers/VersionController.ts b/src/background/controllers/VersionController.ts index f402ed0..2e2a197 100644 --- a/src/background/controllers/VersionController.ts +++ b/src/background/controllers/VersionController.ts @@ -7,7 +7,7 @@ export default class VersionController {      this.versionUseCase = new VersionUseCase();    } -  notify(): void { +  notify(): Promise<void> {      return this.versionUseCase.notify();    }  } diff --git a/src/background/domains/Setting.ts b/src/background/domains/Setting.ts index 106ec0f..b2b1ff2 100644 --- a/src/background/domains/Setting.ts +++ b/src/background/domains/Setting.ts @@ -1,22 +1,30 @@  import DefaultSettings from '../../shared/settings/default';  import * as settingsValues from '../../shared/settings/values'; +type SettingValue = { +    source: string, +    json: string, +    form: any +} +  export default class Setting { -  constructor({ source, json, form }) { +  private obj: SettingValue; + +  constructor({ source, json, form }: SettingValue) {      this.obj = {        source, json, form      };    } -  get source() { +  get source(): string {      return this.obj.source;    } -  get json() { +  get json(): string {      return this.obj.json;    } -  get form() { +  get form(): any {      return this.obj.form;    } @@ -33,11 +41,11 @@ export default class Setting {      return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value };    } -  serialize() { +  serialize(): SettingValue {      return this.obj;    } -  static deserialize(obj) { +  static deserialize(obj: SettingValue): Setting {      return new Setting({ source: obj.source, json: obj.json, form: obj.form });    } diff --git a/src/background/infrastructures/ConsoleClient.ts b/src/background/infrastructures/ConsoleClient.ts index 7ad5d24..c162634 100644 --- a/src/background/infrastructures/ConsoleClient.ts +++ b/src/background/infrastructures/ConsoleClient.ts @@ -1,4 +1,4 @@ -import messages from '../../shared/messages'; +import * as messages from '../../shared/messages';  export default class ConsoleClient {    showCommand(tabId: number, command: string): Promise<any> { diff --git a/src/background/infrastructures/ContentMessageClient.ts b/src/background/infrastructures/ContentMessageClient.ts index 20057c7..d4bc476 100644 --- a/src/background/infrastructures/ContentMessageClient.ts +++ b/src/background/infrastructures/ContentMessageClient.ts @@ -1,4 +1,4 @@ -import messages from '../../shared/messages'; +import * as messages from '../../shared/messages';  export default class ContentMessageClient {    async broadcastSettingsChanged(): Promise<void> { diff --git a/src/background/infrastructures/ContentMessageListener.ts b/src/background/infrastructures/ContentMessageListener.ts index 81d3232..1cc2696 100644 --- a/src/background/infrastructures/ContentMessageListener.ts +++ b/src/background/infrastructures/ContentMessageListener.ts @@ -1,4 +1,4 @@ -import messages from '../../shared/messages'; +import * as messages from '../../shared/messages';  import CompletionGroup from '../domains/CompletionGroup';  import CommandController from '../controllers/CommandController';  import SettingController from '../controllers/SettingController'; @@ -68,7 +68,9 @@ export default class ContentMessageListener {      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) {      case messages.CONSOLE_QUERY_COMPLETIONS:        return this.onConsoleQueryCompletions(message.text); diff --git a/src/background/presenters/NotifyPresenter.ts b/src/background/presenters/NotifyPresenter.ts index c83c205..23932f7 100644 --- a/src/background/presenters/NotifyPresenter.ts +++ b/src/background/presenters/NotifyPresenter.ts @@ -1,11 +1,11 @@  const NOTIFICATION_ID = 'vimvixen-update';  export default class NotifyPresenter { -  notify( +  async notify(      title: string,      message: string,      onclick: () => void, -  ): Promise<string> { +  ): Promise<void> {      const listener = (id: string) => {        if (id !== NOTIFICATION_ID) {          return; @@ -17,7 +17,7 @@ export default class NotifyPresenter {      };      browser.notifications.onClicked.addListener(listener); -    return browser.notifications.create(NOTIFICATION_ID, { +    await browser.notifications.create(NOTIFICATION_ID, {        'type': 'basic',        'iconUrl': browser.extension.getURL('resources/icon_48x48.png'),        title, diff --git a/src/background/usecases/VersionUseCase.ts b/src/background/usecases/VersionUseCase.ts index 207f9e2..3a3cc2e 100644 --- a/src/background/usecases/VersionUseCase.ts +++ b/src/background/usecases/VersionUseCase.ts @@ -12,7 +12,7 @@ export default class VersionUseCase {      this.notifyPresenter = new NotifyPresenter();    } -  notify(): Promise<string> { +  notify(): Promise<void> {      let title = `Vim Vixen ${manifest.version} has been installed`;      let message = 'Click here to see release notes';      let url = this.releaseNoteUrl(manifest.version); diff --git a/src/console/actions/console.ts b/src/console/actions/console.ts index ceb419c..b1494b0 100644 --- a/src/console/actions/console.ts +++ b/src/console/actions/console.ts @@ -1,4 +1,4 @@ -import messages from '../../shared/messages'; +import * as messages from '../../shared/messages';  import * as actions from './index';  const hide = (): actions.ConsoleAction => { diff --git a/src/console/index.tsx b/src/console/index.tsx index ee3a8ee..b655154 100644 --- a/src/console/index.tsx +++ b/src/console/index.tsx @@ -1,4 +1,4 @@ -import messages from '../shared/messages'; +import * as messages from '../shared/messages';  import reducers from './reducers';  import { createStore, applyMiddleware } from 'redux';  import promise from 'redux-promise'; @@ -23,15 +23,16 @@ window.addEventListener('load', () => {  });  const onMessage = (message: any): any => { -  switch (message.type) { +  let msg = messages.valueOf(message); +  switch (msg.type) {    case messages.CONSOLE_SHOW_COMMAND: -    return store.dispatch(consoleActions.showCommand(message.command)); +    return store.dispatch(consoleActions.showCommand(msg.command));    case messages.CONSOLE_SHOW_FIND:      return store.dispatch(consoleActions.showFind());    case messages.CONSOLE_SHOW_ERROR: -    return store.dispatch(consoleActions.showError(message.text)); +    return store.dispatch(consoleActions.showError(msg.text));    case messages.CONSOLE_SHOW_INFO: -    return store.dispatch(consoleActions.showInfo(message.text)); +    return store.dispatch(consoleActions.showInfo(msg.text));    case messages.CONSOLE_HIDE:      return store.dispatch(consoleActions.hide());    } diff --git a/src/content/MessageListener.ts b/src/content/MessageListener.ts new file mode 100644 index 0000000..105d028 --- /dev/null +++ b/src/content/MessageListener.ts @@ -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); +      }, +    ); +  } +} diff --git a/src/content/actions/addon.ts b/src/content/actions/addon.ts index b30cf16..8dedae0 100644 --- a/src/content/actions/addon.ts +++ b/src/content/actions/addon.ts @@ -1,11 +1,11 @@ -import messages from 'shared/messages'; -import actions from 'content/actions'; +import * as messages from '../../shared/messages'; +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({      type: messages.ADDON_ENABLED_RESPONSE,      enabled, diff --git a/src/content/actions/find.ts b/src/content/actions/find.ts index e08d7e5..6dd2ae6 100644 --- a/src/content/actions/find.ts +++ b/src/content/actions/find.ts @@ -5,28 +5,41 @@  // NOTE: window.find is not standard API  // https://developer.mozilla.org/en-US/docs/Web/API/Window/find -import messages from 'shared/messages'; -import actions from 'content/actions'; +import * as messages from '../../shared/messages'; +import * as actions from './index';  import * as consoleFrames from '../console-frames'; -const find = (string, backwards) => { +const find = (str: string, backwards: boolean): boolean => {    let caseSensitive = false;    let wrapScan = true;    // NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work    // 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) {      return found;    } -  window.getSelection().removeAllRanges(); -  return window.find(string, caseSensitive, backwards, wrapScan); +  let sel = window.getSelection(); +  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) { -    window.getSelection().removeAllRanges(); +    let sel = window.getSelection(); +    if (sel) { +      sel.removeAllRanges(); +    }    }    let keyword = currentKeyword; @@ -41,7 +54,8 @@ const findNext = async(currentKeyword, reset, backwards) => {      });    }    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);    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);  }; -const prev = (currentKeyword, reset) => { +const prev = ( +  currentKeyword: string, reset: boolean, +): Promise<actions.FindAction> => {    return findNext(currentKeyword, reset, true);  }; diff --git a/src/content/actions/follow-controller.ts b/src/content/actions/follow-controller.ts index 006b248..115b3b6 100644 --- a/src/content/actions/follow-controller.ts +++ b/src/content/actions/follow-controller.ts @@ -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 {      type: actions.FOLLOW_CONTROLLER_ENABLE,      newTab, @@ -8,20 +10,20 @@ const enable = (newTab, background) => {    };  }; -const disable = () => { +const disable = (): actions.FollowAction => {    return {      type: actions.FOLLOW_CONTROLLER_DISABLE,    };  }; -const keyPress = (key) => { +const keyPress = (key: string): actions.FollowAction => {    return {      type: actions.FOLLOW_CONTROLLER_KEY_PRESS,      key: key    };  }; -const backspace = () => { +const backspace = (): actions.FollowAction => {    return {      type: actions.FOLLOW_CONTROLLER_BACKSPACE,    }; diff --git a/src/content/actions/index.ts b/src/content/actions/index.ts index 0a16fdf..18d0a69 100644 --- a/src/content/actions/index.ts +++ b/src/content/actions/index.ts @@ -1,31 +1,120 @@ -export default { -  // Enable/disable -  ADDON_SET_ENABLED: 'addon.set.enabled', - -  // Settings -  SETTING_SET: 'setting.set', - -  // User input -  INPUT_KEY_PRESS: 'input.key.press', -  INPUT_CLEAR_KEYS: 'input.clear.keys', - -  // Completion -  COMPLETION_SET_ITEMS: 'completion.set.items', -  COMPLETION_SELECT_NEXT: 'completions.select.next', -  COMPLETION_SELECT_PREV: 'completions.select.prev', - -  // Follow -  FOLLOW_CONTROLLER_ENABLE: 'follow.controller.enable', -  FOLLOW_CONTROLLER_DISABLE: 'follow.controller.disable', -  FOLLOW_CONTROLLER_KEY_PRESS: 'follow.controller.key.press', -  FOLLOW_CONTROLLER_BACKSPACE: 'follow.controller.backspace', - -  // Find -  FIND_SET_KEYWORD: 'find.set.keyword', - -  // Mark -  MARK_START_SET: 'mark.start.set', -  MARK_START_JUMP: 'mark.start.jump', -  MARK_CANCEL: 'mark.cancel', -  MARK_SET_LOCAL: 'mark.set.local', -}; +import Redux from 'redux'; + +// Enable/disable +export const ADDON_SET_ENABLED = 'addon.set.enabled'; + +// Find +export const FIND_SET_KEYWORD = 'find.set.keyword'; + +// Settings +export const SETTING_SET = 'setting.set'; + +// User input +export const INPUT_KEY_PRESS = 'input.key.press'; +export const INPUT_CLEAR_KEYS = 'input.clear.keys'; + +// Completion +export const COMPLETION_SET_ITEMS = 'completion.set.items'; +export const COMPLETION_SELECT_NEXT = 'completions.select.next'; +export const COMPLETION_SELECT_PREV = 'completions.select.prev'; + +// Follow +export const FOLLOW_CONTROLLER_ENABLE = 'follow.controller.enable'; +export const FOLLOW_CONTROLLER_DISABLE = 'follow.controller.disable'; +export const FOLLOW_CONTROLLER_KEY_PRESS = 'follow.controller.key.press'; +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; diff --git a/src/content/actions/input.ts b/src/content/actions/input.ts index 465a486..21c912e 100644 --- a/src/content/actions/input.ts +++ b/src/content/actions/input.ts @@ -1,13 +1,13 @@ -import actions from 'content/actions'; +import * as actions from './index'; -const keyPress = (key) => { +const keyPress = (key: string): actions.InputAction => {    return {      type: actions.INPUT_KEY_PRESS,      key,    };  }; -const clearKeys = () => { +const clearKeys = (): actions.InputAction => {    return {      type: actions.INPUT_CLEAR_KEYS    }; diff --git a/src/content/actions/mark.ts b/src/content/actions/mark.ts index 712a811..5eb9554 100644 --- a/src/content/actions/mark.ts +++ b/src/content/actions/mark.ts @@ -1,19 +1,19 @@ -import actions from 'content/actions'; -import messages from 'shared/messages'; +import * as actions from './index'; +import * as messages from '../../shared/messages'; -const startSet = () => { +const startSet = (): actions.MarkAction => {    return { type: actions.MARK_START_SET };  }; -const startJump = () => { +const startJump = (): actions.MarkAction => {    return { type: actions.MARK_START_JUMP };  }; -const cancel = () => { +const cancel = (): actions.MarkAction => {    return { type: actions.MARK_CANCEL };  }; -const setLocal = (key, x, y) => { +const setLocal = (key: string, x: number, y: number): actions.MarkAction => {    return {      type: actions.MARK_SET_LOCAL,      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({      type: messages.MARK_SET_GLOBAL,      key,      x,      y,    }); -  return { type: '' }; +  return { type: actions.NOOP };  }; -const jumpGlobal = (key) => { +const jumpGlobal = (key: string): actions.MarkAction => {    browser.runtime.sendMessage({      type: messages.MARK_JUMP_GLOBAL,      key,    }); -  return { type: '' }; +  return { type: actions.NOOP };  };  export { diff --git a/src/content/actions/operation.ts b/src/content/actions/operation.ts index ed9b2cf..6acb407 100644 --- a/src/content/actions/operation.ts +++ b/src/content/actions/operation.ts @@ -1,16 +1,21 @@ -import operations from 'shared/operations'; -import messages from 'shared/messages'; -import * as scrolls from 'content/scrolls'; -import * as navigates from 'content/navigates'; -import * as focuses from 'content/focuses'; -import * as urls from 'content/urls'; -import * as consoleFrames from 'content/console-frames'; +import * as operations from '../../shared/operations'; +import * as actions from './index'; +import * as messages from '../../shared/messages'; +import * as scrolls from '../scrolls'; +import * as navigates from '../navigates'; +import * as focuses from '../focuses'; +import * as urls from '../urls'; +import * as consoleFrames from '../console-frames';  import * as addonActions from './addon';  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 -const exec = (operation, settings, addonEnabled) => { +const exec = ( +  operation: operations.Operation, +  settings: any, +  addonEnabled: boolean, +): Promise<actions.Action> | actions.Action => {    let smoothscroll = settings.properties.smoothscroll ||      properties.defaults.smoothscroll;    switch (operation.type) { @@ -98,7 +103,7 @@ const exec = (operation, settings, addonEnabled) => {        operation,      });    } -  return { type: '' }; +  return { type: actions.NOOP };  };  export { exec }; diff --git a/src/content/actions/setting.ts b/src/content/actions/setting.ts index 1c15dd7..a8f049a 100644 --- a/src/content/actions/setting.ts +++ b/src/content/actions/setting.ts @@ -1,15 +1,15 @@ -import actions from 'content/actions'; -import * as keyUtils from 'shared/utils/keys'; -import operations from 'shared/operations'; -import messages from 'shared/messages'; +import * as actions from './index'; +import * as keyUtils from '../../shared/utils/keys'; +import * as operations from '../../shared/operations'; +import * as messages from '../../shared/messages';  const reservedKeymaps = {    '<Esc>': { type: operations.CANCEL },    '<C-[>': { type: operations.CANCEL },  }; -const set = (value) => { -  let entries = []; +const set = (value: any): actions.SettingAction => { +  let entries: any[] = [];    if (value.keymaps) {      let keymaps = { ...value.keymaps, ...reservedKeymaps };      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({      type: messages.SETTINGS_QUERY,    }); diff --git a/src/content/components/common/follow.ts b/src/content/components/common/follow.ts index 63ce603..67f2dd9 100644 --- a/src/content/components/common/follow.ts +++ b/src/content/components/common/follow.ts @@ -1,6 +1,8 @@ -import messages from 'shared/messages'; +import MessageListener from '../../MessageListener';  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 = [    'a', 'button', 'input', 'textarea', 'area', @@ -8,8 +10,22 @@ const TARGET_SELECTOR = [    '[role="button"]', 'summary'  ].join(','); +interface Size { +  width: number; +  height: number; +} + +interface Point { +  x: number; +  y: number; +} -const inViewport = (win, element, viewSize, framePosition) => { +const inViewport = ( +  win: Window, +  element: Element, +  viewSize: Size, +  framePosition: Point, +): boolean => {    let {      top, left, bottom, right    } = dom.viewportRect(element); @@ -30,34 +46,44 @@ const inViewport = (win, element, viewSize, framePosition) => {    return true;  }; -const isAriaHiddenOrAriaDisabled = (win, element) => { +const isAriaHiddenOrAriaDisabled = (win: Window, element: Element): boolean => {    if (!element || win.document.documentElement === element) {      return false;    }    for (let attr of ['aria-hidden', 'aria-disabled']) { -    if (element.hasAttribute(attr)) { -      let hidden = element.getAttribute(attr).toLowerCase(); +    let value = element.getAttribute(attr); +    if (value !== null) { +      let hidden = value.toLowerCase();        if (hidden === '' || hidden === 'true') {          return true;        }      }    } -  return isAriaHiddenOrAriaDisabled(win, element.parentNode); +  return isAriaHiddenOrAriaDisabled(win, element.parentElement as Element);  };  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.store = store;      this.newTab = false;      this.background = false;      this.hints = {};      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) {        return false;      } @@ -69,7 +95,7 @@ export default class Follow {      return true;    } -  openLink(element) { +  openLink(element: HTMLAreaElement|HTMLAnchorElement) {      // Browser prevent new tab by link with target='_blank'      if (!this.newTab && element.getAttribute('target') !== '_blank') {        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);      sender.postMessage(JSON.stringify({        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) {        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))        .forEach(key => this.hints[key].show());      Object.keys(this.hints).filter(key => !key.startsWith(keys)) @@ -128,18 +154,19 @@ export default class Follow {      this.targets = [];    } -  activateHints(keys) { +  activateHints(keys: string) {      let hint = this.hints[keys];      if (!hint) {        return;      } -    let element = hint.target; +    let element = hint.getTarget();      switch (element.tagName.toLowerCase()) {      case 'a': +      return this.openLink(element as HTMLAnchorElement);      case 'area': -      return this.openLink(element); +      return this.openLink(element as HTMLAreaElement);      case 'input': -      switch (element.type) { +      switch ((element as HTMLInputElement).type) {        case 'file':        case 'checkbox':        case 'radio': @@ -166,7 +193,7 @@ export default class Follow {      }    } -  onMessage(message, sender) { +  onMessage(message: messages.Message, sender: any) {      switch (message.type) {      case messages.FOLLOW_REQUEST_COUNT_TARGETS:        return this.countHints(sender, message.viewSize, message.framePosition); @@ -178,19 +205,23 @@ export default class Follow {      case messages.FOLLOW_ACTIVATE:        return this.activateHints(message.keys);      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 filtered = Array.prototype.filter.call(all, (element) => { +    let filtered = Array.prototype.filter.call(all, (element: HTMLElement) => {        let style = win.getComputedStyle(element);        // AREA's 'display' in Browser style is 'none'        return (element.tagName === 'AREA' || style.display !== 'none') &&          style.visibility !== 'hidden' && -        element.type !== 'hidden' && +        (element as HTMLInputElement).type !== 'hidden' &&          element.offsetHeight > 0 &&          !isAriaHiddenOrAriaDisabled(win, element) &&          inViewport(win, element, viewSize, framePosition); diff --git a/src/content/components/common/hint.ts b/src/content/components/common/hint.ts index 1472587..2fcbb0f 100644 --- a/src/content/components/common/hint.ts +++ b/src/content/components/common/hint.ts @@ -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);    if (element.tagName !== 'AREA') { @@ -14,17 +19,21 @@ const hintPosition = (element) => {  };  export default class Hint { -  constructor(target, tag) { -    if (!(document.body instanceof HTMLElement)) { -      throw new TypeError('target is not an HTMLElement'); -    } +  private target: HTMLElement; -    this.target = target; +  private element: HTMLElement; +  constructor(target: HTMLElement, tag: string) {      let doc = target.ownerDocument; +    if (doc === null) { +      throw new TypeError('ownerDocument is null'); +    } +      let { x, y } = hintPosition(target);      let { scrollX, scrollY } = window; +    this.target = target; +      this.element = doc.createElement('span');      this.element.className = 'vimvixen-hint';      this.element.textContent = tag; @@ -35,15 +44,19 @@ export default class Hint {      doc.body.append(this.element);    } -  show() { +  show(): void {      this.element.style.display = 'inline';    } -  hide() { +  hide(): void {      this.element.style.display = 'none';    } -  remove() { +  remove(): void {      this.element.remove();    } + +  getTarget(): HTMLElement { +    return this.target; +  }  } diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts index bcab4fa..9b5164e 100644 --- a/src/content/components/common/index.ts +++ b/src/content/components/common/index.ts @@ -2,33 +2,37 @@ import InputComponent from './input';  import FollowComponent from './follow';  import MarkComponent from './mark';  import KeymapperComponent from './keymapper'; -import * as settingActions from 'content/actions/setting'; -import messages from 'shared/messages'; +import * as settingActions from '../../actions/setting'; +import * as messages from '../../../shared/messages'; +import MessageListener from '../../MessageListener';  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 { -  constructor(win, store) { -    const input = new InputComponent(win.document.body, store); -    const follow = new FollowComponent(win, store); +  private win: Window; + +  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 keymapper = new KeymapperComponent(store); -    input.onKey(key => follow.key(key)); -    input.onKey(key => mark.key(key)); -    input.onKey(key => keymapper.key(key)); +    input.onKey((key: keys.Key) => follow.key(key)); +    input.onKey((key: keys.Key) => mark.key(key)); +    input.onKey((key: keys.Key) => keymapper.key(key));      this.win = win;      this.store = store; -    this.prevEnabled = undefined; -    this.prevBlacklist = undefined;      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;      switch (message.type) {      case messages.SETTINGS_CHANGED: @@ -40,12 +44,13 @@ export default class Common {    reloadSettings() {      try { -      this.store.dispatch(settingActions.load()).then(({ value: settings }) => { -        let enabled = !blacklists.includes( -          settings.blacklist, this.win.location.href -        ); -        this.store.dispatch(addonActions.setEnabled(enabled)); -      }); +      this.store.dispatch(settingActions.load()) +        .then(({ value: settings }: any) => { +          let enabled = !blacklists.includes( +            settings.blacklist, this.win.location.href +          ); +          this.store.dispatch(addonActions.setEnabled(enabled)); +        });      } catch (e) {        // Sometime sendMessage fails when background script is not ready.        console.warn(e); diff --git a/src/content/components/common/input.ts b/src/content/components/common/input.ts index eefaf10..64eb5f3 100644 --- a/src/content/components/common/input.ts +++ b/src/content/components/common/input.ts @@ -1,12 +1,16 @@ -import * as dom from 'shared/utils/dom'; -import * as keys from 'shared/utils/keys'; +import * as dom from '../../../shared/utils/dom'; +import * as keys from '../../../shared/utils/keys'; -const cancelKey = (e) => { +const cancelKey = (e: KeyboardEvent): boolean => {    return e.key === 'Escape' || e.key === '[' && e.ctrlKey;  };  export default class InputComponent { -  constructor(target) { +  private pressed: {[key: string]: string} = {}; + +  private onKeyListeners: ((key: keys.Key) => boolean)[] = []; + +  constructor(target: HTMLElement) {      this.pressed = {};      this.onKeyListeners = []; @@ -15,11 +19,11 @@ export default class InputComponent {      target.addEventListener('keyup', this.onKeyUp.bind(this));    } -  onKey(cb) { +  onKey(cb: (key: keys.Key) => boolean) {      this.onKeyListeners.push(cb);    } -  onKeyPress(e) { +  onKeyPress(e: KeyboardEvent) {      if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {        return;      } @@ -27,7 +31,7 @@ export default class InputComponent {      this.capture(e);    } -  onKeyDown(e) { +  onKeyDown(e: KeyboardEvent) {      if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {        return;      } @@ -35,14 +39,19 @@ export default class InputComponent {      this.capture(e);    } -  onKeyUp(e) { +  onKeyUp(e: KeyboardEvent) {      delete this.pressed[e.key];    } -  capture(e) { -    if (this.fromInput(e)) { -      if (cancelKey(e) && e.target.blur) { -        e.target.blur(); +  // eslint-disable-next-line max-statements +  capture(e: KeyboardEvent) { +    let target = e.target; +    if (!(target instanceof HTMLElement)) { +      return; +    } +    if (this.fromInput(target)) { +      if (cancelKey(e) && target.blur) { +        target.blur();        }        return;      } @@ -63,13 +72,10 @@ export default class InputComponent {      }    } -  fromInput(e) { -    if (!e.target) { -      return false; -    } -    return e.target instanceof HTMLInputElement || -      e.target instanceof HTMLTextAreaElement || -      e.target instanceof HTMLSelectElement || -      dom.isContentEditable(e.target); +  fromInput(e: Element) { +    return e instanceof HTMLInputElement || +      e instanceof HTMLTextAreaElement || +      e instanceof HTMLSelectElement || +      dom.isContentEditable(e);    }  } diff --git a/src/content/components/common/keymapper.ts b/src/content/components/common/keymapper.ts index ec0d093..d9c9834 100644 --- a/src/content/components/common/keymapper.ts +++ b/src/content/components/common/keymapper.ts @@ -1,7 +1,7 @@ -import * as inputActions from 'content/actions/input'; -import * as operationActions from 'content/actions/operation'; -import operations from 'shared/operations'; -import * as keyUtils from 'shared/utils/keys'; +import * as inputActions from '../../actions/input'; +import * as operationActions from '../../actions/operation'; +import * as operations from '../../../shared/operations'; +import * as keyUtils from '../../../shared/utils/keys';  const mapStartsWith = (mapping, keys) => {    if (mapping.length < keys.length) { diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts index 0f838a9..500d03b 100644 --- a/src/content/components/common/mark.ts +++ b/src/content/components/common/mark.ts @@ -3,7 +3,7 @@ import * as scrolls from 'content/scrolls';  import * as consoleFrames from 'content/console-frames';  import * as properties from 'shared/settings/properties'; -const cancelKey = (key) => { +const cancelKey = (key): boolean => {    return key.key === 'Esc' || key.key === '[' && key.ctrlKey;  }; diff --git a/src/content/components/top-content/find.ts b/src/content/components/top-content/find.ts index 4d46d79..74b95bc 100644 --- a/src/content/components/top-content/find.ts +++ b/src/content/components/top-content/find.ts @@ -1,15 +1,17 @@ -import * as findActions from 'content/actions/find'; -import messages from 'shared/messages'; +import * as findActions from '../../actions/find'; +import * as messages from '../../../shared/messages'; +import MessageListener from '../../MessageListener';  export default class FindComponent { -  constructor(win, store) { -    this.win = win; +  private store: any; + +  constructor(store: any) {      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) {      case messages.CONSOLE_ENTER_FIND:        return this.start(message.text); @@ -20,22 +22,25 @@ export default class FindComponent {      }    } -  start(text) { +  start(text: string) {      let state = this.store.getState().find;      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));    }    next() {      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() {      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));    }  } diff --git a/src/content/components/top-content/follow-controller.ts b/src/content/components/top-content/follow-controller.ts index 7f36604..be71f6e 100644 --- a/src/content/components/top-content/follow-controller.ts +++ b/src/content/components/top-content/follow-controller.ts @@ -1,30 +1,46 @@ -import * as followControllerActions from 'content/actions/follow-controller'; -import messages from 'shared/messages'; -import HintKeyProducer from 'content/hint-key-producer'; -import * as properties from 'shared/settings/properties'; +import * as followControllerActions from '../../actions/follow-controller'; +import * as messages from '../../../shared/messages'; +import MessageListener, { WebMessageSender } from '../../MessageListener'; +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 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, '*'));  };  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.store = store;      this.state = {};      this.keys = [];      this.producer = null; -    messages.onMessage(this.onMessage.bind(this)); +    new MessageListener().onWebMessage(this.onMessage.bind(this));      store.subscribe(() => {        this.update();      });    } -  onMessage(message, sender) { +  onMessage(message: messages.Message, sender: WebMessageSender) {      switch (message.type) {      case messages.FOLLOW_START:        return this.store.dispatch( @@ -36,7 +52,7 @@ export default class FollowController {      }    } -  update() { +  update(): void {      let prevState = this.state;      this.state = this.store.getState().followController; @@ -49,8 +65,10 @@ export default class FollowController {      }    } -  updateHints() { -    let shown = this.keys.filter(key => key.startsWith(this.state.keys)); +  updateHints(): void { +    let shown = this.keys.filter((key) => { +      return key.startsWith(this.state.keys as string); +    });      if (shown.length === 1) {        this.activate();        this.store.dispatch(followControllerActions.disable()); @@ -58,18 +76,18 @@ export default class FollowController {      broadcastMessage(this.win, {        type: messages.FOLLOW_SHOW_HINTS, -      keys: this.state.keys, +      keys: this.state.keys as string,      });    } -  activate() { +  activate(): void {      broadcastMessage(this.win, {        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) {        this.store.dispatch(followControllerActions.disable());        return true; @@ -107,25 +125,28 @@ export default class FollowController {        viewSize: { width: viewWidth, height: viewHeight },        framePosition: { x: 0, y: 0 },      }), '*'); -    frameElements.forEach((element) => { -      let { left: frameX, top: frameY } = element.getBoundingClientRect(); +    frameElements.forEach((ele) => { +      let { left: frameX, top: frameY } = ele.getBoundingClientRect();        let message = JSON.stringify({          type: messages.FOLLOW_REQUEST_COUNT_TARGETS,          viewSize: { width: viewWidth, height: viewHeight },          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 = [];      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); -    sender.postMessage(JSON.stringify({ +    (sender as Window).postMessage(JSON.stringify({        type: messages.FOLLOW_CREATE_HINTS,        keysArray: produced,        newTab: this.state.newTab, diff --git a/src/content/components/top-content/index.ts b/src/content/components/top-content/index.ts index 1aaef1b..ac95ea9 100644 --- a/src/content/components/top-content/index.ts +++ b/src/content/components/top-content/index.ts @@ -2,33 +2,43 @@ import CommonComponent from '../common';  import FollowController from './follow-controller';  import FindComponent from './find';  import * as consoleFrames from '../../console-frames'; -import messages from 'shared/messages'; -import * as scrolls from 'content/scrolls'; +import * as messages from '../../../shared/messages'; +import MessageListener from '../../MessageListener'; +import * as scrolls from '../../scrolls';  export default class TopContent { +  private win: Window; -  constructor(win, store) { +  private store: any; + +  constructor(win: Window, store: any) {      this.win = win;      this.store = store;      new CommonComponent(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      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) { -    let addonState = this.store.getState().addon; - +  onWebMessage(message: messages.Message) {      switch (message.type) {      case messages.CONSOLE_UNFOCUS:        this.win.focus();        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:        return Promise.resolve({          type: messages.ADDON_ENABLED_RESPONSE, diff --git a/src/content/console-frames.ts b/src/content/console-frames.ts index ecb5a87..bd6b835 100644 --- a/src/content/console-frames.ts +++ b/src/content/console-frames.ts @@ -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');    iframe.src = browser.runtime.getURL('build/console.html');    iframe.id = 'vimvixen-console-frame'; @@ -10,13 +10,13 @@ const initialize = (doc) => {    return iframe;  }; -const blur = (doc) => { -  let iframe = doc.getElementById('vimvixen-console-frame'); -  iframe.blur(); +const blur = (doc: Document) => { +  let ele = doc.getElementById('vimvixen-console-frame') as HTMLIFrameElement; +  ele.blur();  }; -const postError = (text) => { -  browser.runtime.sendMessage({ +const postError = (text: string): Promise<any> => { +  return browser.runtime.sendMessage({      type: messages.CONSOLE_FRAME_MESSAGE,      message: {        type: messages.CONSOLE_SHOW_ERROR, @@ -25,8 +25,8 @@ const postError = (text) => {    });  }; -const postInfo = (text) => { -  browser.runtime.sendMessage({ +const postInfo = (text: string): Promise<any> => { +  return browser.runtime.sendMessage({      type: messages.CONSOLE_FRAME_MESSAGE,      message: {        type: messages.CONSOLE_SHOW_INFO, diff --git a/src/content/focuses.ts b/src/content/focuses.ts index a6f6cc8..8f53881 100644 --- a/src/content/focuses.ts +++ b/src/content/focuses.ts @@ -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 inputSelector = inputTypes.map(type => `input[type=${type}]`).join(',');    let targets = window.document.querySelectorAll(inputSelector + ',textarea');    let target = Array.from(targets).find(doms.isVisible); -  if (target) { +  if (target instanceof HTMLInputElement) { +    target.focus(); +  } else if (target instanceof HTMLTextAreaElement) {      target.focus();    }  }; diff --git a/src/content/hint-key-producer.ts b/src/content/hint-key-producer.ts index 14b23b6..935394e 100644 --- a/src/content/hint-key-producer.ts +++ b/src/content/hint-key-producer.ts @@ -1,5 +1,9 @@  export default class HintKeyProducer { -  constructor(charset) { +  private charset: string; + +  private counter: number[]; + +  constructor(charset: string) {      if (charset.length === 0) {        throw new TypeError('charset is empty');      } @@ -8,13 +12,13 @@ export default class HintKeyProducer {      this.counter = [];    } -  produce() { +  produce(): string {      this.increment();      return this.counter.map(x => this.charset[x]).join('');    } -  increment() { +  private increment(): void {      let max = this.charset.length - 1;      if (this.counter.every(x => x === max)) {        this.counter = new Array(this.counter.length + 1).fill(0); diff --git a/src/content/index.ts b/src/content/index.ts index 9edb712..309f27f 100644 --- a/src/content/index.ts +++ b/src/content/index.ts @@ -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 FrameContentComponent from './components/frame-content';  import consoleFrameStyle from './site-style'; +import { newStore } from './store'; -const store = createStore( -  reducers, -  applyMiddleware(promise), -); +const store = newStore();  if (window.self === window.top) {    new TopContentComponent(window, store); // eslint-disable-line no-new diff --git a/src/content/navigates.ts b/src/content/navigates.ts index c9baa30..a2007a6 100644 --- a/src/content/navigates.ts +++ b/src/content/navigates.ts @@ -1,58 +1,63 @@ -const REL_PATTERN = { +const REL_PATTERN: {[key: string]: RegExp} = {    prev: /^(?:prev(?:ious)?|older)\b|\u2039|\u2190|\xab|\u226a|<</i,    next: /^(?:next|newer)\b|\u203a|\u2192|\xbb|\u226b|>>/i,  };  // Return the last element in the document matching the supplied selector  // and the optional filter, or null if there are no matches. -const selectLast = (win, selector, filter) => { -  let nodes = win.document.querySelectorAll(selector); +// eslint-disable-next-line func-style +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) { -    nodes = Array.from(nodes).filter(filter); +    nodes = nodes.filter(filter);    } -    return nodes.length ? nodes[nodes.length - 1] : null; -}; +} -const historyPrev = (win) => { +const historyPrev = (win: Window): void => {    win.history.back();  }; -const historyNext = (win) => { +const historyNext = (win: Window): void => {    win.history.forward();  };  // Code common to linkPrev and linkNext which navigates to the specified page. -const linkRel = (win, rel) => { -  let link = selectLast(win, `link[rel~=${rel}][href]`); - +const linkRel = (win: Window, rel: string): void => { +  let link = selectLast<HTMLLinkElement>(win, `link[rel~=${rel}][href]`);    if (link) { -    win.location = link.href; +    win.location.href = link.href;      return;    }    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      // (i.e. less unexpected) results      selectLast(win, 'a[href]', lnk => pattern.test(lnk.innerText)); -  if (link) { -    link.click(); +  if (a) { +    a.click();    }  }; -const linkPrev = (win) => { +const linkPrev = (win: Window): void => {    linkRel(win, 'prev');  }; -const linkNext = (win) => { +const linkNext = (win: Window): void => {    linkRel(win, 'next');  }; -const parent = (win) => { +const parent = (win: Window): void => {    const loc = win.location;    if (loc.hash !== '') {      loc.hash = ''; @@ -71,8 +76,8 @@ const parent = (win) => {    }  }; -const root = (win) => { -  win.location = win.location.origin; +const root = (win: Window): void => { +  win.location.href = win.location.origin;  };  export { historyPrev, historyNext, linkPrev, linkNext, parent, root }; diff --git a/src/content/reducers/addon.ts b/src/content/reducers/addon.ts index 0def55a..2131228 100644 --- a/src/content/reducers/addon.ts +++ b/src/content/reducers/addon.ts @@ -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,  }; -export default function reducer(state = defaultState, action = {}) { +export default function reducer( +  state: State = defaultState, +  action: actions.AddonAction, +): State {    switch (action.type) {    case actions.ADDON_SET_ENABLED:      return { ...state, diff --git a/src/content/reducers/find.ts b/src/content/reducers/find.ts index 4560e2c..8c3e637 100644 --- a/src/content/reducers/find.ts +++ b/src/content/reducers/find.ts @@ -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,    found: false,  }; -export default function reducer(state = defaultState, action = {}) { +export default function reducer( +  state: State = defaultState, +  action: actions.FindAction, +): State {    switch (action.type) {    case actions.FIND_SET_KEYWORD:      return { ...state, diff --git a/src/content/reducers/follow-controller.ts b/src/content/reducers/follow-controller.ts index 5869c47..6965704 100644 --- a/src/content/reducers/follow-controller.ts +++ b/src/content/reducers/follow-controller.ts @@ -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,    newTab: false,    background: false,    keys: '',  }; -export default function reducer(state = defaultState, action = {}) { +export default function reducer( +  state: State = defaultState, +  action: actions.FollowAction, +): State {    switch (action.type) {    case actions.FOLLOW_CONTROLLER_ENABLE:      return { ...state, diff --git a/src/content/reducers/index.ts b/src/content/reducers/index.ts index bf612a3..fb5eb84 100644 --- a/src/content/reducers/index.ts +++ b/src/content/reducers/index.ts @@ -1,10 +1,20 @@  import { combineReducers } from 'redux'; -import addon from './addon'; -import find from './find'; -import setting from './setting'; -import input from './input'; -import followController from './follow-controller'; -import mark from './mark'; +import addon, { State as AddonState } from './addon'; +import find, { State as FindState } from './find'; +import setting, { State as SettingState } from './setting'; +import input, { State as InputState } from './input'; +import followController, { State as FollowControllerState } +  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({    addon, find, setting, input, followController, mark, diff --git a/src/content/reducers/input.ts b/src/content/reducers/input.ts index 23e7dd2..6257e49 100644 --- a/src/content/reducers/input.ts +++ b/src/content/reducers/input.ts @@ -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: []  }; -export default function reducer(state = defaultState, action = {}) { +export default function reducer( +  state: State = defaultState, +  action: actions.InputAction, +): State {    switch (action.type) {    case actions.INPUT_KEY_PRESS:      return { ...state, diff --git a/src/content/reducers/mark.ts b/src/content/reducers/mark.ts index 2c96cc5..e78b7b9 100644 --- a/src/content/reducers/mark.ts +++ b/src/content/reducers/mark.ts @@ -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,    jumpMode: false,    marks: {},  }; -export default function reducer(state = defaultState, action = {}) { +export default function reducer( +  state: State = defaultState, +  action: actions.MarkAction, +): State {    switch (action.type) {    case actions.MARK_START_SET:      return { ...state, setMode: true }; diff --git a/src/content/reducers/setting.ts b/src/content/reducers/setting.ts index a49db6d..fa8e8ee 100644 --- a/src/content/reducers/setting.ts +++ b/src/content/reducers/setting.ts @@ -1,11 +1,18 @@ -import actions from 'content/actions'; +import * as actions from '../actions'; + +export interface State { +  keymaps: any[]; +}  const defaultState = {    // keymaps is and arrays of key-binding pairs, which is entries of Map    keymaps: [],  }; -export default function reducer(state = defaultState, action = {}) { +export default function reducer( +  state: State = defaultState, +  action: actions.SettingAction, +): State {    switch (action.type) {    case actions.SETTING_SET:      return { ...action.value }; diff --git a/src/content/scrolls.ts b/src/content/scrolls.ts index bbf2491..6a35315 100644 --- a/src/content/scrolls.ts +++ b/src/content/scrolls.ts @@ -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_Y = 64;  // dirty way to store scrolling state on globally  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);    return !(overflowX !== 'scroll' && overflowX !== 'auto' &&      overflowY !== 'scroll' && overflowY !== 'auto');  }; -const isOverflowed = (element) => { +const isOverflowed = (element: Element): boolean => {    return element.scrollWidth > element.clientWidth ||      element.scrollHeight > element.clientHeight;  }; @@ -22,7 +22,7 @@ const isOverflowed = (element) => {  // 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  // most pages, the window is root element i,e, documentElement. -const findScrollable = (element) => { +const findScrollable = (element: Element): Element | null => {    if (isScrollableStyle(element) && isOverflowed(element)) {      return element;    } @@ -56,12 +56,16 @@ const resetScrolling = () => {  };  class Scroller { -  constructor(element, smooth) { +  private element: Element; + +  private smooth: boolean; + +  constructor(element: Element, smooth: boolean) {      this.element = element;      this.smooth = smooth;    } -  scrollTo(x, y) { +  scrollTo(x: number, y: number): void {      if (!this.smooth) {        this.element.scrollTo(x, y);        return; @@ -74,13 +78,13 @@ class Scroller {      this.prepareReset();    } -  scrollBy(x, y) { +  scrollBy(x: number, y: number): void {      let left = this.element.scrollLeft + x;      let top = this.element.scrollTop + y;      this.scrollTo(left, top);    } -  prepareReset() { +  prepareReset(): void {      scrolling = true;      if (lastTimeoutId) {        clearTimeout(lastTimeoutId); @@ -95,7 +99,7 @@ const getScroll = () => {    return { x: target.scrollLeft, y: target.scrollTop };  }; -const scrollVertically = (count, smooth) => { +const scrollVertically = (count: number, smooth: boolean): void => {    let target = scrollTarget();    let delta = SCROLL_DELTA_Y * count;    if (scrolling) { @@ -104,7 +108,7 @@ const scrollVertically = (count, smooth) => {    new Scroller(target, smooth).scrollBy(0, delta);  }; -const scrollHorizonally = (count, smooth) => { +const scrollHorizonally = (count: number, smooth: boolean): void => {    let target = scrollTarget();    let delta = SCROLL_DELTA_X * count;    if (scrolling) { @@ -113,7 +117,7 @@ const scrollHorizonally = (count, smooth) => {    new Scroller(target, smooth).scrollBy(delta, 0);  }; -const scrollPages = (count, smooth) => { +const scrollPages = (count: number, smooth: boolean): void => {    let target = scrollTarget();    let height = target.clientHeight;    let delta = height * count; @@ -123,33 +127,33 @@ const scrollPages = (count, smooth) => {    new Scroller(target, smooth).scrollBy(0, delta);  }; -const scrollTo = (x, y, smooth) => { +const scrollTo = (x: number, y: number, smooth: boolean): void => {    let target = scrollTarget();    new Scroller(target, smooth).scrollTo(x, y);  }; -const scrollToTop = (smooth) => { +const scrollToTop = (smooth: boolean): void => {    let target = scrollTarget();    let x = target.scrollLeft;    let y = 0;    new Scroller(target, smooth).scrollTo(x, y);  }; -const scrollToBottom = (smooth) => { +const scrollToBottom = (smooth: boolean): void => {    let target = scrollTarget();    let x = target.scrollLeft;    let y = target.scrollHeight;    new Scroller(target, smooth).scrollTo(x, y);  }; -const scrollToHome = (smooth) => { +const scrollToHome = (smooth: boolean): void => {    let target = scrollTarget();    let x = 0;    let y = target.scrollTop;    new Scroller(target, smooth).scrollTo(x, y);  }; -const scrollToEnd = (smooth) => { +const scrollToEnd = (smooth: boolean): void => {    let target = scrollTarget();    let x = target.scrollWidth;    let y = target.scrollTop; diff --git a/src/content/store/index.ts b/src/content/store/index.ts new file mode 100644 index 0000000..5c41744 --- /dev/null +++ b/src/content/store/index.ts @@ -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), +); diff --git a/src/content/urls.ts b/src/content/urls.ts index 6e7ea31..390efde 100644 --- a/src/content/urls.ts +++ b/src/content/urls.ts @@ -1,7 +1,7 @@ -import messages from 'shared/messages'; +import * as messages from '../shared/messages';  import * as urls from '../shared/urls'; -const yank = (win) => { +const yank = (win: Window) => {    let input = win.document.createElement('input');    win.document.body.append(input); @@ -15,7 +15,7 @@ const yank = (win) => {    input.remove();  }; -const paste = (win, newTab, searchSettings) => { +const paste = (win: Window, newTab: boolean, searchSettings: any) => {    let textarea = win.document.createElement('textarea');    win.document.body.append(textarea); @@ -25,7 +25,7 @@ const paste = (win, newTab, searchSettings) => {    textarea.focus();    if (win.document.execCommand('paste')) { -    let value = textarea.textContent; +    let value = textarea.textContent as string;      let url = urls.searchUrl(value, searchSettings);      browser.runtime.sendMessage({        type: messages.OPEN_URL, diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 2bc12d8..41b0f0b 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -1,78 +1,276 @@ -type WebMessageSender = Window | MessagePort | ServiceWorker | null; -type WebMessageListener = (msg: any, sender: WebMessageSender | null) => void; - -const onWebMessage = (listener: WebMessageListener) => { -  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); -  }); -}; +import * as operations from './operations'; -const onBackgroundMessage = ( -  listener: (msg: any, sender: browser.runtime.MessageSender, -) => void) => { -  browser.runtime.onMessage.addListener(listener); -}; +export const BACKGROUND_OPERATION = 'background.operation'; -const onMessage = ( -  listener: (msg: any, sender: WebMessageSender | browser.runtime.MessageSender, -) => void) => { -  onWebMessage(listener); -  onBackgroundMessage(listener); -}; +export const CONSOLE_UNFOCUS = 'console.unfocus'; +export const CONSOLE_ENTER_COMMAND = 'console.enter.command'; +export const CONSOLE_ENTER_FIND = 'console.enter.find'; +export const CONSOLE_QUERY_COMPLETIONS = 'console.query.completions'; +export const CONSOLE_SHOW_COMMAND = 'console.show.command'; +export const CONSOLE_SHOW_ERROR = 'console.show.error'; +export const CONSOLE_SHOW_INFO = 'console.show.info'; +export const CONSOLE_SHOW_FIND = 'console.show.find'; +export const CONSOLE_HIDE = 'console.hide'; + +export const FOLLOW_START = 'follow.start'; +export const FOLLOW_REQUEST_COUNT_TARGETS = 'follow.request.count.targets'; +export const FOLLOW_RESPONSE_COUNT_TARGETS = 'follow.response.count.targets'; +export const FOLLOW_CREATE_HINTS = 'follow.create.hints'; +export const FOLLOW_SHOW_HINTS = 'follow.update.hints'; +export const FOLLOW_REMOVE_HINTS = 'follow.remove.hints'; +export const FOLLOW_ACTIVATE = 'follow.activate'; +export const FOLLOW_KEY_PRESS = 'follow.key.press'; + +export const MARK_SET_GLOBAL = 'mark.set.global'; +export const MARK_JUMP_GLOBAL = 'mark.jump.global'; + +export const TAB_SCROLL_TO = 'tab.scroll.to'; + +export const FIND_NEXT = 'find.next'; +export const FIND_PREV = 'find.prev'; +export const FIND_GET_KEYWORD = 'find.get.keyword'; +export const FIND_SET_KEYWORD = 'find.set.keyword'; + +export const ADDON_ENABLED_QUERY = 'addon.enabled.query'; +export const ADDON_ENABLED_RESPONSE = 'addon.enabled.response'; +export const ADDON_TOGGLE_ENABLED = 'addon.toggle.enabled'; + +export const OPEN_URL = 'open.url'; + +export const SETTINGS_CHANGED = 'settings.changed'; +export const SETTINGS_QUERY = 'settings.query'; + +export const CONSOLE_FRAME_MESSAGE = 'console.frame.message'; + +interface BackgroundOperationMessage { +  type: typeof BACKGROUND_OPERATION; +  operation: operations.Operation; +} + +interface ConsoleUnfocusMessage { +  type: typeof CONSOLE_UNFOCUS; +} + +interface ConsoleEnterCommandMessage { +  type: typeof CONSOLE_ENTER_COMMAND; +  text: string; +} + +interface ConsoleEnterFindMessage { +  type: typeof CONSOLE_ENTER_FIND; +  text: string; +} + +interface ConsoleQueryCompletionsMessage { +  type: typeof CONSOLE_QUERY_COMPLETIONS; +  text: string; +} + +interface ConsoleShowCommandMessage { +  type: typeof CONSOLE_SHOW_COMMAND; +  command: string; +} + +interface ConsoleShowErrorMessage { +  type: typeof CONSOLE_SHOW_ERROR; +  text: string; +} + +interface ConsoleShowInfoMessage { +  type: typeof CONSOLE_SHOW_INFO; +  text: string; +} + +interface ConsoleShowFindMessage { +  type: typeof CONSOLE_SHOW_FIND; +} + +interface ConsoleHideMessage { +  type: typeof CONSOLE_HIDE; +} + +interface FollowStartMessage { +  type: typeof FOLLOW_START; +  newTab: boolean; +  background: boolean; +} + +interface FollowRequestCountTargetsMessage { +  type: typeof FOLLOW_REQUEST_COUNT_TARGETS; +  viewSize: { width: number, height: number }; +  framePosition: { x: number, y: number }; +} + +interface FollowResponseCountTargetsMessage { +  type: typeof FOLLOW_RESPONSE_COUNT_TARGETS; +  count: number; +} + +interface FollowCreateHintsMessage { +  type: typeof FOLLOW_CREATE_HINTS; +  keysArray: string[]; +  newTab: boolean; +  background: boolean; +} + +interface FollowShowHintsMessage { +  type: typeof FOLLOW_SHOW_HINTS; +  keys: string; +} + +interface FollowRemoveHintsMessage { +  type: typeof FOLLOW_REMOVE_HINTS; +} + +interface FollowActivateMessage { +  type: typeof FOLLOW_ACTIVATE; +  keys: string; +} + +interface FollowKeyPressMessage { +  type: typeof FOLLOW_KEY_PRESS; +  key: string; +  ctrlKey: boolean; +} + +interface MarkSetGlobalMessage { +  type: typeof MARK_SET_GLOBAL; +  key: string; +  x: number; +  y: number; +} + +interface MarkJumpGlobalMessage { +  type: typeof MARK_JUMP_GLOBAL; +  key: string; +} + +interface TabScrollToMessage { +  type: typeof TAB_SCROLL_TO; +  x: number; +  y: number; +} + +interface FindNextMessage { +  type: typeof FIND_NEXT; +} + +interface FindPrevMessage { +  type: typeof FIND_PREV; +} + +interface FindGetKeywordMessage { +  type: typeof FIND_GET_KEYWORD; +} + +interface FindSetKeywordMessage { +  type: typeof FIND_SET_KEYWORD; +  keyword: string; +  found: boolean; +} + +interface AddonEnabledQueryMessage { +  type: typeof ADDON_ENABLED_QUERY; +} + +interface AddonEnabledResponseMessage { +  type: typeof ADDON_ENABLED_RESPONSE; +  enabled: boolean; +} + +interface AddonToggleEnabledMessage { +  type: typeof ADDON_TOGGLE_ENABLED; +} + +interface OpenUrlMessage { +  type: typeof OPEN_URL; +  url: string; +  newTab: boolean; +  background: boolean; +} + +interface SettingsChangedMessage { +  type: typeof SETTINGS_CHANGED; +} + +interface SettingsQueryMessage { +  type: typeof SETTINGS_QUERY; +} + +interface ConsoleFrameMessageMessage { +  type: typeof CONSOLE_FRAME_MESSAGE; +  message: any; +} + +export type Message = +  BackgroundOperationMessage | +  ConsoleUnfocusMessage | +  ConsoleEnterCommandMessage | +  ConsoleEnterFindMessage | +  ConsoleQueryCompletionsMessage | +  ConsoleShowCommandMessage | +  ConsoleShowErrorMessage | +  ConsoleShowInfoMessage | +  ConsoleShowFindMessage | +  ConsoleHideMessage | +  FollowStartMessage | +  FollowRequestCountTargetsMessage | +  FollowResponseCountTargetsMessage | +  FollowCreateHintsMessage | +  FollowShowHintsMessage | +  FollowRemoveHintsMessage | +  FollowActivateMessage | +  FollowKeyPressMessage | +  MarkSetGlobalMessage | +  MarkJumpGlobalMessage | +  TabScrollToMessage | +  FindNextMessage | +  FindPrevMessage | +  FindGetKeywordMessage | +  FindSetKeywordMessage | +  AddonEnabledQueryMessage | +  AddonEnabledResponseMessage | +  AddonToggleEnabledMessage | +  OpenUrlMessage | +  SettingsChangedMessage | +  SettingsQueryMessage | +  ConsoleFrameMessageMessage; -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, +// eslint-disable-next-line complexity +export const valueOf = (o: any): Message => { +  switch (o.type) { +  case CONSOLE_UNFOCUS: +  case CONSOLE_ENTER_COMMAND: +  case CONSOLE_ENTER_FIND: +  case CONSOLE_QUERY_COMPLETIONS: +  case CONSOLE_SHOW_COMMAND: +  case CONSOLE_SHOW_ERROR: +  case CONSOLE_SHOW_INFO: +  case CONSOLE_SHOW_FIND: +  case CONSOLE_HIDE: +  case FOLLOW_START: +  case FOLLOW_REQUEST_COUNT_TARGETS: +  case FOLLOW_RESPONSE_COUNT_TARGETS: +  case FOLLOW_CREATE_HINTS: +  case FOLLOW_SHOW_HINTS: +  case FOLLOW_REMOVE_HINTS: +  case FOLLOW_ACTIVATE: +  case FOLLOW_KEY_PRESS: +  case MARK_SET_GLOBAL: +  case MARK_JUMP_GLOBAL: +  case TAB_SCROLL_TO: +  case FIND_NEXT: +  case FIND_PREV: +  case FIND_GET_KEYWORD: +  case FIND_SET_KEYWORD: +  case ADDON_ENABLED_QUERY: +  case ADDON_ENABLED_RESPONSE: +  case ADDON_TOGGLE_ENABLED: +  case OPEN_URL: +  case SETTINGS_CHANGED: +  case SETTINGS_QUERY: +  case CONSOLE_FRAME_MESSAGE: +    return o; +  } +  throw new Error('unknown operation type: ' + o.type);  }; diff --git a/src/shared/operations.ts b/src/shared/operations.ts index d59723e..cc22f75 100644 --- a/src/shared/operations.ts +++ b/src/shared/operations.ts @@ -1,80 +1,447 @@ -const operations: { [key: string]: string } = { -  // Hide console, or cancel some user actions -  CANCEL: 'cancel', - -  // Addons -  ADDON_ENABLE: 'addon.enable', -  ADDON_DISABLE: 'addon.disable', -  ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled', - -  // Command -  COMMAND_SHOW: 'command.show', -  COMMAND_SHOW_OPEN: 'command.show.open', -  COMMAND_SHOW_TABOPEN: 'command.show.tabopen', -  COMMAND_SHOW_WINOPEN: 'command.show.winopen', -  COMMAND_SHOW_BUFFER: 'command.show.buffer', -  COMMAND_SHOW_ADDBOOKMARK: 'command.show.addbookmark', - -  // Scrolls -  SCROLL_VERTICALLY: 'scroll.vertically', -  SCROLL_HORIZONALLY: 'scroll.horizonally', -  SCROLL_PAGES: 'scroll.pages', -  SCROLL_TOP: 'scroll.top', -  SCROLL_BOTTOM: 'scroll.bottom', -  SCROLL_HOME: 'scroll.home', -  SCROLL_END: 'scroll.end', - -  // Follows -  FOLLOW_START: 'follow.start', - -  // Navigations -  NAVIGATE_HISTORY_PREV: 'navigate.history.prev', -  NAVIGATE_HISTORY_NEXT: 'navigate.history.next', -  NAVIGATE_LINK_PREV: 'navigate.link.prev', -  NAVIGATE_LINK_NEXT: 'navigate.link.next', -  NAVIGATE_PARENT: 'navigate.parent', -  NAVIGATE_ROOT: 'navigate.root', - -  // Focus -  FOCUS_INPUT: 'focus.input', - -  // Page -  PAGE_SOURCE: 'page.source', -  PAGE_HOME: 'page.home', - -  // Tabs -  TAB_CLOSE: 'tabs.close', -  TAB_CLOSE_FORCE: 'tabs.close.force', -  TAB_CLOSE_RIGHT: 'tabs.close.right', -  TAB_REOPEN: 'tabs.reopen', -  TAB_PREV: 'tabs.prev', -  TAB_NEXT: 'tabs.next', -  TAB_FIRST: 'tabs.first', -  TAB_LAST: 'tabs.last', -  TAB_PREV_SEL: 'tabs.prevsel', -  TAB_RELOAD: 'tabs.reload', -  TAB_PIN: 'tabs.pin', -  TAB_UNPIN: 'tabs.unpin', -  TAB_TOGGLE_PINNED: 'tabs.pin.toggle', -  TAB_DUPLICATE: 'tabs.duplicate', - -  // Zooms -  ZOOM_IN: 'zoom.in', -  ZOOM_OUT: 'zoom.out', -  ZOOM_NEUTRAL: 'zoom.neutral', - -  // Url yank/paste -  URLS_YANK: 'urls.yank', -  URLS_PASTE: 'urls.paste', - -  // Find -  FIND_START: 'find.start', -  FIND_NEXT: 'find.next', -  FIND_PREV: 'find.prev', - -  // Mark -  MARK_SET_PREFIX: 'mark.set.prefix', -  MARK_JUMP_PREFIX: 'mark.jump.prefix', +// Hide console; or cancel some user actions +export const CANCEL = 'cancel'; + +// Addons +export const ADDON_ENABLE = 'addon.enable'; +export const ADDON_DISABLE = 'addon.disable'; +export const ADDON_TOGGLE_ENABLED = 'addon.toggle.enabled'; + +// Command +export const COMMAND_SHOW = 'command.show'; +export const COMMAND_SHOW_OPEN = 'command.show.open'; +export const COMMAND_SHOW_TABOPEN = 'command.show.tabopen'; +export const COMMAND_SHOW_WINOPEN = 'command.show.winopen'; +export const COMMAND_SHOW_BUFFER = 'command.show.buffer'; +export const COMMAND_SHOW_ADDBOOKMARK = 'command.show.addbookmark'; + +// Scrolls +export const SCROLL_VERTICALLY = 'scroll.vertically'; +export const SCROLL_HORIZONALLY = 'scroll.horizonally'; +export const SCROLL_PAGES = 'scroll.pages'; +export const SCROLL_TOP = 'scroll.top'; +export const SCROLL_BOTTOM = 'scroll.bottom'; +export const SCROLL_HOME = 'scroll.home'; +export const SCROLL_END = 'scroll.end'; + +// Follows +export const FOLLOW_START = 'follow.start'; + +// Navigations +export const NAVIGATE_HISTORY_PREV = 'navigate.history.prev'; +export const NAVIGATE_HISTORY_NEXT = 'navigate.history.next'; +export const NAVIGATE_LINK_PREV = 'navigate.link.prev'; +export const NAVIGATE_LINK_NEXT = 'navigate.link.next'; +export const NAVIGATE_PARENT = 'navigate.parent'; +export const NAVIGATE_ROOT = 'navigate.root'; + +// Focus +export const FOCUS_INPUT = 'focus.input'; + +// Page +export const PAGE_SOURCE = 'page.source'; +export const PAGE_HOME = 'page.home'; + +// Tabs +export const TAB_CLOSE = 'tabs.close'; +export const TAB_CLOSE_FORCE = 'tabs.close.force'; +export const TAB_CLOSE_RIGHT = 'tabs.close.right'; +export const TAB_REOPEN = 'tabs.reopen'; +export const TAB_PREV = 'tabs.prev'; +export const TAB_NEXT = 'tabs.next'; +export const TAB_FIRST = 'tabs.first'; +export const TAB_LAST = 'tabs.last'; +export const TAB_PREV_SEL = 'tabs.prevsel'; +export const TAB_RELOAD = 'tabs.reload'; +export const TAB_PIN = 'tabs.pin'; +export const TAB_UNPIN = 'tabs.unpin'; +export const TAB_TOGGLE_PINNED = 'tabs.pin.toggle'; +export const TAB_DUPLICATE = 'tabs.duplicate'; + +// Zooms +export const ZOOM_IN = 'zoom.in'; +export const ZOOM_OUT = 'zoom.out'; +export const ZOOM_NEUTRAL = 'zoom.neutral'; + +// Url yank/paste +export const URLS_YANK = 'urls.yank'; +export const URLS_PASTE = 'urls.paste'; + +// Find +export const FIND_START = 'find.start'; +export const FIND_NEXT = 'find.next'; +export const FIND_PREV = 'find.prev'; + +// Mark +export const MARK_SET_PREFIX = 'mark.set.prefix'; +export const MARK_JUMP_PREFIX = 'mark.jump.prefix'; + +export interface CancelOperation { +  type: typeof CANCEL; +} + +export interface AddonEnableOperation { +  type: typeof ADDON_ENABLE; +} + +export interface AddonDisableOperation { +  type: typeof ADDON_DISABLE; +} + +export interface AddonToggleEnabledOperation { +  type: typeof ADDON_TOGGLE_ENABLED; +} + +export interface CommandShowOperation { +  type: typeof COMMAND_SHOW; +} + +export interface CommandShowOpenOperation { +  type: typeof COMMAND_SHOW_OPEN; +  alter: boolean; +} + +export interface CommandShowTabopenOperation { +  type: typeof COMMAND_SHOW_TABOPEN; +  alter: boolean; +} + +export interface CommandShowWinopenOperation { +  type: typeof COMMAND_SHOW_WINOPEN; +  alter: boolean; +} + +export interface CommandShowBufferOperation { +  type: typeof COMMAND_SHOW_BUFFER; +} + +export interface CommandShowAddbookmarkOperation { +  type: typeof COMMAND_SHOW_ADDBOOKMARK; +  alter: boolean; +} + +export interface ScrollVerticallyOperation { +  type: typeof SCROLL_VERTICALLY; +  count: number; +} + +export interface ScrollHorizonallyOperation { +  type: typeof SCROLL_HORIZONALLY; +  count: number; +} + +export interface ScrollPagesOperation { +  type: typeof SCROLL_PAGES; +  count: number; +} + +export interface ScrollTopOperation { +  type: typeof SCROLL_TOP; +} + +export interface ScrollBottomOperation { +  type: typeof SCROLL_BOTTOM; +} + +export interface ScrollHomeOperation { +  type: typeof SCROLL_HOME; +} + +export interface ScrollEndOperation { +  type: typeof SCROLL_END; +} + +export interface FollowStartOperation { +  type: typeof FOLLOW_START; +  newTab: boolean; +  background: boolean; +} + +export interface NavigateHistoryPrevOperation { +  type: typeof NAVIGATE_HISTORY_PREV; +} + +export interface NavigateHistoryNextOperation { +  type: typeof NAVIGATE_HISTORY_NEXT; +} + +export interface NavigateLinkPrevOperation { +  type: typeof NAVIGATE_LINK_PREV; +} + +export interface NavigateLinkNextOperation { +  type: typeof NAVIGATE_LINK_NEXT; +} + +export interface NavigateParentOperation { +  type: typeof NAVIGATE_PARENT; +} + +export interface NavigateRootOperation { +  type: typeof NAVIGATE_ROOT; +} + +export interface FocusInputOperation { +  type: typeof FOCUS_INPUT; +} + +export interface PageSourceOperation { +  type: typeof PAGE_SOURCE; +} + +export interface PageHomeOperation { +  type: typeof PAGE_HOME; +  newTab: boolean; +} + +export interface TabCloseOperation { +  type: typeof TAB_CLOSE; +} + +export interface TabCloseForceOperation { +  type: typeof TAB_CLOSE_FORCE; +} + +export interface TabCloseRightOperation { +  type: typeof TAB_CLOSE_RIGHT; +} + +export interface TabReopenOperation { +  type: typeof TAB_REOPEN; +} + +export interface TabPrevOperation { +  type: typeof TAB_PREV; +} + +export interface TabNextOperation { +  type: typeof TAB_NEXT; +} + +export interface TabFirstOperation { +  type: typeof TAB_FIRST; +} + +export interface TabLastOperation { +  type: typeof TAB_LAST; +} + +export interface TabPrevSelOperation { +  type: typeof TAB_PREV_SEL; +} + +export interface TabReloadOperation { +  type: typeof TAB_RELOAD; +  cache: boolean; +} + +export interface TabPinOperation { +  type: typeof TAB_PIN; +} + +export interface TabUnpinOperation { +  type: typeof TAB_UNPIN; +} + +export interface TabTogglePinnedOperation { +  type: typeof TAB_TOGGLE_PINNED; +} + +export interface TabDuplicateOperation { +  type: typeof TAB_DUPLICATE; +} + +export interface ZoomInOperation { +  type: typeof ZOOM_IN; +} + +export interface ZoomOutOperation { +  type: typeof ZOOM_OUT; +} + +export interface ZoomNeutralOperation { +  type: typeof ZOOM_NEUTRAL; +} + +export interface UrlsYankOperation { +  type: typeof URLS_YANK; +} + +export interface UrlsPasteOperation { +  type: typeof URLS_PASTE; +  newTab: boolean; +} + +export interface FindStartOperation { +  type: typeof FIND_START; +} + +export interface FindNextOperation { +  type: typeof FIND_NEXT; +} + +export interface FindPrevOperation { +  type: typeof FIND_PREV; +} + +export interface MarkSetPrefixOperation { +  type: typeof MARK_SET_PREFIX; +} + +export interface MarkJumpPrefixOperation { +  type: typeof MARK_JUMP_PREFIX; +} + +export type Operation = +  CancelOperation | +  AddonEnableOperation | +  AddonDisableOperation | +  AddonToggleEnabledOperation | +  CommandShowOperation | +  CommandShowOpenOperation | +  CommandShowTabopenOperation | +  CommandShowWinopenOperation | +  CommandShowBufferOperation | +  CommandShowAddbookmarkOperation | +  ScrollVerticallyOperation | +  ScrollHorizonallyOperation | +  ScrollPagesOperation | +  ScrollTopOperation | +  ScrollBottomOperation | +  ScrollHomeOperation | +  ScrollEndOperation | +  FollowStartOperation | +  NavigateHistoryPrevOperation | +  NavigateHistoryNextOperation | +  NavigateLinkPrevOperation | +  NavigateLinkNextOperation | +  NavigateParentOperation | +  NavigateRootOperation | +  FocusInputOperation | +  PageSourceOperation | +  PageHomeOperation | +  TabCloseOperation | +  TabCloseForceOperation | +  TabCloseRightOperation | +  TabReopenOperation | +  TabPrevOperation | +  TabNextOperation | +  TabFirstOperation | +  TabLastOperation | +  TabPrevSelOperation | +  TabReloadOperation | +  TabPinOperation | +  TabUnpinOperation | +  TabTogglePinnedOperation | +  TabDuplicateOperation | +  ZoomInOperation | +  ZoomOutOperation | +  ZoomNeutralOperation | +  UrlsYankOperation | +  UrlsPasteOperation | +  FindStartOperation | +  FindNextOperation | +  FindPrevOperation | +  MarkSetPrefixOperation | +  MarkJumpPrefixOperation; + +const assertOptionalBoolean = (obj: any, name: string) => { +  if (Object.prototype.hasOwnProperty.call(obj, name) && +      typeof obj[name] !== 'boolean') { +    throw new TypeError(`Not a boolean parameter '${name}'`); +  } +}; + +const assertRequiredNumber = (obj: any, name: string) => { +  if (!Object.prototype.hasOwnProperty.call(obj, name) || +    typeof obj[name] !== 'number') { +    throw new TypeError(`Missing number parameter '${name}`); +  }  }; -export default operations; +// 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); +}; diff --git a/src/shared/settings/validator.ts b/src/shared/settings/validator.ts index 0483931..71cc466 100644 --- a/src/shared/settings/validator.ts +++ b/src/shared/settings/validator.ts @@ -1,4 +1,4 @@ -import operations from '../operations'; +import * as operations from '../operations';  import * as properties from './properties';  const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties']; diff --git a/src/shared/utils/keys.ts b/src/shared/utils/keys.ts index d9abef7..e9b0365 100644 --- a/src/shared/utils/keys.ts +++ b/src/shared/utils/keys.ts @@ -1,4 +1,4 @@ -interface Key { +export interface Key {      key: string;      shiftKey: boolean | undefined;      ctrlKey: boolean | undefined;  | 
