diff options
Diffstat (limited to 'src/content/components')
| -rw-r--r-- | src/content/components/common/follow.ts (renamed from src/content/components/common/follow.js) | 79 | ||||
| -rw-r--r-- | src/content/components/common/hint.ts (renamed from src/content/components/common/hint.js) | 33 | ||||
| -rw-r--r-- | src/content/components/common/index.js | 55 | ||||
| -rw-r--r-- | src/content/components/common/index.ts | 61 | ||||
| -rw-r--r-- | src/content/components/common/input.ts (renamed from src/content/components/common/input.js) | 47 | ||||
| -rw-r--r-- | src/content/components/common/keymapper.ts (renamed from src/content/components/common/keymapper.js) | 36 | ||||
| -rw-r--r-- | src/content/components/common/mark.js | 74 | ||||
| -rw-r--r-- | src/content/components/common/mark.ts | 79 | ||||
| -rw-r--r-- | src/content/components/frame-content.ts (renamed from src/content/components/frame-content.js) | 0 | ||||
| -rw-r--r-- | src/content/components/top-content/find.js | 41 | ||||
| -rw-r--r-- | src/content/components/top-content/find.ts | 46 | ||||
| -rw-r--r-- | src/content/components/top-content/follow-controller.ts (renamed from src/content/components/top-content/follow-controller.js) | 67 | ||||
| -rw-r--r-- | src/content/components/top-content/index.ts (renamed from src/content/components/top-content/index.js) | 28 | 
13 files changed, 375 insertions, 271 deletions
| diff --git a/src/content/components/common/follow.js b/src/content/components/common/follow.ts index 63ce603..67f2dd9 100644 --- a/src/content/components/common/follow.js +++ 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.js b/src/content/components/common/hint.ts index 1472587..2fcbb0f 100644 --- a/src/content/components/common/hint.js +++ 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.js b/src/content/components/common/index.js deleted file mode 100644 index bcab4fa..0000000 --- a/src/content/components/common/index.js +++ /dev/null @@ -1,55 +0,0 @@ -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 addonActions from '../../actions/addon'; -import * as blacklists from 'shared/blacklists'; - -export default class Common { -  constructor(win, store) { -    const input = new InputComponent(win.document.body, store); -    const follow = new FollowComponent(win, store); -    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)); - -    this.win = win; -    this.store = store; -    this.prevEnabled = undefined; -    this.prevBlacklist = undefined; - -    this.reloadSettings(); - -    messages.onMessage(this.onMessage.bind(this)); -  } - -  onMessage(message) { -    let { enabled } = this.store.getState().addon; -    switch (message.type) { -    case messages.SETTINGS_CHANGED: -      return this.reloadSettings(); -    case messages.ADDON_TOGGLE_ENABLED: -      this.store.dispatch(addonActions.setEnabled(!enabled)); -    } -  } - -  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)); -      }); -    } catch (e) { -      // Sometime sendMessage fails when background script is not ready. -      console.warn(e); -      setTimeout(() => this.reloadSettings(), 500); -    } -  } -} diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts new file mode 100644 index 0000000..5b097b6 --- /dev/null +++ b/src/content/components/common/index.ts @@ -0,0 +1,61 @@ +import InputComponent from './input'; +import FollowComponent from './follow'; +import MarkComponent from './mark'; +import KeymapperComponent from './keymapper'; +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 keys from '../../../shared/utils/keys'; +import * as actions from '../../actions'; + +export default class Common { +  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(store); +    const keymapper = new KeymapperComponent(store); + +    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.reloadSettings(); + +    new MessageListener().onBackgroundMessage(this.onMessage.bind(this)); +  } + +  onMessage(message: messages.Message) { +    let { enabled } = this.store.getState().addon; +    switch (message.type) { +    case messages.SETTINGS_CHANGED: +      return this.reloadSettings(); +    case messages.ADDON_TOGGLE_ENABLED: +      this.store.dispatch(addonActions.setEnabled(!enabled)); +    } +  } + +  reloadSettings() { +    try { +      this.store.dispatch(settingActions.load()) +        .then((action: actions.SettingAction) => { +          let enabled = !blacklists.includes( +            action.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); +      setTimeout(() => this.reloadSettings(), 500); +    } +  } +} diff --git a/src/content/components/common/input.js b/src/content/components/common/input.ts index eefaf10..1fe34c9 100644 --- a/src/content/components/common/input.js +++ 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;      } @@ -52,7 +61,6 @@ export default class InputComponent {      }      let key = keys.fromKeyboardEvent(e); -      for (let listener of this.onKeyListeners) {        let stop = listener(key);        if (stop) { @@ -63,13 +71,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.js b/src/content/components/common/keymapper.ts index ec0d093..c94bae0 100644 --- a/src/content/components/common/keymapper.js +++ b/src/content/components/common/keymapper.ts @@ -1,9 +1,12 @@ -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) => { +const mapStartsWith = ( +  mapping: keyUtils.Key[], +  keys: keyUtils.Key[], +): boolean => {    if (mapping.length < keys.length) {      return false;    } @@ -16,26 +19,33 @@ const mapStartsWith = (mapping, keys) => {  };  export default class KeymapperComponent { -  constructor(store) { +  private store: any; + +  constructor(store: any) {      this.store = store;    }    // eslint-disable-next-line max-statements -  key(key) { +  key(key: keyUtils.Key): boolean {      this.store.dispatch(inputActions.keyPress(key));      let state = this.store.getState();      let input = state.input; -    let keymaps = new Map(state.setting.keymaps); +    let keymaps = new Map<keyUtils.Key[], operations.Operation>( +      state.setting.keymaps.map( +        (e: {key: keyUtils.Key[], op: operations.Operation}) => [e.key, e.op], +      ) +    ); -    let matched = Array.from(keymaps.keys()).filter((mapping) => { -      return mapStartsWith(mapping, input.keys); -    }); +    let matched = Array.from(keymaps.keys()).filter( +      (mapping: keyUtils.Key[]) => { +        return mapStartsWith(mapping, input.keys); +      });      if (!state.addon.enabled) {        // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if        // the addon disabled        matched = matched.filter((keys) => { -        let type = keymaps.get(keys).type; +        let type = (keymaps.get(keys) as operations.Operation).type;          return type === operations.ADDON_ENABLE ||            type === operations.ADDON_TOGGLE_ENABLED;        }); @@ -47,7 +57,7 @@ export default class KeymapperComponent {        matched.length === 1 && input.keys.length < matched[0].length) {        return true;      } -    let operation = keymaps.get(matched[0]); +    let operation = keymaps.get(matched[0]) as operations.Operation;      let act = operationActions.exec(        operation, state.setting, state.addon.enabled      ); diff --git a/src/content/components/common/mark.js b/src/content/components/common/mark.js deleted file mode 100644 index 0f838a9..0000000 --- a/src/content/components/common/mark.js +++ /dev/null @@ -1,74 +0,0 @@ -import * as markActions from 'content/actions/mark'; -import * as scrolls from 'content/scrolls'; -import * as consoleFrames from 'content/console-frames'; -import * as properties from 'shared/settings/properties'; - -const cancelKey = (key) => { -  return key.key === 'Esc' || key.key === '[' && key.ctrlKey; -}; - -const globalKey = (key) => { -  return (/^[A-Z0-9]$/).test(key); -}; - -export default class MarkComponent { -  constructor(body, store) { -    this.body = body; -    this.store = store; -  } - -  // eslint-disable-next-line max-statements -  key(key) { -    let { mark: markStage, setting } = this.store.getState(); -    let smoothscroll = setting.properties.smoothscroll || -      properties.defaults.smoothscroll; - -    if (!markStage.setMode && !markStage.jumpMode) { -      return false; -    } - -    if (cancelKey(key)) { -      this.store.dispatch(markActions.cancel()); -      return true; -    } - -    if (key.ctrlKey || key.metaKey || key.altKey) { -      consoleFrames.postError('Unknown mark'); -    } else if (globalKey(key.key) && markStage.setMode) { -      this.doSetGlobal(key); -    } else if (globalKey(key.key) && markStage.jumpMode) { -      this.doJumpGlobal(key); -    } else if (markStage.setMode) { -      this.doSet(key); -    } else if (markStage.jumpMode) { -      this.doJump(markStage.marks, key, smoothscroll); -    } - -    this.store.dispatch(markActions.cancel()); -    return true; -  } - -  doSet(key) { -    let { x, y } = scrolls.getScroll(); -    this.store.dispatch(markActions.setLocal(key.key, x, y)); -  } - -  doJump(marks, key, smoothscroll) { -    if (!marks[key.key]) { -      consoleFrames.postError('Mark is not set'); -      return; -    } - -    let { x, y } = marks[key.key]; -    scrolls.scrollTo(x, y, smoothscroll); -  } - -  doSetGlobal(key) { -    let { x, y } = scrolls.getScroll(); -    this.store.dispatch(markActions.setGlobal(key.key, x, y)); -  } - -  doJumpGlobal(key) { -    this.store.dispatch(markActions.jumpGlobal(key.key)); -  } -} diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts new file mode 100644 index 0000000..1237385 --- /dev/null +++ b/src/content/components/common/mark.ts @@ -0,0 +1,79 @@ +import * as markActions from '../../actions/mark'; +import * as scrolls from '../..//scrolls'; +import * as consoleFrames from '../..//console-frames'; +import * as keyUtils from '../../../shared/utils/keys'; +import Mark from '../../Mark'; + +const cancelKey = (key: keyUtils.Key): boolean => { +  return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey); +}; + +const globalKey = (key: string): boolean => { +  return (/^[A-Z0-9]$/).test(key); +}; + +export default class MarkComponent { +  private store: any; + +  constructor(store: any) { +    this.store = store; +  } + +  // eslint-disable-next-line max-statements +  key(key: keyUtils.Key) { +    let { mark: markState, setting } = this.store.getState(); +    let smoothscroll = setting.properties.smoothscroll; + +    if (!markState.setMode && !markState.jumpMode) { +      return false; +    } + +    if (cancelKey(key)) { +      this.store.dispatch(markActions.cancel()); +      return true; +    } + +    if (key.ctrlKey || key.metaKey || key.altKey) { +      consoleFrames.postError('Unknown mark'); +    } else if (globalKey(key.key) && markState.setMode) { +      this.doSetGlobal(key); +    } else if (globalKey(key.key) && markState.jumpMode) { +      this.doJumpGlobal(key); +    } else if (markState.setMode) { +      this.doSet(key); +    } else if (markState.jumpMode) { +      this.doJump(markState.marks, key, smoothscroll); +    } + +    this.store.dispatch(markActions.cancel()); +    return true; +  } + +  doSet(key: keyUtils.Key) { +    let { x, y } = scrolls.getScroll(); +    this.store.dispatch(markActions.setLocal(key.key, x, y)); +  } + +  doJump( +    marks: { [key: string]: Mark }, +    key: keyUtils.Key, +    smoothscroll: boolean, +  ) { +    if (!marks[key.key]) { +      consoleFrames.postError('Mark is not set'); +      return; +    } + +    let { x, y } = marks[key.key]; +    scrolls.scrollTo(x, y, smoothscroll); +  } + +  doSetGlobal(key: keyUtils.Key) { +    let { x, y } = scrolls.getScroll(); +    this.store.dispatch(markActions.setGlobal(key.key, x, y)); +  } + +  doJumpGlobal(key: keyUtils.Key) { +    this.store.dispatch(markActions.jumpGlobal(key.key)); +  } +} diff --git a/src/content/components/frame-content.js b/src/content/components/frame-content.ts index ca999ba..ca999ba 100644 --- a/src/content/components/frame-content.js +++ b/src/content/components/frame-content.ts diff --git a/src/content/components/top-content/find.js b/src/content/components/top-content/find.js deleted file mode 100644 index 4d46d79..0000000 --- a/src/content/components/top-content/find.js +++ /dev/null @@ -1,41 +0,0 @@ -import * as findActions from 'content/actions/find'; -import messages from 'shared/messages'; - -export default class FindComponent { -  constructor(win, store) { -    this.win = win; -    this.store = store; - -    messages.onMessage(this.onMessage.bind(this)); -  } - -  onMessage(message) { -    switch (message.type) { -    case messages.CONSOLE_ENTER_FIND: -      return this.start(message.text); -    case messages.FIND_NEXT: -      return this.next(); -    case messages.FIND_PREV: -      return this.prev(); -    } -  } - -  start(text) { -    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(text, true)); -  } - -  next() { -    let state = this.store.getState().find; -    return this.store.dispatch(findActions.next(state.keyword, false)); -  } - -  prev() { -    let state = this.store.getState().find; -    return this.store.dispatch(findActions.prev(state.keyword, false)); -  } -} diff --git a/src/content/components/top-content/find.ts b/src/content/components/top-content/find.ts new file mode 100644 index 0000000..74b95bc --- /dev/null +++ b/src/content/components/top-content/find.ts @@ -0,0 +1,46 @@ +import * as findActions from '../../actions/find'; +import * as messages from '../../../shared/messages'; +import MessageListener from '../../MessageListener'; + +export default class FindComponent { +  private store: any; + +  constructor(store: any) { +    this.store = store; + +    new MessageListener().onWebMessage(this.onMessage.bind(this)); +  } + +  onMessage(message: messages.Message) { +    switch (message.type) { +    case messages.CONSOLE_ENTER_FIND: +      return this.start(message.text); +    case messages.FIND_NEXT: +      return this.next(); +    case messages.FIND_PREV: +      return this.prev(); +    } +  } + +  start(text: string) { +    let state = this.store.getState().find; + +    if (text.length === 0) { +      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 as string, false)); +  } + +  prev() { +    let state = this.store.getState().find; +    return this.store.dispatch( +      findActions.prev(state.keyword as string, false)); +  } +} diff --git a/src/content/components/top-content/follow-controller.js b/src/content/components/top-content/follow-controller.ts index 7f36604..d49b22a 100644 --- a/src/content/components/top-content/follow-controller.js +++ b/src/content/components/top-content/follow-controller.ts @@ -1,30 +1,45 @@ -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'; -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 +51,7 @@ export default class FollowController {      }    } -  update() { +  update(): void {      let prevState = this.state;      this.state = this.store.getState().followController; @@ -49,8 +64,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 +75,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 +124,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, @@ -141,7 +161,6 @@ export default class FollowController {    }    hintchars() { -    return this.store.getState().setting.properties.hintchars || -      properties.defaults.hintchars; +    return this.store.getState().setting.properties.hintchars;    }  } diff --git a/src/content/components/top-content/index.js b/src/content/components/top-content/index.ts index 1aaef1b..ac95ea9 100644 --- a/src/content/components/top-content/index.js +++ 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, | 
