parent
e0c4182f14
commit
4be04628e1
27 changed files with 1 additions and 1083 deletions
@ -1,32 +0,0 @@ |
||||
import * as actions from './index'; |
||||
|
||||
const enable = ( |
||||
newTab: boolean, background: boolean, |
||||
): actions.FollowAction => { |
||||
return { |
||||
type: actions.FOLLOW_CONTROLLER_ENABLE, |
||||
newTab, |
||||
background, |
||||
}; |
||||
}; |
||||
|
||||
const disable = (): actions.FollowAction => { |
||||
return { |
||||
type: actions.FOLLOW_CONTROLLER_DISABLE, |
||||
}; |
||||
}; |
||||
|
||||
const keyPress = (key: string): actions.FollowAction => { |
||||
return { |
||||
type: actions.FOLLOW_CONTROLLER_KEY_PRESS, |
||||
key: key |
||||
}; |
||||
}; |
||||
|
||||
const backspace = (): actions.FollowAction => { |
||||
return { |
||||
type: actions.FOLLOW_CONTROLLER_BACKSPACE, |
||||
}; |
||||
}; |
||||
|
||||
export { enable, disable, keyPress, backspace }; |
@ -1,81 +0,0 @@ |
||||
import Redux from 'redux'; |
||||
import Key from '../domains/Key'; |
||||
|
||||
// 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 NOOP = 'noop'; |
||||
|
||||
export interface InputKeyPressAction extends Redux.Action { |
||||
type: typeof INPUT_KEY_PRESS; |
||||
key: Key; |
||||
} |
||||
|
||||
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 NoopAction extends Redux.Action { |
||||
type: typeof NOOP; |
||||
} |
||||
|
||||
export type InputAction = InputKeyPressAction | InputClearKeysAction; |
||||
export type FollowAction = |
||||
FollowControllerEnableAction | FollowControllerDisableAction | |
||||
FollowControllerKeyPressAction | FollowControllerBackspaceAction; |
||||
export type MarkAction = |
||||
MarkStartSetAction | MarkStartJumpAction | MarkCancelAction | NoopAction; |
||||
|
||||
export type Action = |
||||
InputAction | |
||||
FollowAction | |
||||
MarkAction | |
||||
NoopAction; |
@ -1,17 +0,0 @@ |
||||
import * as actions from './index'; |
||||
import Key from '../domains/Key'; |
||||
|
||||
const keyPress = (key: Key): actions.InputAction => { |
||||
return { |
||||
type: actions.INPUT_KEY_PRESS, |
||||
key, |
||||
}; |
||||
}; |
||||
|
||||
const clearKeys = (): actions.InputAction => { |
||||
return { |
||||
type: actions.INPUT_CLEAR_KEYS |
||||
}; |
||||
}; |
||||
|
||||
export { keyPress, clearKeys }; |
@ -1,17 +0,0 @@ |
||||
import * as actions from './index'; |
||||
|
||||
const startSet = (): actions.MarkAction => { |
||||
return { type: actions.MARK_START_SET }; |
||||
}; |
||||
|
||||
const startJump = (): actions.MarkAction => { |
||||
return { type: actions.MARK_START_JUMP }; |
||||
}; |
||||
|
||||
const cancel = (): actions.MarkAction => { |
||||
return { type: actions.MARK_CANCEL }; |
||||
}; |
||||
|
||||
export { |
||||
startSet, startJump, cancel, |
||||
}; |
@ -1,112 +0,0 @@ |
||||
import * as operations from '../../shared/operations'; |
||||
import * as actions from './index'; |
||||
import * as messages from '../../shared/messages'; |
||||
import * as navigates from '../navigates'; |
||||
import * as focuses from '../focuses'; |
||||
import * as markActions from './mark'; |
||||
|
||||
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase'; |
||||
import ClipboardUseCase from '../usecases/ClipboardUseCase'; |
||||
import { SettingRepositoryImpl } from '../repositories/SettingRepository'; |
||||
import { ScrollPresenterImpl } from '../presenters/ScrollPresenter'; |
||||
import { FollowMasterClientImpl } from '../client/FollowMasterClient'; |
||||
|
||||
let addonEnabledUseCase = new AddonEnabledUseCase(); |
||||
let clipbaordUseCase = new ClipboardUseCase(); |
||||
let settingRepository = new SettingRepositoryImpl(); |
||||
let scrollPresenter = new ScrollPresenterImpl(); |
||||
let followMasterClient = new FollowMasterClientImpl(window.top); |
||||
|
||||
// eslint-disable-next-line complexity, max-lines-per-function
|
||||
const exec = async( |
||||
operation: operations.Operation, |
||||
): Promise<actions.Action> => { |
||||
let settings = settingRepository.get(); |
||||
let smoothscroll = settings.properties.smoothscroll; |
||||
switch (operation.type) { |
||||
case operations.ADDON_ENABLE: |
||||
await addonEnabledUseCase.enable(); |
||||
return { type: actions.NOOP }; |
||||
case operations.ADDON_DISABLE: |
||||
await addonEnabledUseCase.disable(); |
||||
return { type: actions.NOOP }; |
||||
case operations.ADDON_TOGGLE_ENABLED: |
||||
await addonEnabledUseCase.toggle(); |
||||
return { type: actions.NOOP }; |
||||
case operations.FIND_NEXT: |
||||
window.top.postMessage(JSON.stringify({ |
||||
type: messages.FIND_NEXT, |
||||
}), '*'); |
||||
break; |
||||
case operations.FIND_PREV: |
||||
window.top.postMessage(JSON.stringify({ |
||||
type: messages.FIND_PREV, |
||||
}), '*'); |
||||
break; |
||||
case operations.SCROLL_VERTICALLY: |
||||
scrollPresenter.scrollVertically(operation.count, smoothscroll); |
||||
break; |
||||
case operations.SCROLL_HORIZONALLY: |
||||
scrollPresenter.scrollHorizonally(operation.count, smoothscroll); |
||||
break; |
||||
case operations.SCROLL_PAGES: |
||||
scrollPresenter.scrollPages(operation.count, smoothscroll); |
||||
break; |
||||
case operations.SCROLL_TOP: |
||||
scrollPresenter.scrollToTop(smoothscroll); |
||||
break; |
||||
case operations.SCROLL_BOTTOM: |
||||
scrollPresenter.scrollToBottom(smoothscroll); |
||||
break; |
||||
case operations.SCROLL_HOME: |
||||
scrollPresenter.scrollToHome(smoothscroll); |
||||
break; |
||||
case operations.SCROLL_END: |
||||
scrollPresenter.scrollToEnd(smoothscroll); |
||||
break; |
||||
case operations.FOLLOW_START: |
||||
followMasterClient.startFollow(operation.newTab, operation.background); |
||||
break; |
||||
case operations.MARK_SET_PREFIX: |
||||
return markActions.startSet(); |
||||
case operations.MARK_JUMP_PREFIX: |
||||
return markActions.startJump(); |
||||
case operations.NAVIGATE_HISTORY_PREV: |
||||
navigates.historyPrev(window); |
||||
break; |
||||
case operations.NAVIGATE_HISTORY_NEXT: |
||||
navigates.historyNext(window); |
||||
break; |
||||
case operations.NAVIGATE_LINK_PREV: |
||||
navigates.linkPrev(window); |
||||
break; |
||||
case operations.NAVIGATE_LINK_NEXT: |
||||
navigates.linkNext(window); |
||||
break; |
||||
case operations.NAVIGATE_PARENT: |
||||
navigates.parent(window); |
||||
break; |
||||
case operations.NAVIGATE_ROOT: |
||||
navigates.root(window); |
||||
break; |
||||
case operations.FOCUS_INPUT: |
||||
focuses.focusInput(); |
||||
break; |
||||
case operations.URLS_YANK: |
||||
await clipbaordUseCase.yankCurrentURL(); |
||||
break; |
||||
case operations.URLS_PASTE: |
||||
await clipbaordUseCase.openOrSearch( |
||||
operation.newTab ? operation.newTab : false, |
||||
); |
||||
break; |
||||
default: |
||||
browser.runtime.sendMessage({ |
||||
type: messages.BACKGROUND_OPERATION, |
||||
operation, |
||||
}); |
||||
} |
||||
return { type: actions.NOOP }; |
||||
}; |
||||
|
||||
export { exec }; |
@ -1,104 +0,0 @@ |
||||
import MessageListener from '../../MessageListener'; |
||||
import { LinkHint, InputHint } from '../../presenters/Hint'; |
||||
import * as messages from '../../../shared/messages'; |
||||
import Key from '../../domains/Key'; |
||||
import TabsClient, { TabsClientImpl } from '../../client/TabsClient'; |
||||
import FollowMasterClient, { FollowMasterClientImpl } |
||||
from '../../client/FollowMasterClient'; |
||||
import FollowPresenter, { FollowPresenterImpl } |
||||
from '../../presenters/FollowPresenter'; |
||||
|
||||
let tabsClient: TabsClient = new TabsClientImpl(); |
||||
let followMasterClient: FollowMasterClient = |
||||
new FollowMasterClientImpl(window.top); |
||||
let followPresenter: FollowPresenter = |
||||
new FollowPresenterImpl(); |
||||
|
||||
interface Size { |
||||
width: number; |
||||
height: number; |
||||
} |
||||
|
||||
interface Point { |
||||
x: number; |
||||
y: number; |
||||
} |
||||
|
||||
export default class Follow { |
||||
private enabled: boolean; |
||||
|
||||
constructor() { |
||||
this.enabled = false; |
||||
|
||||
new MessageListener().onWebMessage(this.onMessage.bind(this)); |
||||
} |
||||
|
||||
key(key: Key): boolean { |
||||
if (!this.enabled) { |
||||
return false; |
||||
} |
||||
followMasterClient.sendKey(key); |
||||
return true; |
||||
} |
||||
|
||||
countHints(viewSize: Size, framePosition: Point) { |
||||
let count = followPresenter.getTargetCount(viewSize, framePosition); |
||||
followMasterClient.responseHintCount(count); |
||||
} |
||||
|
||||
createHints(viewSize: Size, framePosition: Point, tags: string[]) { |
||||
this.enabled = true; |
||||
followPresenter.createHints(viewSize, framePosition, tags); |
||||
} |
||||
|
||||
showHints(prefix: string) { |
||||
followPresenter.filterHints(prefix); |
||||
} |
||||
|
||||
removeHints() { |
||||
followPresenter.clearHints(); |
||||
this.enabled = false; |
||||
} |
||||
|
||||
async activateHints( |
||||
tag: string, newTab: boolean, background: boolean, |
||||
): Promise<void> { |
||||
let hint = followPresenter.getHint(tag); |
||||
if (!hint) { |
||||
return; |
||||
} |
||||
|
||||
if (hint instanceof LinkHint) { |
||||
let url = hint.getLink(); |
||||
// ignore taget='_blank'
|
||||
if (!newTab && hint.getLinkTarget() !== '_blank') { |
||||
hint.click(); |
||||
return; |
||||
} |
||||
// eslint-disable-next-line no-script-url
|
||||
if (!url || url === '#' || url.toLowerCase().startsWith('javascript:')) { |
||||
return; |
||||
} |
||||
await tabsClient.openUrl(url, newTab, background); |
||||
} else if (hint instanceof InputHint) { |
||||
hint.activate(); |
||||
} |
||||
} |
||||
|
||||
onMessage(message: messages.Message, _sender: Window) { |
||||
switch (message.type) { |
||||
case messages.FOLLOW_REQUEST_COUNT_TARGETS: |
||||
return this.countHints(message.viewSize, message.framePosition); |
||||
case messages.FOLLOW_CREATE_HINTS: |
||||
return this.createHints( |
||||
message.viewSize, message.framePosition, message.tags); |
||||
case messages.FOLLOW_SHOW_HINTS: |
||||
return this.showHints(message.prefix); |
||||
case messages.FOLLOW_ACTIVATE: |
||||
return this.activateHints( |
||||
message.tag, message.newTab, message.background); |
||||
case messages.FOLLOW_REMOVE_HINTS: |
||||
return this.removeHints(); |
||||
} |
||||
} |
||||
} |
@ -1,59 +0,0 @@ |
||||
import InputDriver from './../../InputDriver'; |
||||
import FollowComponent from './follow'; |
||||
import MarkComponent from './mark'; |
||||
// import KeymapperComponent from './keymapper';
|
||||
import * as messages from '../../../shared/messages'; |
||||
import MessageListener from '../../MessageListener'; |
||||
import * as blacklists from '../../../shared/blacklists'; |
||||
import Key from '../../domains/Key'; |
||||
|
||||
import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase'; |
||||
import SettingUseCase from '../../usecases/SettingUseCase'; |
||||
|
||||
let addonEnabledUseCase = new AddonEnabledUseCase(); |
||||
let settingUseCase = new SettingUseCase(); |
||||
|
||||
export default class Common { |
||||
constructor(win: Window, store: any) { |
||||
const input = new InputDriver(win.document.body); |
||||
const follow = new FollowComponent(); |
||||
const mark = new MarkComponent(store); |
||||
// const keymapper = new KeymapperComponent(store);
|
||||
|
||||
input.onKey((key: Key) => follow.key(key)); |
||||
input.onKey((key: Key) => mark.key(key)); |
||||
// input.onKey((key: Key) => keymapper.key(key));
|
||||
|
||||
this.reloadSettings(); |
||||
|
||||
new MessageListener().onBackgroundMessage(this.onMessage.bind(this)); |
||||
} |
||||
|
||||
onMessage(message: messages.Message) { |
||||
switch (message.type) { |
||||
case messages.SETTINGS_CHANGED: |
||||
return this.reloadSettings(); |
||||
case messages.ADDON_TOGGLE_ENABLED: |
||||
return addonEnabledUseCase.toggle(); |
||||
} |
||||
return undefined; |
||||
} |
||||
|
||||
async reloadSettings() { |
||||
try { |
||||
let current = await settingUseCase.reload(); |
||||
let disabled = blacklists.includes( |
||||
current.blacklist, window.location.href, |
||||
); |
||||
if (disabled) { |
||||
addonEnabledUseCase.disable(); |
||||
} else { |
||||
addonEnabledUseCase.enable(); |
||||
} |
||||
} catch (e) { |
||||
// Sometime sendMessage fails when background script is not ready.
|
||||
console.warn(e); |
||||
setTimeout(() => this.reloadSettings(), 500); |
||||
} |
||||
} |
||||
} |
@ -1,44 +0,0 @@ |
||||
import * as markActions from '../../actions/mark'; |
||||
import * as consoleFrames from '../..//console-frames'; |
||||
import Key from '../../domains/Key'; |
||||
|
||||
import MarkUseCase from '../../usecases/MarkUseCase'; |
||||
|
||||
let markUseCase = new MarkUseCase(); |
||||
|
||||
const cancelKey = (key: Key): boolean => { |
||||
return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey); |
||||
}; |
||||
|
||||
export default class MarkComponent { |
||||
private store: any; |
||||
|
||||
constructor(store: any) { |
||||
this.store = store; |
||||
} |
||||
|
||||
// eslint-disable-next-line max-statements
|
||||
key(key: Key) { |
||||
let { mark: markState } = this.store.getState(); |
||||
|
||||
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 (markState.setMode) { |
||||
markUseCase.set(key.key); |
||||
} else if (markState.jumpMode) { |
||||
markUseCase.jump(key.key); |
||||
} |
||||
|
||||
this.store.dispatch(markActions.cancel()); |
||||
return true; |
||||
} |
||||
} |
@ -1,3 +0,0 @@ |
||||
import CommonComponent from './common'; |
||||
|
||||
export default CommonComponent; |
@ -1,181 +0,0 @@ |
||||
import * as followControllerActions from '../../actions/follow-controller'; |
||||
import * as messages from '../../../shared/messages'; |
||||
import MessageListener from '../../MessageListener'; |
||||
import HintKeyProducer from '../../hint-key-producer'; |
||||
|
||||
import { SettingRepositoryImpl } from '../../repositories/SettingRepository'; |
||||
import FollowSlaveClient, { FollowSlaveClientImpl } |
||||
from '../../client/FollowSlaveClient'; |
||||
|
||||
let settingRepository = new SettingRepositoryImpl(); |
||||
|
||||
export default class FollowController { |
||||
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; |
||||
|
||||
new MessageListener().onWebMessage(this.onMessage.bind(this)); |
||||
|
||||
store.subscribe(() => { |
||||
this.update(); |
||||
}); |
||||
} |
||||
|
||||
onMessage(message: messages.Message, sender: Window) { |
||||
switch (message.type) { |
||||
case messages.FOLLOW_START: |
||||
return this.store.dispatch( |
||||
followControllerActions.enable(message.newTab, message.background)); |
||||
case messages.FOLLOW_RESPONSE_COUNT_TARGETS: |
||||
return this.create(message.count, sender); |
||||
case messages.FOLLOW_KEY_PRESS: |
||||
return this.keyPress(message.key, message.ctrlKey); |
||||
} |
||||
} |
||||
|
||||
update(): void { |
||||
let prevState = this.state; |
||||
this.state = this.store.getState().followController; |
||||
|
||||
if (!prevState.enabled && this.state.enabled) { |
||||
this.count(); |
||||
} else if (prevState.enabled && !this.state.enabled) { |
||||
this.remove(); |
||||
} else if (prevState.keys !== this.state.keys) { |
||||
this.updateHints(); |
||||
} |
||||
} |
||||
|
||||
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()); |
||||
} |
||||
|
||||
this.broadcastMessage((c: FollowSlaveClient) => { |
||||
c.filterHints(this.state.keys!!); |
||||
}); |
||||
} |
||||
|
||||
activate(): void { |
||||
this.broadcastMessage((c: FollowSlaveClient) => { |
||||
c.activateIfExists( |
||||
this.state.keys!!, |
||||
this.state.newTab!!, |
||||
this.state.background!!); |
||||
}); |
||||
} |
||||
|
||||
keyPress(key: string, ctrlKey: boolean): boolean { |
||||
if (key === '[' && ctrlKey) { |
||||
this.store.dispatch(followControllerActions.disable()); |
||||
return true; |
||||
} |
||||
switch (key) { |
||||
case 'Enter': |
||||
this.activate(); |
||||
this.store.dispatch(followControllerActions.disable()); |
||||
break; |
||||
case 'Esc': |
||||
this.store.dispatch(followControllerActions.disable()); |
||||
break; |
||||
case 'Backspace': |
||||
case 'Delete': |
||||
this.store.dispatch(followControllerActions.backspace()); |
||||
break; |
||||
default: |
||||
if (this.hintchars().includes(key)) { |
||||
this.store.dispatch(followControllerActions.keyPress(key)); |
||||
} |
||||
break; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
count() { |
||||
this.producer = new HintKeyProducer(this.hintchars()); |
||||
let doc = this.win.document; |
||||
let viewWidth = this.win.innerWidth || doc.documentElement.clientWidth; |
||||
let viewHeight = this.win.innerHeight || doc.documentElement.clientHeight; |
||||
let frameElements = this.win.document.querySelectorAll('iframe'); |
||||
|
||||
new FollowSlaveClientImpl(this.win).requestHintCount( |
||||
{ width: viewWidth, height: viewHeight }, |
||||
{ x: 0, y: 0 }); |
||||
|
||||
for (let ele of Array.from(frameElements)) { |
||||
let { left: frameX, top: frameY } = ele.getBoundingClientRect(); |
||||
new FollowSlaveClientImpl(ele.contentWindow!!).requestHintCount( |
||||
{ width: viewWidth, height: viewHeight }, |
||||
{ x: frameX, y: frameY }, |
||||
); |
||||
} |
||||
} |
||||
|
||||
create(count: number, sender: Window) { |
||||
let produced = []; |
||||
for (let i = 0; i < count; ++i) { |
||||
produced.push((this.producer as HintKeyProducer).produce()); |
||||
} |
||||
this.keys = this.keys.concat(produced); |
||||
|
||||
let doc = this.win.document; |
||||
let viewWidth = this.win.innerWidth || doc.documentElement.clientWidth; |
||||
let viewHeight = this.win.innerHeight || doc.documentElement.clientHeight; |
||||
let pos = { x: 0, y: 0 }; |
||||
if (sender !== window) { |
||||
let frameElements = this.win.document.querySelectorAll('iframe'); |
||||
let ele = Array.from(frameElements).find(e => e.contentWindow === sender); |
||||
if (!ele) { |
||||
// elements of the sender is gone
|
||||
return; |
||||
} |
||||
let { left: frameX, top: frameY } = ele.getBoundingClientRect(); |
||||
pos = { x: frameX, y: frameY }; |
||||
} |
||||
new FollowSlaveClientImpl(sender).createHints( |
||||
{ width: viewWidth, height: viewHeight }, |
||||
pos, |
||||
produced, |
||||
); |
||||
} |
||||
|
||||
remove() { |
||||
this.keys = []; |
||||
this.broadcastMessage((c: FollowSlaveClient) => { |
||||
c.clearHints(); |
||||
}); |
||||
} |
||||
|
||||
private hintchars() { |
||||
return settingRepository.get().properties.hintchars; |
||||
} |
||||
|
||||
private broadcastMessage(f: (clinet: FollowSlaveClient) => void) { |
||||
let windows = [window.self].concat(Array.from(window.frames as any)); |
||||
windows |
||||
.map(w => new FollowSlaveClientImpl(w)) |
||||
.forEach(c => f(c)); |
||||
} |
||||
} |
@ -1,47 +0,0 @@ |
||||
import CommonComponent from '../common'; |
||||
import FollowController from './follow-controller'; |
||||
import * as consoleFrames from '../../console-frames'; |
||||
import * as messages from '../../../shared/messages'; |
||||
import MessageListener from '../../MessageListener'; |
||||
import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase'; |
||||
import { ScrollPresenterImpl } from '../../presenters/ScrollPresenter'; |
||||
|
||||
let addonEnabledUseCase = new AddonEnabledUseCase(); |
||||
let scrollPresenter = new ScrollPresenterImpl(); |
||||
|
||||
export default class TopContent { |
||||
private win: Window; |
||||
|
||||
constructor(win: Window, store: any) { |
||||
this.win = win; |
||||
|
||||
new CommonComponent(win, store); // eslint-disable-line no-new
|
||||
new FollowController(win, store); // eslint-disable-line no-new
|
||||
|
||||
// TODO make component
|
||||
consoleFrames.initialize(this.win.document); |
||||
|
||||
new MessageListener().onWebMessage(this.onWebMessage.bind(this)); |
||||
new MessageListener().onBackgroundMessage( |
||||
this.onBackgroundMessage.bind(this)); |
||||
} |
||||
|
||||
onWebMessage(message: messages.Message) { |
||||
switch (message.type) { |
||||
case messages.CONSOLE_UNFOCUS: |
||||
this.win.focus(); |
||||
consoleFrames.blur(window.document); |
||||
} |
||||
} |
||||
|
||||
onBackgroundMessage(message: messages.Message) { |
||||
let addonEnabled = addonEnabledUseCase.getEnabled(); |
||||
|
||||
switch (message.type) { |
||||
case messages.ADDON_ENABLED_QUERY: |
||||
return Promise.resolve(addonEnabled); |
||||
case messages.TAB_SCROLL_TO: |
||||
return scrollPresenter.scrollTo(message.x, message.y, false); |
||||
} |
||||
} |
||||
} |
@ -1,15 +0,0 @@ |
||||
import * as doms from '../shared/utils/dom'; |
||||
|
||||
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 instanceof HTMLInputElement) { |
||||
target.focus(); |
||||
} else if (target instanceof HTMLTextAreaElement) { |
||||
target.focus(); |
||||
} |
||||
}; |
||||
|
||||
export { focusInput }; |
@ -1,40 +0,0 @@ |
||||
import * as actions from '../actions'; |
||||
|
||||
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: State = defaultState, |
||||
action: actions.FollowAction, |
||||
): State { |
||||
switch (action.type) { |
||||
case actions.FOLLOW_CONTROLLER_ENABLE: |
||||
return { ...state, |
||||
enabled: true, |
||||
newTab: action.newTab, |
||||
background: action.background, |
||||
keys: '', }; |
||||
case actions.FOLLOW_CONTROLLER_DISABLE: |
||||
return { ...state, |
||||
enabled: false, }; |
||||
case actions.FOLLOW_CONTROLLER_KEY_PRESS: |
||||
return { ...state, |
||||
keys: state.keys + action.key, }; |
||||
case actions.FOLLOW_CONTROLLER_BACKSPACE: |
||||
return { ...state, |
||||
keys: state.keys.slice(0, -1), }; |
||||
default: |
||||
return state; |
||||
} |
||||
} |
@ -1,15 +0,0 @@ |
||||
import { combineReducers } from 'redux'; |
||||
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 { |
||||
input: InputState; |
||||
followController: FollowControllerState; |
||||
mark: MarkState; |
||||
} |
||||
|
||||
export default combineReducers({ |
||||
input, followController, mark, |
||||
}); |
@ -1,26 +0,0 @@ |
||||
import * as actions from '../actions'; |
||||
import Key from '../domains/Key'; |
||||
|
||||
export interface State { |
||||
keys: Key[], |
||||
} |
||||
|
||||
const defaultState: State = { |
||||
keys: [] |
||||
}; |
||||
|
||||
export default function reducer( |
||||
state: State = defaultState, |
||||
action: actions.InputAction, |
||||
): State { |
||||
switch (action.type) { |
||||
case actions.INPUT_KEY_PRESS: |
||||
return { ...state, |
||||
keys: state.keys.concat([action.key]), }; |
||||
case actions.INPUT_CLEAR_KEYS: |
||||
return { ...state, |
||||
keys: [], }; |
||||
default: |
||||
return state; |
||||
} |
||||
} |
@ -1,27 +0,0 @@ |
||||
import * as actions from '../actions'; |
||||
|
||||
export interface State { |
||||
setMode: boolean; |
||||
jumpMode: boolean; |
||||
} |
||||
|
||||
const defaultState: State = { |
||||
setMode: false, |
||||
jumpMode: false, |
||||
}; |
||||
|
||||
export default function reducer( |
||||
state: State = defaultState, |
||||
action: actions.MarkAction, |
||||
): State { |
||||
switch (action.type) { |
||||
case actions.MARK_START_SET: |
||||
return { ...state, setMode: true }; |
||||
case actions.MARK_START_JUMP: |
||||
return { ...state, jumpMode: true }; |
||||
case actions.MARK_CANCEL: |
||||
return { ...state, setMode: false, jumpMode: false }; |
||||
default: |
||||
return state; |
||||
} |
||||
} |
@ -1,8 +0,0 @@ |
||||
import promise from 'redux-promise'; |
||||
import reducers from '../reducers'; |
||||
import { createStore, applyMiddleware } from 'redux'; |
||||
|
||||
export const newStore = () => createStore( |
||||
reducers, |
||||
applyMiddleware(promise), |
||||
); |
@ -1,34 +0,0 @@ |
||||
import * as actions from 'content/actions'; |
||||
import * as followControllerActions from 'content/actions/follow-controller'; |
||||
|
||||
describe('follow-controller actions', () => { |
||||
describe('enable', () => { |
||||
it('creates FOLLOW_CONTROLLER_ENABLE action', () => { |
||||
let action = followControllerActions.enable(true); |
||||
expect(action.type).to.equal(actions.FOLLOW_CONTROLLER_ENABLE); |
||||
expect(action.newTab).to.equal(true); |
||||
}); |
||||
}); |
||||
|
||||
describe('disable', () => { |
||||
it('creates FOLLOW_CONTROLLER_DISABLE action', () => { |
||||
let action = followControllerActions.disable(true); |
||||
expect(action.type).to.equal(actions.FOLLOW_CONTROLLER_DISABLE); |
||||
}); |
||||
}); |
||||
|
||||
describe('keyPress', () => { |
||||
it('creates FOLLOW_CONTROLLER_KEY_PRESS action', () => { |
||||
let action = followControllerActions.keyPress(100); |
||||
expect(action.type).to.equal(actions.FOLLOW_CONTROLLER_KEY_PRESS); |
||||
expect(action.key).to.equal(100); |
||||
}); |
||||
}); |
||||
|
||||
describe('backspace', () => { |
||||
it('creates FOLLOW_CONTROLLER_BACKSPACE action', () => { |
||||
let action = followControllerActions.backspace(100); |
||||
expect(action.type).to.equal(actions.FOLLOW_CONTROLLER_BACKSPACE); |
||||
}); |
||||
}); |
||||
}); |
@ -1,19 +0,0 @@ |
||||
import * as actions from 'content/actions'; |
||||
import * as inputActions from 'content/actions/input'; |
||||
|
||||
describe("input actions", () => { |
||||
describe("keyPress", () => { |
||||
it('create INPUT_KEY_PRESS action', () => { |
||||
let action = inputActions.keyPress('a'); |
||||
expect(action.type).to.equal(actions.INPUT_KEY_PRESS); |
||||
expect(action.key).to.equal('a'); |
||||
}); |
||||
}); |
||||
|
||||
describe("clearKeys", () => { |
||||
it('create INPUT_CLEAR_KEYSaction', () => { |
||||
let action = inputActions.clearKeys(); |
||||
expect(action.type).to.equal(actions.INPUT_CLEAR_KEYS); |
||||
}); |
||||
}); |
||||
}); |
@ -1,25 +0,0 @@ |
||||
import * as actions from 'content/actions'; |
||||
import * as markActions from 'content/actions/mark'; |
||||
|
||||
describe('mark actions', () => { |
||||
describe('startSet', () => { |
||||
it('create MARK_START_SET action', () => { |
||||
let action = markActions.startSet(); |
||||
expect(action.type).to.equal(actions.MARK_START_SET); |
||||
}); |
||||
}); |
||||
|
||||
describe('startJump', () => { |
||||
it('create MARK_START_JUMP action', () => { |
||||
let action = markActions.startJump(); |
||||
expect(action.type).to.equal(actions.MARK_START_JUMP); |
||||
}); |
||||
}); |
||||
|
||||
describe('cancel', () => { |
||||
it('create MARK_CANCEL action', () => { |
||||
let action = markActions.cancel(); |
||||
expect(action.type).to.equal(actions.MARK_CANCEL); |
||||
}); |
||||
}); |
||||
}); |
@ -1,17 +0,0 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<body> |
||||
<a id='visible_a' href='#' >link</a> |
||||
<a href='#' style='display:none'>invisible 1</a> |
||||
<a href='#' style='visibility:hidden'>invisible 2</a> |
||||
<i>not link<i> |
||||
<div id='editable_div_1' contenteditable>link</div> |
||||
<div id='editable_div_2' contenteditable='true'>link</div> |
||||
<div id='x' contenteditable='false'>link</div> |
||||
<details> |
||||
<summary id='summary_1'>summary link</summary> |
||||
Some details |
||||
<a href='#'>not visible</a> |
||||
</details> |
||||
</body> |
||||
</html> |
@ -1,25 +0,0 @@ |
||||
import FollowComponent from 'content/components/common/follow'; |
||||
|
||||
describe('FollowComponent', () => { |
||||
describe('#getTargetElements', () => { |
||||
beforeEach(() => { |
||||
document.body.innerHTML = __html__['test/content/components/common/follow.html']; |
||||
}); |
||||
|
||||
it('returns visible links', () => { |
||||
let targets = FollowComponent.getTargetElements( |
||||
window, |
||||
{ width: window.innerWidth, height: window.innerHeight }, |
||||
{ x: 0, y: 0 }); |
||||
expect(targets).to.have.lengthOf(4); |
||||
|
||||
let ids = Array.prototype.map.call(targets, (e) => e.id); |
||||
expect(ids).to.include.members([ |
||||
'visible_a', |
||||
'editable_div_1', |
||||
'editable_div_2', |
||||
'summary_1', |
||||
]); |
||||
}); |
||||
}); |
||||
}); |
@ -1,47 +0,0 @@ |
||||
import * as actions from 'content/actions'; |
||||
import followControllerReducer from 'content/reducers/follow-controller'; |
||||
|
||||
describe('follow-controller reducer', () => { |
||||
it ('returns the initial state', () => { |
||||
let state = followControllerReducer(undefined, {}); |
||||
expect(state).to.have.property('enabled', false); |
||||
expect(state).to.have.property('newTab'); |
||||
expect(state).to.have.deep.property('keys', ''); |
||||
}); |
||||
|
||||
it ('returns next state for FOLLOW_CONTROLLER_ENABLE', () => { |
||||
let action = { type: actions.FOLLOW_CONTROLLER_ENABLE, newTab: true }; |
||||
let state = followControllerReducer({ enabled: false, newTab: false }, action); |
||||
expect(state).to.have.property('enabled', true); |
||||
expect(state).to.have.property('newTab', true); |
||||
expect(state).to.have.property('keys', ''); |
||||
}); |
||||
|
||||
it ('returns next state for FOLLOW_CONTROLLER_DISABLE', () => { |
||||
let action = { type: actions.FOLLOW_CONTROLLER_DISABLE }; |
||||
let state = followControllerReducer({ enabled: true }, action); |
||||
expect(state).to.have.property('enabled', false); |
||||
}); |
||||
|
||||
it ('returns next state for FOLLOW_CONTROLLER_KEY_PRESS', () => { |
||||
let action = { type: actions.FOLLOW_CONTROLLER_KEY_PRESS, key: 'a'}; |
||||
let state = followControllerReducer({ keys: '' }, action); |
||||
expect(state).to.have.deep.property('keys', 'a'); |
||||
|
||||
action = { type: actions.FOLLOW_CONTROLLER_KEY_PRESS, key: 'b'}; |
||||
state = followControllerReducer(state, action); |
||||
expect(state).to.have.deep.property('keys', 'ab'); |
||||
}); |
||||
|
||||
it ('returns next state for FOLLOW_CONTROLLER_BACKSPACE', () => { |
||||
let action = { type: actions.FOLLOW_CONTROLLER_BACKSPACE }; |
||||
let state = followControllerReducer({ keys: 'ab' }, action); |
||||
expect(state).to.have.deep.property('keys', 'a'); |
||||
|
||||
state = followControllerReducer(state, action); |
||||
expect(state).to.have.deep.property('keys', ''); |
||||
|
||||
state = followControllerReducer(state, action); |
||||
expect(state).to.have.deep.property('keys', ''); |
||||
}); |
||||
}); |
@ -1,25 +0,0 @@ |
||||
import * as actions from 'content/actions'; |
||||
import inputReducer from 'content/reducers/input'; |
||||
|
||||
describe("input reducer", () => { |
||||
it('return the initial state', () => { |
||||
let state = inputReducer(undefined, {}); |
||||
expect(state).to.have.deep.property('keys', []); |
||||
}); |
||||
|
||||
it('return next state for INPUT_KEY_PRESS', () => { |
||||
let action = { type: actions.INPUT_KEY_PRESS, key: 'a' }; |
||||
let state = inputReducer(undefined, action); |
||||
expect(state).to.have.deep.property('keys', ['a']); |
||||
|
||||
action = { type: actions.INPUT_KEY_PRESS, key: 'b' }; |
||||
state = inputReducer(state, action); |
||||
expect(state).to.have.deep.property('keys', ['a', 'b']); |
||||
}); |
||||
|
||||
it('return next state for INPUT_CLEAR_KEYS', () => { |
||||
let action = { type: actions.INPUT_CLEAR_KEYS }; |
||||
let state = inputReducer({ keys: [1, 2, 3] }, action); |
||||
expect(state).to.have.deep.property('keys', []); |
||||
}); |
||||
}); |
@ -1,31 +0,0 @@ |
||||
import * as actions from 'content/actions'; |
||||
import reducer from 'content/reducers/mark'; |
||||
|
||||
describe("mark reducer", () => { |
||||
it('return the initial state', () => { |
||||
let state = reducer(undefined, {}); |
||||
expect(state.setMode).to.be.false; |
||||
expect(state.jumpMode).to.be.false; |
||||
}); |
||||
|
||||
it('starts set mode', () => { |
||||
let action = { type: actions.MARK_START_SET }; |
||||
let state = reducer(undefined, action); |
||||
expect(state.setMode).to.be.true; |
||||
}); |
||||
|
||||
it('starts jump mode', () => { |
||||
let action = { type: actions.MARK_START_JUMP }; |
||||
let state = reducer(undefined, action); |
||||
expect(state.jumpMode).to.be.true; |
||||
}); |
||||
|
||||
it('cancels set and jump mode', () => { |
||||
let action = { type: actions.MARK_CANCEL }; |
||||
let state = reducer({ setMode: true }, action); |
||||
expect(state.setMode).to.be.false; |
||||
|
||||
state = reducer({ jumpMode: true }, action); |
||||
expect(state.jumpMode).to.be.false; |
||||
}); |
||||
}); |
Reference in new issue