From e76ca380f733b515c31297a285d8bea44e074a1b Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Fri, 10 May 2019 22:27:20 +0900 Subject: Make addon-enabled as a clean architecture --- src/content/reducers/addon.ts | 22 ---------------------- src/content/reducers/index.ts | 4 +--- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 src/content/reducers/addon.ts (limited to 'src/content/reducers') diff --git a/src/content/reducers/addon.ts b/src/content/reducers/addon.ts deleted file mode 100644 index 2131228..0000000 --- a/src/content/reducers/addon.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as actions from '../actions'; - -export interface State { - enabled: boolean; -} - -const defaultState: State = { - enabled: true, -}; - -export default function reducer( - state: State = defaultState, - action: actions.AddonAction, -): State { - switch (action.type) { - case actions.ADDON_SET_ENABLED: - return { ...state, - enabled: action.enabled, }; - default: - return state; - } -} diff --git a/src/content/reducers/index.ts b/src/content/reducers/index.ts index fb5eb84..6f11512 100644 --- a/src/content/reducers/index.ts +++ b/src/content/reducers/index.ts @@ -1,5 +1,4 @@ import { combineReducers } from 'redux'; -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'; @@ -8,7 +7,6 @@ import followController, { State as FollowControllerState } import mark, { State as MarkState } from './mark'; export interface State { - addon: AddonState; find: FindState; setting: SettingState; input: InputState; @@ -17,5 +15,5 @@ export interface State { } export default combineReducers({ - addon, find, setting, input, followController, mark, + find, setting, input, followController, mark, }); -- cgit v1.2.3 From bacf83a32083c5a4c4a45c061288081423bbf18a Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 11 May 2019 08:04:01 +0900 Subject: Make settings as a clean architecture --- src/content/actions/index.ts | 11 ---- src/content/actions/operation.ts | 7 ++- src/content/actions/setting.ts | 28 --------- src/content/client/SettingClient.ts | 17 ++++++ src/content/components/common/index.ts | 36 +++++------ src/content/components/common/keymapper.ts | 39 ++++++++---- src/content/components/common/mark.ts | 8 ++- .../components/top-content/follow-controller.ts | 8 ++- src/content/reducers/index.ts | 4 +- src/content/reducers/setting.ts | 40 ------------ src/content/repositories/SettingRepository.ts | 22 +++++++ src/content/usecases/SettingUseCase.ts | 24 ++++++++ test/content/actions/setting.test.ts | 43 ------------- test/content/reducers/setting.test.ts | 31 ---------- .../content/repositories/SettingRepository.test.ts | 30 +++++++++ test/content/usecases/SettingUseCaase.test.ts | 71 ++++++++++++++++++++++ 16 files changed, 223 insertions(+), 196 deletions(-) delete mode 100644 src/content/actions/setting.ts create mode 100644 src/content/client/SettingClient.ts delete mode 100644 src/content/reducers/setting.ts create mode 100644 src/content/repositories/SettingRepository.ts create mode 100644 src/content/usecases/SettingUseCase.ts delete mode 100644 test/content/actions/setting.test.ts delete mode 100644 test/content/reducers/setting.test.ts create mode 100644 test/content/repositories/SettingRepository.test.ts create mode 100644 test/content/usecases/SettingUseCaase.test.ts (limited to 'src/content/reducers') diff --git a/src/content/actions/index.ts b/src/content/actions/index.ts index 74353fb..4e395c5 100644 --- a/src/content/actions/index.ts +++ b/src/content/actions/index.ts @@ -1,13 +1,9 @@ import Redux from 'redux'; -import Settings from '../../shared/Settings'; import * as keyUtils from '../../shared/utils/keys'; // 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'; @@ -37,11 +33,6 @@ export interface FindSetKeywordAction extends Redux.Action { found: boolean; } -export interface SettingSetAction extends Redux.Action { - type: typeof SETTING_SET; - settings: Settings, -} - export interface InputKeyPressAction extends Redux.Action { type: typeof INPUT_KEY_PRESS; key: keyUtils.Key; @@ -94,7 +85,6 @@ export interface NoopAction extends Redux.Action { } export type FindAction = FindSetKeywordAction | NoopAction; -export type SettingAction = SettingSetAction; export type InputAction = InputKeyPressAction | InputClearKeysAction; export type FollowAction = FollowControllerEnableAction | FollowControllerDisableAction | @@ -105,7 +95,6 @@ export type MarkAction = export type Action = FindAction | - SettingAction | InputAction | FollowAction | MarkAction | diff --git a/src/content/actions/operation.ts b/src/content/actions/operation.ts index 949f69f..f65d0bd 100644 --- a/src/content/actions/operation.ts +++ b/src/content/actions/operation.ts @@ -9,14 +9,16 @@ import * as consoleFrames from '../console-frames'; import * as markActions from './mark'; import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase'; +import { SettingRepositoryImpl } from '../repositories/SettingRepository'; let addonEnabledUseCase = new AddonEnabledUseCase(); +let settingRepository = new SettingRepositoryImpl(); // eslint-disable-next-line complexity, max-lines-per-function const exec = async( operation: operations.Operation, - settings: any, ): Promise => { + let settings = settingRepository.get(); let smoothscroll = settings.properties.smoothscroll; switch (operation.type) { case operations.ADDON_ENABLE: @@ -97,7 +99,8 @@ const exec = async( break; case operations.URLS_PASTE: urls.paste( - window, operation.newTab ? operation.newTab : false, settings.search + window, operation.newTab ? operation.newTab : false, + settings.search, ); break; default: diff --git a/src/content/actions/setting.ts b/src/content/actions/setting.ts deleted file mode 100644 index 92f8559..0000000 --- a/src/content/actions/setting.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as actions from './index'; -import * as operations from '../../shared/operations'; -import * as messages from '../../shared/messages'; -import Settings, { Keymaps } from '../../shared/Settings'; - -const reservedKeymaps: Keymaps = { - '': { type: operations.CANCEL }, - '': { type: operations.CANCEL }, -}; - -const set = (settings: Settings): actions.SettingAction => { - return { - type: actions.SETTING_SET, - settings: { - ...settings, - keymaps: { ...settings.keymaps, ...reservedKeymaps }, - } - }; -}; - -const load = async(): Promise => { - let settings = await browser.runtime.sendMessage({ - type: messages.SETTINGS_QUERY, - }); - return set(settings); -}; - -export { set, load }; diff --git a/src/content/client/SettingClient.ts b/src/content/client/SettingClient.ts new file mode 100644 index 0000000..c67f544 --- /dev/null +++ b/src/content/client/SettingClient.ts @@ -0,0 +1,17 @@ +import Settings from '../../shared/Settings'; +import * as messages from '../../shared/messages'; + +export default interface SettingClient { + load(): Promise; + + // eslint-disable-next-line semi +} + +export class SettingClientImpl { + async load(): Promise { + let settings = await browser.runtime.sendMessage({ + type: messages.SETTINGS_QUERY, + }); + return settings as Settings; + } +} diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts index be77812..899953d 100644 --- a/src/content/components/common/index.ts +++ b/src/content/components/common/index.ts @@ -2,22 +2,18 @@ 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 blacklists from '../../../shared/blacklists'; import * as keys from '../../../shared/utils/keys'; -import * as actions from '../../actions'; import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase'; +import SettingUseCase from '../../usecases/SettingUseCase'; let addonEnabledUseCase = new AddonEnabledUseCase(); +let settingUseCase = new SettingUseCase(); 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); @@ -28,9 +24,6 @@ export default class Common { 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)); @@ -41,23 +34,22 @@ export default class Common { case messages.SETTINGS_CHANGED: return this.reloadSettings(); case messages.ADDON_TOGGLE_ENABLED: - addonEnabledUseCase.toggle(); + return addonEnabledUseCase.toggle(); } + return undefined; } - reloadSettings() { + async reloadSettings() { try { - this.store.dispatch(settingActions.load()) - .then((action: actions.SettingAction) => { - let enabled = !blacklists.includes( - action.settings.blacklist, this.win.location.href - ); - if (enabled) { - addonEnabledUseCase.enable(); - } else { - addonEnabledUseCase.disable(); - } - }); + 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); diff --git a/src/content/components/common/keymapper.ts b/src/content/components/common/keymapper.ts index 02579ec..c901ffe 100644 --- a/src/content/components/common/keymapper.ts +++ b/src/content/components/common/keymapper.ts @@ -4,8 +4,18 @@ import * as operations from '../../../shared/operations'; import * as keyUtils from '../../../shared/utils/keys'; import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase'; +import { SettingRepositoryImpl } from '../../repositories/SettingRepository'; +import { Keymaps } from '../../../shared/Settings'; + +type KeymapEntityMap = Map; let addonEnabledUseCase = new AddonEnabledUseCase(); +let settingRepository = new SettingRepositoryImpl(); + +const reservedKeymaps: Keymaps = { + '': { type: operations.CANCEL }, + '': { type: operations.CANCEL }, +}; const mapStartsWith = ( mapping: keyUtils.Key[], @@ -29,18 +39,11 @@ export default class KeymapperComponent { this.store = store; } - // eslint-disable-next-line max-statements 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.map( - (e: {key: keyUtils.Key[], op: operations.Operation}) => [e.key, e.op], - ) - ); - + let input = this.store.getState().input; + let keymaps = this.keymapEntityMap(); let matched = Array.from(keymaps.keys()).filter( (mapping: keyUtils.Key[]) => { return mapStartsWith(mapping, input.keys); @@ -62,11 +65,23 @@ export default class KeymapperComponent { return true; } let operation = keymaps.get(matched[0]) as operations.Operation; - let act = operationActions.exec( - operation, state.setting, - ); + let act = operationActions.exec(operation); this.store.dispatch(act); this.store.dispatch(inputActions.clearKeys()); return true; } + + private keymapEntityMap(): KeymapEntityMap { + let keymaps = { + ...settingRepository.get().keymaps, + ...reservedKeymaps, + }; + let entries = Object.entries(keymaps).map((entry) => { + return [ + keyUtils.fromMapKeys(entry[0]), + entry[1], + ]; + }) as [keyUtils.Key[], operations.Operation][]; + return new Map(entries); + } } diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts index 1237385..77aa15d 100644 --- a/src/content/components/common/mark.ts +++ b/src/content/components/common/mark.ts @@ -4,6 +4,10 @@ import * as consoleFrames from '../..//console-frames'; import * as keyUtils from '../../../shared/utils/keys'; import Mark from '../../Mark'; +import { SettingRepositoryImpl } from '../../repositories/SettingRepository'; + +let settingRepository = new SettingRepositoryImpl(); + const cancelKey = (key: keyUtils.Key): boolean => { return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey); }; @@ -21,8 +25,8 @@ export default class MarkComponent { // eslint-disable-next-line max-statements key(key: keyUtils.Key) { - let { mark: markState, setting } = this.store.getState(); - let smoothscroll = setting.properties.smoothscroll; + let smoothscroll = settingRepository.get().properties.smoothscroll; + let { mark: markState } = this.store.getState(); if (!markState.setMode && !markState.jumpMode) { return false; diff --git a/src/content/components/top-content/follow-controller.ts b/src/content/components/top-content/follow-controller.ts index d49b22a..2fcf365 100644 --- a/src/content/components/top-content/follow-controller.ts +++ b/src/content/components/top-content/follow-controller.ts @@ -3,6 +3,10 @@ import * as messages from '../../../shared/messages'; import MessageListener, { WebMessageSender } from '../../MessageListener'; import HintKeyProducer from '../../hint-key-producer'; +import { SettingRepositoryImpl } from '../../repositories/SettingRepository'; + +let settingRepository = new SettingRepositoryImpl(); + const broadcastMessage = (win: Window, message: messages.Message): void => { let json = JSON.stringify(message); let frames = [win.self].concat(Array.from(win.frames as any)); @@ -160,7 +164,7 @@ export default class FollowController { }); } - hintchars() { - return this.store.getState().setting.properties.hintchars; + private hintchars() { + return settingRepository.get().properties.hintchars; } } diff --git a/src/content/reducers/index.ts b/src/content/reducers/index.ts index 6f11512..21e8918 100644 --- a/src/content/reducers/index.ts +++ b/src/content/reducers/index.ts @@ -1,6 +1,5 @@ import { combineReducers } from 'redux'; 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'; @@ -8,12 +7,11 @@ import mark, { State as MarkState } from './mark'; export interface State { find: FindState; - setting: SettingState; input: InputState; followController: FollowControllerState; mark: MarkState; } export default combineReducers({ - find, setting, input, followController, mark, + find, input, followController, mark, }); diff --git a/src/content/reducers/setting.ts b/src/content/reducers/setting.ts deleted file mode 100644 index 9ca1380..0000000 --- a/src/content/reducers/setting.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as actions from '../actions'; -import * as keyUtils from '../../shared/utils/keys'; -import * as operations from '../../shared/operations'; -import { Search, Properties, DefaultSetting } from '../../shared/Settings'; - -export interface State { - keymaps: { key: keyUtils.Key[], op: operations.Operation }[]; - search: Search; - properties: Properties; -} - -// defaultState does not refer due to the state is load from -// background on load. -const defaultState: State = { - keymaps: [], - search: DefaultSetting.search, - properties: DefaultSetting.properties, -}; - -export default function reducer( - state: State = defaultState, - action: actions.SettingAction, -): State { - switch (action.type) { - case actions.SETTING_SET: - return { - keymaps: Object.entries(action.settings.keymaps).map((entry) => { - return { - key: keyUtils.fromMapKeys(entry[0]), - op: entry[1], - }; - }), - properties: action.settings.properties, - search: action.settings.search, - }; - default: - return state; - } -} - diff --git a/src/content/repositories/SettingRepository.ts b/src/content/repositories/SettingRepository.ts new file mode 100644 index 0000000..ce13c25 --- /dev/null +++ b/src/content/repositories/SettingRepository.ts @@ -0,0 +1,22 @@ +import Settings, { DefaultSetting } from '../../shared/Settings'; + +let current: Settings = DefaultSetting; + +export default interface SettingRepository { + set(setting: Settings): void; + + get(): Settings; + + // eslint-disable-next-line semi +} + +export class SettingRepositoryImpl implements SettingRepository { + set(setting: Settings): void { + current = setting; + } + + get(): Settings { + return current; + } + +} diff --git a/src/content/usecases/SettingUseCase.ts b/src/content/usecases/SettingUseCase.ts new file mode 100644 index 0000000..765cb45 --- /dev/null +++ b/src/content/usecases/SettingUseCase.ts @@ -0,0 +1,24 @@ +import SettingRepository, { SettingRepositoryImpl } + from '../repositories/SettingRepository'; +import SettingClient, { SettingClientImpl } from '../client/SettingClient'; +import Settings from '../../shared/Settings'; + +export default class SettingUseCase { + private repository: SettingRepository; + + private client: SettingClient; + + constructor({ + repository = new SettingRepositoryImpl(), + client = new SettingClientImpl(), + } = {}) { + this.repository = repository; + this.client = client; + } + + async reload(): Promise { + let settings = await this.client.load(); + this.repository.set(settings); + return settings; + } +} diff --git a/test/content/actions/setting.test.ts b/test/content/actions/setting.test.ts deleted file mode 100644 index c831433..0000000 --- a/test/content/actions/setting.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as actions from 'content/actions'; -import * as settingActions from 'content/actions/setting'; - -describe("setting actions", () => { - describe("set", () => { - it('create SETTING_SET action', () => { - let action = settingActions.set({ - keymaps: { - 'dd': 'remove current tab', - 'z': 'increment', - }, - search: { - default: "google", - engines: { - google: 'https://google.com/search?q={}', - } - }, - properties: { - hintchars: 'abcd1234', - }, - blacklist: [], - }); - expect(action.type).to.equal(actions.SETTING_SET); - expect(action.settings.properties.hintchars).to.equal('abcd1234'); - }); - - it('overrides cancel keys', () => { - let action = settingActions.set({ - keymaps: { - "k": { "type": "scroll.vertically", "count": -1 }, - "j": { "type": "scroll.vertically", "count": 1 }, - } - }); - let keymaps = action.settings.keymaps; - expect(action.settings.keymaps).to.deep.equals({ - "k": { type: "scroll.vertically", count: -1 }, - "j": { type: "scroll.vertically", count: 1 }, - '': { type: 'cancel' }, - '': { type: 'cancel' }, - }); - }); - }); -}); diff --git a/test/content/reducers/setting.test.ts b/test/content/reducers/setting.test.ts deleted file mode 100644 index 9b332aa..0000000 --- a/test/content/reducers/setting.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as actions from 'content/actions'; -import settingReducer from 'content/reducers/setting'; - -describe("content setting reducer", () => { - it('return the initial state', () => { - let state = settingReducer(undefined, {}); - expect(state.keymaps).to.be.empty; - }); - - it('return next state for SETTING_SET', () => { - let newSettings = { red: 'apple', yellow: 'banana' }; - let action = { - type: actions.SETTING_SET, - settings: { - keymaps: { - "zz": { type: "zoom.neutral" }, - "": { "type": "addon.toggle.enabled" } - }, - "blacklist": [] - } - } - let state = settingReducer(undefined, action); - expect(state.keymaps).to.have.deep.all.members([ - { key: [{ key: 'z', shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, - { key: 'z', shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }], - op: { type: 'zoom.neutral' }}, - { key: [{ key: 'Esc', shiftKey: true, ctrlKey: false, altKey: false, metaKey: false }], - op: { type: 'addon.toggle.enabled' }}, - ]); - }); -}); diff --git a/test/content/repositories/SettingRepository.test.ts b/test/content/repositories/SettingRepository.test.ts new file mode 100644 index 0000000..fea70b7 --- /dev/null +++ b/test/content/repositories/SettingRepository.test.ts @@ -0,0 +1,30 @@ +import { SettingRepositoryImpl } from '../../../src/content/repositories/SettingRepository'; +import { expect } from 'chai'; + +describe('SettingRepositoryImpl', () => { + it('updates and gets current value', () => { + let sut = new SettingRepositoryImpl(); + + let settings = { + keymaps: {}, + search: { + default: 'google', + engines: { + google: 'https://google.com/?q={}', + } + }, + properties: { + hintchars: 'abcd1234', + smoothscroll: false, + complete: 'sbh', + }, + blacklist: [], + } + + sut.set(settings); + + let actual = sut.get(); + expect(actual.properties.hintchars).to.equal('abcd1234'); + }); +}); + diff --git a/test/content/usecases/SettingUseCaase.test.ts b/test/content/usecases/SettingUseCaase.test.ts new file mode 100644 index 0000000..02cef78 --- /dev/null +++ b/test/content/usecases/SettingUseCaase.test.ts @@ -0,0 +1,71 @@ +import SettingRepository from '../../../src/content/repositories/SettingRepository'; +import SettingClient from '../../../src/content/client/SettingClient'; +import SettingUseCase from '../../../src/content/usecases/SettingUseCase'; +import Settings, { DefaultSetting } from '../../../src/shared/Settings'; +import { expect } from 'chai'; + +class MockSettingRepository implements SettingRepository { + private current: Settings; + + constructor() { + this.current = DefaultSetting; + } + + set(settings: Settings): void { + this.current= settings; + } + + get(): Settings { + return this.current; + } +} + +class MockSettingClient implements SettingClient { + private data: Settings; + + constructor(data: Settings) { + this.data = data; + } + + load(): Promise { + return Promise.resolve(this.data); + } +} + +describe('AddonEnabledUseCase', () => { + let repository: MockSettingRepository; + let client: MockSettingClient; + let sut: SettingUseCase; + + beforeEach(() => { + let testSettings = { + keymaps: {}, + search: { + default: 'google', + engines: { + google: 'https://google.com/?q={}', + } + }, + properties: { + hintchars: 'abcd1234', + smoothscroll: false, + complete: 'sbh', + }, + blacklist: [], + }; + + repository = new MockSettingRepository(); + client = new MockSettingClient(testSettings); + sut = new SettingUseCase({ repository, client }); + }); + + describe('#reload', () => { + it('loads settings and store to repository', async() => { + let settings = await sut.reload(); + expect(settings.properties.hintchars).to.equal('abcd1234'); + + let saved = repository.get(); + expect(saved.properties.hintchars).to.equal('abcd1234'); + }); + }); +}); -- cgit v1.2.3 From 1ba1660269b24446e9df7df0016de8c3e5596c8f Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 11 May 2019 11:37:18 +0900 Subject: Make find as a clean architecture --- src/content/actions/find.ts | 100 ------------ src/content/actions/index.ts | 11 -- src/content/client/ConsoleClient.ts | 30 ++++ src/content/client/FindClient.ts | 25 +++ src/content/components/top-content/find.ts | 26 +--- src/content/components/top-content/index.ts | 2 +- src/content/presenters/FindPresenter.ts | 59 ++++++++ src/content/reducers/find.ts | 25 --- src/content/reducers/index.ts | 4 +- src/content/repositories/FindRepository.ts | 19 +++ src/content/repositories/SettingRepository.ts | 1 - src/content/usecases/FindUseCase.ts | 81 ++++++++++ test/content/reducers/find.test.ts | 22 --- test/content/repositories/FindRepository.test.ts | 15 ++ test/content/usecases/FindUseCase.test.ts | 184 +++++++++++++++++++++++ 15 files changed, 423 insertions(+), 181 deletions(-) delete mode 100644 src/content/actions/find.ts create mode 100644 src/content/client/ConsoleClient.ts create mode 100644 src/content/client/FindClient.ts create mode 100644 src/content/presenters/FindPresenter.ts delete mode 100644 src/content/reducers/find.ts create mode 100644 src/content/repositories/FindRepository.ts create mode 100644 src/content/usecases/FindUseCase.ts delete mode 100644 test/content/reducers/find.test.ts create mode 100644 test/content/repositories/FindRepository.test.ts create mode 100644 test/content/usecases/FindUseCase.test.ts (limited to 'src/content/reducers') diff --git a/src/content/actions/find.ts b/src/content/actions/find.ts deleted file mode 100644 index 53e03ae..0000000 --- a/src/content/actions/find.ts +++ /dev/null @@ -1,100 +0,0 @@ -// -// window.find(aString, aCaseSensitive, aBackwards, aWrapAround, -// aWholeWord, aSearchInFrames); -// -// NOTE: window.find is not standard API -// https://developer.mozilla.org/en-US/docs/Web/API/Window/find - -import * as messages from '../../shared/messages'; -import * as actions from './index'; -import * as consoleFrames from '../console-frames'; - -interface MyWindow extends Window { - find( - aString: string, - aCaseSensitive?: boolean, - aBackwards?: boolean, - aWrapAround?: boolean, - aWholeWord?: boolean, - aSearchInFrames?: boolean, - aShowDialog?: boolean): boolean; -} - -// eslint-disable-next-line no-var, vars-on-top, init-declarations -declare var window: MyWindow; - -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 - - // eslint-disable-next-line no-extra-parens - let found = window.find(str, caseSensitive, backwards, wrapScan); - if (found) { - return found; - } - let sel = window.getSelection(); - if (sel) { - sel.removeAllRanges(); - } - - // eslint-disable-next-line no-extra-parens - return window.find(str, caseSensitive, backwards, wrapScan); -}; - -// eslint-disable-next-line max-statements -const findNext = async( - currentKeyword: string, reset: boolean, backwards: boolean, -): Promise => { - if (reset) { - let sel = window.getSelection(); - if (sel) { - sel.removeAllRanges(); - } - } - - let keyword = currentKeyword; - if (currentKeyword) { - browser.runtime.sendMessage({ - type: messages.FIND_SET_KEYWORD, - keyword: currentKeyword, - }); - } else { - keyword = await browser.runtime.sendMessage({ - type: messages.FIND_GET_KEYWORD, - }); - } - if (!keyword) { - await consoleFrames.postError('No previous search keywords'); - return { type: actions.NOOP }; - } - let found = find(keyword, backwards); - if (found) { - consoleFrames.postInfo('Pattern found: ' + keyword); - } else { - consoleFrames.postError('Pattern not found: ' + keyword); - } - - return { - type: actions.FIND_SET_KEYWORD, - keyword, - found, - }; -}; - -const next = ( - currentKeyword: string, reset: boolean, -): Promise => { - return findNext(currentKeyword, reset, false); -}; - -const prev = ( - currentKeyword: string, reset: boolean, -): Promise => { - return findNext(currentKeyword, reset, true); -}; - -export { next, prev }; diff --git a/src/content/actions/index.ts b/src/content/actions/index.ts index 4e395c5..f6d19aa 100644 --- a/src/content/actions/index.ts +++ b/src/content/actions/index.ts @@ -1,9 +1,6 @@ import Redux from 'redux'; import * as keyUtils from '../../shared/utils/keys'; -// Find -export const FIND_SET_KEYWORD = 'find.set.keyword'; - // User input export const INPUT_KEY_PRESS = 'input.key.press'; export const INPUT_CLEAR_KEYS = 'input.clear.keys'; @@ -27,12 +24,6 @@ export const MARK_SET_LOCAL = 'mark.set.local'; export const NOOP = 'noop'; -export interface FindSetKeywordAction extends Redux.Action { - type: typeof FIND_SET_KEYWORD; - keyword: string; - found: boolean; -} - export interface InputKeyPressAction extends Redux.Action { type: typeof INPUT_KEY_PRESS; key: keyUtils.Key; @@ -84,7 +75,6 @@ export interface NoopAction extends Redux.Action { type: typeof NOOP; } -export type FindAction = FindSetKeywordAction | NoopAction; export type InputAction = InputKeyPressAction | InputClearKeysAction; export type FollowAction = FollowControllerEnableAction | FollowControllerDisableAction | @@ -94,7 +84,6 @@ export type MarkAction = MarkCancelAction | MarkSetLocalAction | NoopAction; export type Action = - FindAction | InputAction | FollowAction | MarkAction | diff --git a/src/content/client/ConsoleClient.ts b/src/content/client/ConsoleClient.ts new file mode 100644 index 0000000..e7046e5 --- /dev/null +++ b/src/content/client/ConsoleClient.ts @@ -0,0 +1,30 @@ +import * as messages from '../../shared/messages'; + +export default interface ConsoleClient { + info(text: string): Promise; + error(text: string): Promise; + + // eslint-disable-next-line semi +} + +export class ConsoleClientImpl implements ConsoleClient { + async info(text: string): Promise { + await browser.runtime.sendMessage({ + type: messages.CONSOLE_FRAME_MESSAGE, + message: { + type: messages.CONSOLE_SHOW_INFO, + text, + }, + }); + } + + async error(text: string): Promise { + await browser.runtime.sendMessage({ + type: messages.CONSOLE_FRAME_MESSAGE, + message: { + type: messages.CONSOLE_SHOW_ERROR, + text, + }, + }); + } +} diff --git a/src/content/client/FindClient.ts b/src/content/client/FindClient.ts new file mode 100644 index 0000000..22cd3cb --- /dev/null +++ b/src/content/client/FindClient.ts @@ -0,0 +1,25 @@ +import * as messages from '../../shared/messages'; + +export default interface FindClient { + getGlobalLastKeyword(): Promise; + + setGlobalLastKeyword(keyword: string): Promise; + + // eslint-disable-next-line semi +} + +export class FindClientImpl implements FindClient { + async getGlobalLastKeyword(): Promise { + let keyword = await browser.runtime.sendMessage({ + type: messages.FIND_GET_KEYWORD, + }); + return keyword as string; + } + + async setGlobalLastKeyword(keyword: string): Promise { + await browser.runtime.sendMessage({ + type: messages.FIND_SET_KEYWORD, + keyword: keyword, + }); + } +} diff --git a/src/content/components/top-content/find.ts b/src/content/components/top-content/find.ts index 74b95bc..c25cbeb 100644 --- a/src/content/components/top-content/find.ts +++ b/src/content/components/top-content/find.ts @@ -1,13 +1,12 @@ -import * as findActions from '../../actions/find'; import * as messages from '../../../shared/messages'; import MessageListener from '../../MessageListener'; -export default class FindComponent { - private store: any; +import FindUseCase from '../../usecases/FindUseCase'; - constructor(store: any) { - this.store = store; +let findUseCase = new FindUseCase(); +export default class FindComponent { + constructor() { new MessageListener().onWebMessage(this.onMessage.bind(this)); } @@ -20,27 +19,18 @@ export default class FindComponent { case messages.FIND_PREV: return this.prev(); } + return Promise.resolve(); } 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)); + return findUseCase.startFind(text.length === 0 ? null : text); } next() { - let state = this.store.getState().find; - return this.store.dispatch( - findActions.next(state.keyword as string, false)); + return findUseCase.findNext(); } prev() { - let state = this.store.getState().find; - return this.store.dispatch( - findActions.prev(state.keyword as string, false)); + return findUseCase.findPrev(); } } diff --git a/src/content/components/top-content/index.ts b/src/content/components/top-content/index.ts index 101edca..b9ef2dd 100644 --- a/src/content/components/top-content/index.ts +++ b/src/content/components/top-content/index.ts @@ -17,7 +17,7 @@ export default class TopContent { new CommonComponent(win, store); // eslint-disable-line no-new new FollowController(win, store); // eslint-disable-line no-new - new FindComponent(store); // eslint-disable-line no-new + new FindComponent(); // eslint-disable-line no-new // TODO make component consoleFrames.initialize(this.win.document); diff --git a/src/content/presenters/FindPresenter.ts b/src/content/presenters/FindPresenter.ts new file mode 100644 index 0000000..6dd03f8 --- /dev/null +++ b/src/content/presenters/FindPresenter.ts @@ -0,0 +1,59 @@ +import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient'; + +export default interface FindPresenter { + find(keyword: string, backwards: boolean): boolean; + + clearSelection(): void; + + // eslint-disable-next-line semi +} + +// window.find(aString, aCaseSensitive, aBackwards, aWrapAround, +// aWholeWord, aSearchInFrames); +// +// NOTE: window.find is not standard API +// https://developer.mozilla.org/en-US/docs/Web/API/Window/find +interface MyWindow extends Window { + find( + aString: string, + aCaseSensitive?: boolean, + aBackwards?: boolean, + aWrapAround?: boolean, + aWholeWord?: boolean, + aSearchInFrames?: boolean, + aShowDialog?: boolean): boolean; +} + +// eslint-disable-next-line no-var, vars-on-top, init-declarations +declare var window: MyWindow; + +export class FindPresenterImpl implements FindPresenter { + private consoleClient: ConsoleClient; + + constructor({ consoleClient = new ConsoleClientImpl() } = {}) { + this.consoleClient = consoleClient; + } + + find(keyword: 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(keyword, caseSensitive, backwards, wrapScan); + if (found) { + return found; + } + this.clearSelection(); + + return window.find(keyword, caseSensitive, backwards, wrapScan); + } + + clearSelection(): void { + let sel = window.getSelection(); + if (sel) { + sel.removeAllRanges(); + } + } +} diff --git a/src/content/reducers/find.ts b/src/content/reducers/find.ts deleted file mode 100644 index 8c3e637..0000000 --- a/src/content/reducers/find.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as actions from '../actions'; - -export interface State { - keyword: string | null; - found: boolean; -} - -const defaultState: State = { - keyword: null, - found: false, -}; - -export default function reducer( - state: State = defaultState, - action: actions.FindAction, -): State { - switch (action.type) { - case actions.FIND_SET_KEYWORD: - return { ...state, - keyword: action.keyword, - found: action.found, }; - default: - return state; - } -} diff --git a/src/content/reducers/index.ts b/src/content/reducers/index.ts index 21e8918..812a404 100644 --- a/src/content/reducers/index.ts +++ b/src/content/reducers/index.ts @@ -1,17 +1,15 @@ import { combineReducers } from 'redux'; -import find, { State as FindState } from './find'; 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 { - find: FindState; input: InputState; followController: FollowControllerState; mark: MarkState; } export default combineReducers({ - find, input, followController, mark, + input, followController, mark, }); diff --git a/src/content/repositories/FindRepository.ts b/src/content/repositories/FindRepository.ts new file mode 100644 index 0000000..85eca40 --- /dev/null +++ b/src/content/repositories/FindRepository.ts @@ -0,0 +1,19 @@ +export default interface FindRepository { + getLastKeyword(): string | null; + + setLastKeyword(keyword: string): void; + + // eslint-disable-next-line semi +} + +let current: string | null = null; + +export class FindRepositoryImpl implements FindRepository { + getLastKeyword(): string | null { + return current; + } + + setLastKeyword(keyword: string): void { + current = keyword; + } +} diff --git a/src/content/repositories/SettingRepository.ts b/src/content/repositories/SettingRepository.ts index ce13c25..711b2a2 100644 --- a/src/content/repositories/SettingRepository.ts +++ b/src/content/repositories/SettingRepository.ts @@ -18,5 +18,4 @@ export class SettingRepositoryImpl implements SettingRepository { get(): Settings { return current; } - } diff --git a/src/content/usecases/FindUseCase.ts b/src/content/usecases/FindUseCase.ts new file mode 100644 index 0000000..4fda323 --- /dev/null +++ b/src/content/usecases/FindUseCase.ts @@ -0,0 +1,81 @@ +import FindPresenter, { FindPresenterImpl } from '../presenters/FindPresenter'; +import FindRepository, { FindRepositoryImpl } + from '../repositories/FindRepository'; +import FindClient, { FindClientImpl } from '../client/FindClient'; +import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient'; + +export default class FindUseCase { + private presenter: FindPresenter; + + private repository: FindRepository; + + private client: FindClient; + + private consoleClient: ConsoleClient; + + constructor({ + presenter = new FindPresenterImpl() as FindPresenter, + repository = new FindRepositoryImpl(), + client = new FindClientImpl(), + consoleClient = new ConsoleClientImpl(), + } = {}) { + this.presenter = presenter; + this.repository = repository; + this.client = client; + this.consoleClient = consoleClient; + } + + async startFind(keyword: string | null): Promise { + this.presenter.clearSelection(); + if (keyword) { + this.saveKeyword(keyword); + } else { + let lastKeyword = await this.getKeyword(); + if (!lastKeyword) { + return this.showNoLastKeywordError(); + } + this.saveKeyword(lastKeyword); + } + return this.findNext(); + } + + findNext(): Promise { + return this.findNextPrev(false); + } + + findPrev(): Promise { + return this.findNextPrev(true); + } + + private async findNextPrev( + backwards: boolean, + ): Promise { + let keyword = await this.getKeyword(); + if (!keyword) { + return this.showNoLastKeywordError(); + } + let found = this.presenter.find(keyword, backwards); + if (found) { + this.consoleClient.info('Pattern found: ' + keyword); + } else { + this.consoleClient.error('Pattern not found: ' + keyword); + } + } + + private async getKeyword(): Promise { + let keyword = this.repository.getLastKeyword(); + if (!keyword) { + keyword = await this.client.getGlobalLastKeyword(); + } + return keyword; + } + + private async saveKeyword(keyword: string): Promise { + this.repository.setLastKeyword(keyword); + await this.client.setGlobalLastKeyword(keyword); + } + + private async showNoLastKeywordError(): Promise { + await this.consoleClient.error('No previous search keywords'); + } +} diff --git a/test/content/reducers/find.test.ts b/test/content/reducers/find.test.ts deleted file mode 100644 index 66a2c67..0000000 --- a/test/content/reducers/find.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as actions from 'content/actions'; -import findReducer from 'content/reducers/find'; - -describe("find reducer", () => { - it('return the initial state', () => { - let state = findReducer(undefined, {}); - expect(state).to.have.property('keyword', null); - expect(state).to.have.property('found', false); - }); - - it('return next state for FIND_SET_KEYWORD', () => { - let action = { - type: actions.FIND_SET_KEYWORD, - keyword: 'xyz', - found: true, - }; - let state = findReducer({}, action); - - expect(state.keyword).is.equal('xyz'); - expect(state.found).to.be.true; - }); -}); diff --git a/test/content/repositories/FindRepository.test.ts b/test/content/repositories/FindRepository.test.ts new file mode 100644 index 0000000..dcb2dff --- /dev/null +++ b/test/content/repositories/FindRepository.test.ts @@ -0,0 +1,15 @@ +import { FindRepositoryImpl } from '../../../src/content/repositories/FindRepository'; +import { expect } from 'chai'; + +describe('FindRepositoryImpl', () => { + it('updates and gets last keyword', () => { + let sut = new FindRepositoryImpl(); + + expect(sut.getLastKeyword()).to.be.null; + + sut.setLastKeyword('monkey'); + + expect(sut.getLastKeyword()).to.equal('monkey'); + }); +}); + diff --git a/test/content/usecases/FindUseCase.test.ts b/test/content/usecases/FindUseCase.test.ts new file mode 100644 index 0000000..347b817 --- /dev/null +++ b/test/content/usecases/FindUseCase.test.ts @@ -0,0 +1,184 @@ +import FindRepository from '../../../src/content/repositories/FindRepository'; +import FindPresenter from '../../../src/content/presenters/FindPresenter'; +import ConsoleClient from '../../../src/content/client/ConsoleClient'; +import FindClient from '../../../src/content/client/FindClient'; +import FindUseCase from '../../../src/content/usecases/FindUseCase'; +import { expect } from 'chai'; + +class MockFindRepository implements FindRepository { + public keyword: string | null; + + constructor() { + this.keyword = null; + } + + getLastKeyword(): string | null { + return this.keyword; + } + + setLastKeyword(keyword: string): void { + this.keyword = keyword; + } +} + +class MockFindPresenter implements FindPresenter { + public document: string; + + public highlighted: boolean; + + constructor() { + this.document = ''; + this.highlighted = false; + } + + find(keyword: string, _backward: boolean): boolean { + let found = this.document.includes(keyword); + this.highlighted = found; + return found; + } + + clearSelection(): void { + this.highlighted = false; + } +} + +class MockFindClient implements FindClient { + public keyword: string | null; + + constructor() { + this.keyword = null; + } + + getGlobalLastKeyword(): Promise { + return Promise.resolve(this.keyword); + } + + setGlobalLastKeyword(keyword: string): Promise { + this.keyword = keyword; + return Promise.resolve(); + } +} + +class MockConsoleClient implements ConsoleClient { + public isError: boolean; + + public text: string; + + constructor() { + this.isError = false; + this.text = ''; + } + + info(text: string): Promise { + this.isError = false; + this.text = text; + return Promise.resolve(); + } + + error(text: string): Promise { + this.isError = true; + this.text = text; + return Promise.resolve(); + } +} + +describe('FindUseCase', () => { + let repository: MockFindRepository; + let presenter: MockFindPresenter; + let client: MockFindClient; + let consoleClient: MockConsoleClient; + let sut: FindUseCase; + + beforeEach(() => { + repository = new MockFindRepository(); + presenter = new MockFindPresenter(); + client = new MockFindClient(); + consoleClient = new MockConsoleClient(); + sut = new FindUseCase({ repository, presenter, client, consoleClient }); + }); + + describe('#startFind', () => { + it('find next by ketword', async() => { + presenter.document = 'monkey punch'; + + await sut.startFind('monkey'); + + expect(await presenter.highlighted).to.be.true; + expect(await consoleClient.text).to.equal('Pattern found: monkey'); + expect(await repository.getLastKeyword()).to.equal('monkey'); + expect(await client.getGlobalLastKeyword()).to.equal('monkey'); + }); + + it('find next by last keyword', async() => { + presenter.document = 'gorilla kick'; + repository.keyword = 'gorilla'; + + await sut.startFind(null); + + expect(await presenter.highlighted).to.be.true; + expect(await consoleClient.text).to.equal('Pattern found: gorilla'); + expect(await repository.getLastKeyword()).to.equal('gorilla'); + expect(await client.getGlobalLastKeyword()).to.equal('gorilla'); + }); + + it('find next by global last keyword', async() => { + presenter.document = 'chimpanzee typing'; + + repository.keyword = null; + client.keyword = 'chimpanzee'; + + await sut.startFind(null); + + expect(await presenter.highlighted).to.be.true; + expect(await consoleClient.text).to.equal('Pattern found: chimpanzee'); + expect(await repository.getLastKeyword()).to.equal('chimpanzee'); + expect(await client.getGlobalLastKeyword()).to.equal('chimpanzee'); + }); + + it('find not found error', async() => { + presenter.document = 'zoo'; + + await sut.startFind('giraffe'); + + expect(await presenter.highlighted).to.be.false; + expect(await consoleClient.text).to.equal('Pattern not found: giraffe'); + expect(await repository.getLastKeyword()).to.equal('giraffe'); + expect(await client.getGlobalLastKeyword()).to.equal('giraffe'); + }); + + it('show errors when no last keywords', async() => { + repository.keyword = null; + client.keyword = null; + + await sut.startFind(null); + + expect(await consoleClient.text).to.equal('No previous search keywords'); + expect(await consoleClient.isError).to.be.true; + }); + }); + + describe('#findNext', () => { + it('finds by last keyword', async() => { + presenter.document = 'monkey punch'; + repository.keyword = 'monkey'; + + await sut.findNext(); + + expect(await presenter.highlighted).to.be.true; + expect(await consoleClient.text).to.equal('Pattern found: monkey'); + }); + + it('show errors when no last keywords', async() => { + repository.keyword = null; + client.keyword = null; + + await sut.findNext(); + + expect(await consoleClient.text).to.equal('No previous search keywords'); + expect(await consoleClient.isError).to.be.true; + }); + }); + + describe('#findPrev', () => { + }); +}); -- cgit v1.2.3 From c6288f19d93a05f96274dd172450b8350389c39f Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 11 May 2019 16:38:08 +0900 Subject: Mark set/jump as a clean architecture --- src/content/Mark.ts | 6 -- src/content/actions/index.ts | 11 +-- src/content/actions/mark.ts | 31 +------ src/content/client/MarkClient.ts | 28 ++++++ src/content/components/common/mark.ts | 48 +--------- src/content/domains/Mark.ts | 6 ++ src/content/presenters/ScrollPresenter.ts | 2 +- src/content/reducers/mark.ts | 8 -- src/content/repositories/MarkRepository.ts | 25 ++++++ src/content/usecases/MarkUseCase.ts | 62 +++++++++++++ test/content/actions/mark.test.ts | 10 --- test/content/mock/MockConsoleClient.ts | 26 ++++++ test/content/mock/MockScrollPresenter.ts | 47 ++++++++++ test/content/reducers/mark.test.ts | 10 --- test/content/repositories/MarkRepository.test.ts | 13 +++ test/content/usecases/FindUseCase.test.ts | 25 +----- test/content/usecases/MarkUseCase.test.ts | 107 +++++++++++++++++++++++ 17 files changed, 322 insertions(+), 143 deletions(-) delete mode 100644 src/content/Mark.ts create mode 100644 src/content/client/MarkClient.ts create mode 100644 src/content/domains/Mark.ts create mode 100644 src/content/repositories/MarkRepository.ts create mode 100644 src/content/usecases/MarkUseCase.ts create mode 100644 test/content/mock/MockConsoleClient.ts create mode 100644 test/content/mock/MockScrollPresenter.ts create mode 100644 test/content/repositories/MarkRepository.test.ts create mode 100644 test/content/usecases/MarkUseCase.test.ts (limited to 'src/content/reducers') diff --git a/src/content/Mark.ts b/src/content/Mark.ts deleted file mode 100644 index f1282fc..0000000 --- a/src/content/Mark.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface Mark { - x: number; - y: number; - // eslint-disable-next-line semi -} - diff --git a/src/content/actions/index.ts b/src/content/actions/index.ts index f6d19aa..eb826fc 100644 --- a/src/content/actions/index.ts +++ b/src/content/actions/index.ts @@ -20,7 +20,6 @@ export const FOLLOW_CONTROLLER_BACKSPACE = 'follow.controller.backspace'; 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'; @@ -64,13 +63,6 @@ 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; } @@ -80,8 +72,7 @@ export type FollowAction = FollowControllerEnableAction | FollowControllerDisableAction | FollowControllerKeyPressAction | FollowControllerBackspaceAction; export type MarkAction = - MarkStartSetAction | MarkStartJumpAction | - MarkCancelAction | MarkSetLocalAction | NoopAction; + MarkStartSetAction | MarkStartJumpAction | MarkCancelAction | NoopAction; export type Action = InputAction | diff --git a/src/content/actions/mark.ts b/src/content/actions/mark.ts index 5eb9554..1068507 100644 --- a/src/content/actions/mark.ts +++ b/src/content/actions/mark.ts @@ -1,5 +1,4 @@ import * as actions from './index'; -import * as messages from '../../shared/messages'; const startSet = (): actions.MarkAction => { return { type: actions.MARK_START_SET }; @@ -13,34 +12,6 @@ const cancel = (): actions.MarkAction => { return { type: actions.MARK_CANCEL }; }; -const setLocal = (key: string, x: number, y: number): actions.MarkAction => { - return { - type: actions.MARK_SET_LOCAL, - 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: actions.NOOP }; -}; - -const jumpGlobal = (key: string): actions.MarkAction => { - browser.runtime.sendMessage({ - type: messages.MARK_JUMP_GLOBAL, - key, - }); - return { type: actions.NOOP }; -}; - export { - startSet, startJump, cancel, setLocal, - setGlobal, jumpGlobal, + startSet, startJump, cancel, }; diff --git a/src/content/client/MarkClient.ts b/src/content/client/MarkClient.ts new file mode 100644 index 0000000..b7cf535 --- /dev/null +++ b/src/content/client/MarkClient.ts @@ -0,0 +1,28 @@ +import Mark from '../domains/Mark'; +import * as messages from '../../shared/messages'; + +export default interface MarkClient { + setGloablMark(key: string, mark: Mark): Promise; + + jumpGlobalMark(key: string): Promise; + + // eslint-disable-next-line semi +} + +export class MarkClientImpl implements MarkClient { + async setGloablMark(key: string, mark: Mark): Promise { + await browser.runtime.sendMessage({ + type: messages.MARK_SET_GLOBAL, + key, + x: mark.x, + y: mark.y, + }); + } + + async jumpGlobalMark(key: string): Promise { + await browser.runtime.sendMessage({ + type: messages.MARK_JUMP_GLOBAL, + key, + }); + } +} diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts index ddd1a38..eec95d6 100644 --- a/src/content/components/common/mark.ts +++ b/src/content/components/common/mark.ts @@ -1,22 +1,15 @@ import * as markActions from '../../actions/mark'; import * as consoleFrames from '../..//console-frames'; import * as keyUtils from '../../../shared/utils/keys'; -import Mark from '../../Mark'; -import { SettingRepositoryImpl } from '../../repositories/SettingRepository'; -import { ScrollPresenterImpl } from '../../presenters/ScrollPresenter'; +import MarkUseCase from '../../usecases/MarkUseCase'; -let settingRepository = new SettingRepositoryImpl(); -let scrollPresenter = new ScrollPresenterImpl(); +let markUseCase = new MarkUseCase(); 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; @@ -26,7 +19,6 @@ export default class MarkComponent { // eslint-disable-next-line max-statements key(key: keyUtils.Key) { - let smoothscroll = settingRepository.get().properties.smoothscroll; let { mark: markState } = this.store.getState(); if (!markState.setMode && !markState.jumpMode) { @@ -40,45 +32,13 @@ export default class MarkComponent { 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); + markUseCase.set(key.key); } else if (markState.jumpMode) { - this.doJump(markState.marks, key, smoothscroll); + markUseCase.jump(key.key); } this.store.dispatch(markActions.cancel()); return true; } - - doSet(key: keyUtils.Key) { - let { x, y } = scrollPresenter.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]; - scrollPresenter.scrollTo(x, y, smoothscroll); - } - - doSetGlobal(key: keyUtils.Key) { - let { x, y } = scrollPresenter.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/domains/Mark.ts b/src/content/domains/Mark.ts new file mode 100644 index 0000000..f1282fc --- /dev/null +++ b/src/content/domains/Mark.ts @@ -0,0 +1,6 @@ +export default interface Mark { + x: number; + y: number; + // eslint-disable-next-line semi +} + diff --git a/src/content/presenters/ScrollPresenter.ts b/src/content/presenters/ScrollPresenter.ts index 9f47394..9286fb0 100644 --- a/src/content/presenters/ScrollPresenter.ts +++ b/src/content/presenters/ScrollPresenter.ts @@ -94,7 +94,7 @@ class Scroller { } } -type Point = { x: number, y: number }; +export type Point = { x: number, y: number }; export default interface ScrollPresenter { getScroll(): Point; diff --git a/src/content/reducers/mark.ts b/src/content/reducers/mark.ts index 7409938..a8f2f1b 100644 --- a/src/content/reducers/mark.ts +++ b/src/content/reducers/mark.ts @@ -1,16 +1,13 @@ -import Mark from '../Mark'; import * as actions from '../actions'; export interface State { setMode: boolean; jumpMode: boolean; - marks: { [key: string]: Mark }; } const defaultState: State = { setMode: false, jumpMode: false, - marks: {}, }; export default function reducer( @@ -24,11 +21,6 @@ export default function reducer( return { ...state, jumpMode: true }; case actions.MARK_CANCEL: return { ...state, setMode: false, jumpMode: false }; - case actions.MARK_SET_LOCAL: { - let marks = { ...state.marks }; - marks[action.key] = { x: action.x, y: action.y }; - return { ...state, setMode: false, marks }; - } default: return state; } diff --git a/src/content/repositories/MarkRepository.ts b/src/content/repositories/MarkRepository.ts new file mode 100644 index 0000000..ed5afe2 --- /dev/null +++ b/src/content/repositories/MarkRepository.ts @@ -0,0 +1,25 @@ +import Mark from '../domains/Mark'; + +export default interface MarkRepository { + set(key: string, mark: Mark): void; + + get(key: string): Mark | null; + + // eslint-disable-next-line semi +} + +const saved: {[key: string]: Mark} = {}; + +export class MarkRepositoryImpl implements MarkRepository { + set(key: string, mark: Mark): void { + saved[key] = mark; + } + + get(key: string): Mark | null { + let v = saved[key]; + if (!v) { + return null; + } + return { ...v }; + } +} diff --git a/src/content/usecases/MarkUseCase.ts b/src/content/usecases/MarkUseCase.ts new file mode 100644 index 0000000..ec63f2b --- /dev/null +++ b/src/content/usecases/MarkUseCase.ts @@ -0,0 +1,62 @@ +import ScrollPresenter, { ScrollPresenterImpl } + from '../presenters/ScrollPresenter'; +import MarkClient, { MarkClientImpl } from '../client/MarkClient'; +import MarkRepository, { MarkRepositoryImpl } + from '../repositories/MarkRepository'; +import SettingRepository, { SettingRepositoryImpl } + from '../repositories/SettingRepository'; +import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient'; + +export default class MarkUseCase { + private scrollPresenter: ScrollPresenter; + + private client: MarkClient; + + private repository: MarkRepository; + + private settingRepository: SettingRepository; + + private consoleClient: ConsoleClient; + + constructor({ + scrollPresenter = new ScrollPresenterImpl(), + client = new MarkClientImpl(), + repository = new MarkRepositoryImpl(), + settingRepository = new SettingRepositoryImpl(), + consoleClient = new ConsoleClientImpl(), + } = {}) { + this.scrollPresenter = scrollPresenter; + this.client = client; + this.repository = repository; + this.settingRepository = settingRepository; + this.consoleClient = consoleClient; + } + + async set(key: string): Promise { + let pos = this.scrollPresenter.getScroll(); + if (this.globalKey(key)) { + this.client.setGloablMark(key, pos); + await this.consoleClient.info(`Set global mark to '${key}'`); + } else { + this.repository.set(key, pos); + await this.consoleClient.info(`Set local mark to '${key}'`); + } + } + + async jump(key: string): Promise { + if (this.globalKey(key)) { + await this.client.jumpGlobalMark(key); + } else { + let pos = this.repository.get(key); + if (!pos) { + throw new Error('Mark is not set'); + } + let smooth = this.settingRepository.get().properties.smoothscroll; + this.scrollPresenter.scrollTo(pos.x, pos.y, smooth); + } + } + + private globalKey(key: string) { + return (/^[A-Z0-9]$/).test(key); + } +} diff --git a/test/content/actions/mark.test.ts b/test/content/actions/mark.test.ts index 6c6d59e..f2df367 100644 --- a/test/content/actions/mark.test.ts +++ b/test/content/actions/mark.test.ts @@ -22,14 +22,4 @@ describe('mark actions', () => { expect(action.type).to.equal(actions.MARK_CANCEL); }); }); - - describe('setLocal', () => { - it('create setLocal action', () => { - let action = markActions.setLocal('a', 20, 30); - expect(action.type).to.equal(actions.MARK_SET_LOCAL); - expect(action.key).to.equal('a'); - expect(action.x).to.equal(20); - expect(action.y).to.equal(30); - }); - }); }); diff --git a/test/content/mock/MockConsoleClient.ts b/test/content/mock/MockConsoleClient.ts new file mode 100644 index 0000000..8de2d83 --- /dev/null +++ b/test/content/mock/MockConsoleClient.ts @@ -0,0 +1,26 @@ +import ConsoleClient from '../../../src/content/client/ConsoleClient'; + +export default class MockConsoleClient implements ConsoleClient { + public isError: boolean; + + public text: string; + + constructor() { + this.isError = false; + this.text = ''; + } + + info(text: string): Promise { + this.isError = false; + this.text = text; + return Promise.resolve(); + } + + error(text: string): Promise { + this.isError = true; + this.text = text; + return Promise.resolve(); + } +} + + diff --git a/test/content/mock/MockScrollPresenter.ts b/test/content/mock/MockScrollPresenter.ts new file mode 100644 index 0000000..819569a --- /dev/null +++ b/test/content/mock/MockScrollPresenter.ts @@ -0,0 +1,47 @@ +import ScrollPresenter, { Point } from '../../../src/content/presenters/ScrollPresenter'; + +export default class MockScrollPresenter implements ScrollPresenter { + private pos: Point; + + constructor() { + this.pos = { x: 0, y: 0 }; + } + + getScroll(): Point { + return this.pos; + } + + scrollVertically(amount: number, _smooth: boolean): void { + this.pos.y += amount; + } + + scrollHorizonally(amount: number, _smooth: boolean): void { + this.pos.x += amount; + } + + scrollPages(amount: number, _smooth: boolean): void { + this.pos.x += amount; + } + + scrollTo(x: number, y: number, _smooth: boolean): void { + this.pos.x = x; + this.pos.y = y; + } + + scrollToTop(_smooth: boolean): void { + this.pos.y = 0; + } + + scrollToBottom(_smooth: boolean): void { + this.pos.y = Infinity; + } + + scrollToHome(_smooth: boolean): void { + this.pos.x = 0; + } + + scrollToEnd(_smooth: boolean): void { + this.pos.x = Infinity; + } +} + diff --git a/test/content/reducers/mark.test.ts b/test/content/reducers/mark.test.ts index 1a51c3e..918a560 100644 --- a/test/content/reducers/mark.test.ts +++ b/test/content/reducers/mark.test.ts @@ -6,7 +6,6 @@ describe("mark reducer", () => { let state = reducer(undefined, {}); expect(state.setMode).to.be.false; expect(state.jumpMode).to.be.false; - expect(state.marks).to.be.empty; }); it('starts set mode', () => { @@ -29,13 +28,4 @@ describe("mark reducer", () => { state = reducer({ jumpMode: true }, action); expect(state.jumpMode).to.be.false; }); - - it('stores local mark', () => { - let action = { type: actions.MARK_SET_LOCAL, key: 'a', x: 20, y: 30}; - let state = reducer({ setMode: true }, action); - expect(state.setMode).to.be.false; - expect(state.marks['a']).to.be.an('object') - expect(state.marks['a'].x).to.equal(20) - expect(state.marks['a'].y).to.equal(30) - }); }); diff --git a/test/content/repositories/MarkRepository.test.ts b/test/content/repositories/MarkRepository.test.ts new file mode 100644 index 0000000..7fced5f --- /dev/null +++ b/test/content/repositories/MarkRepository.test.ts @@ -0,0 +1,13 @@ +import { MarkRepositoryImpl } from '../../../src/content/repositories/MarkRepository'; +import { expect } from 'chai'; + +describe('MarkRepositoryImpl', () => { + it('save and load marks', () => { + let sut = new MarkRepositoryImpl(); + + sut.set('a', { x: 10, y: 20 }); + expect(sut.get('a')).to.deep.equal({ x: 10, y: 20 }); + expect(sut.get('b')).to.be.null; + }); +}); + diff --git a/test/content/usecases/FindUseCase.test.ts b/test/content/usecases/FindUseCase.test.ts index 2f966ae..c7bfd39 100644 --- a/test/content/usecases/FindUseCase.test.ts +++ b/test/content/usecases/FindUseCase.test.ts @@ -1,8 +1,8 @@ import FindRepository from '../../../src/content/repositories/FindRepository'; import FindPresenter from '../../../src/content/presenters/FindPresenter'; -import ConsoleClient from '../../../src/content/client/ConsoleClient'; import FindClient from '../../../src/content/client/FindClient'; import FindUseCase from '../../../src/content/usecases/FindUseCase'; +import MockConsoleClient from '../mock/MockConsoleClient'; import { expect } from 'chai'; class MockFindRepository implements FindRepository { @@ -59,29 +59,6 @@ class MockFindClient implements FindClient { } } -class MockConsoleClient implements ConsoleClient { - public isError: boolean; - - public text: string; - - constructor() { - this.isError = false; - this.text = ''; - } - - info(text: string): Promise { - this.isError = false; - this.text = text; - return Promise.resolve(); - } - - error(text: string): Promise { - this.isError = true; - this.text = text; - return Promise.resolve(); - } -} - describe('FindUseCase', () => { let repository: MockFindRepository; let presenter: MockFindPresenter; diff --git a/test/content/usecases/MarkUseCase.test.ts b/test/content/usecases/MarkUseCase.test.ts new file mode 100644 index 0000000..4f2dee4 --- /dev/null +++ b/test/content/usecases/MarkUseCase.test.ts @@ -0,0 +1,107 @@ +import MarkRepository from '../../../src/content/repositories/MarkRepository'; +import MarkUseCase from '../../../src/content/usecases/MarkUseCase'; +import MarkClient from '../../../src/content/client/MarkClient'; +import MockConsoleClient from '../mock/MockConsoleClient'; +import MockScrollPresenter from '../mock/MockScrollPresenter'; +import Mark from '../../../src/content/domains/Mark'; +import { expect } from 'chai'; + +class MockMarkRepository implements MarkRepository { + private current: {[key: string]: Mark}; + + constructor() { + this.current = {}; + } + + set(key: string, mark: Mark): void { + this.current[key] = mark; + } + + get(key: string): Mark | null { + return this.current[key]; + } +} + +class MockMarkClient implements MarkClient { + public marks: {[key: string]: Mark}; + public last: string; + + constructor() { + this.marks = {}; + this.last = ''; + } + + setGloablMark(key: string, mark: Mark): Promise { + this.marks[key] = mark; + return Promise.resolve(); + } + + jumpGlobalMark(key: string): Promise { + this.last = key + return Promise.resolve(); + } +} + +describe('MarkUseCase', () => { + let repository: MockMarkRepository; + let client: MockMarkClient; + let consoleClient: MockConsoleClient; + let scrollPresenter: MockScrollPresenter; + let sut: MarkUseCase; + + beforeEach(() => { + repository = new MockMarkRepository(); + client = new MockMarkClient(); + consoleClient = new MockConsoleClient(); + scrollPresenter = new MockScrollPresenter(); + sut = new MarkUseCase({ + repository, client, consoleClient, scrollPresenter, + }); + }); + + describe('#set', () => { + it('sets local mark', async() => { + scrollPresenter.scrollTo(10, 20, false); + + await sut.set('x'); + + expect(repository.get('x')).to.deep.equals({ x: 10, y: 20 }); + expect(consoleClient.text).to.equal("Set local mark to 'x'"); + }); + + it('sets global mark', async() => { + scrollPresenter.scrollTo(30, 40, false); + + await sut.set('Z'); + + expect(client.marks['Z']).to.deep.equals({ x: 30, y: 40 }); + expect(consoleClient.text).to.equal("Set global mark to 'Z'"); + }); + }); + + describe('#jump', () => { + it('jumps to local mark', async() => { + repository.set('x', { x: 20, y: 40 }); + + await sut.jump('x'); + + expect(scrollPresenter.getScroll()).to.deep.equals({ x: 20, y: 40 }); + }); + + it('throws an error when no local marks', () => { + return sut.jump('a').then(() => { + throw new Error('error'); + }).catch((e) => { + expect(e).to.be.instanceof(Error); + }) + }) + + it('jumps to global mark', async() => { + client.marks['Z'] = { x: 20, y: 0 }; + + await sut.jump('Z'); + + expect(client.last).to.equal('Z') + }); + }); +}); -- cgit v1.2.3 From a5518dce3d101cb1cb65724b82079f66f20c80c8 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 18 May 2019 21:43:56 +0900 Subject: Define Key and KeySequence --- src/content/InputDriver.ts | 6 +- src/content/actions/index.ts | 4 +- src/content/actions/input.ts | 4 +- src/content/client/FollowMasterClient.ts | 2 +- src/content/components/common/follow.ts | 2 +- src/content/components/common/index.ts | 12 +- src/content/components/common/keymapper.ts | 87 -------------- src/content/components/common/mark.ts | 6 +- src/content/controllers/KeymapController.ts | 2 +- src/content/controllers/MarkKeyController.ts | 4 +- src/content/domains/Key.ts | 74 ++++++++++++ src/content/domains/KeySequence.ts | 64 +++++++++++ src/content/reducers/input.ts | 4 +- src/content/repositories/KeymapRepository.ts | 11 +- src/content/usecases/KeymapUseCase.ts | 35 ++---- src/shared/utils/keys.ts | 99 ---------------- test/content/domains/Key.test.ts | 137 ++++++++++++++++++++++ test/content/domains/KeySequence.test.ts | 72 ++++++++++++ test/shared/utils/keys.test.ts | 164 --------------------------- 19 files changed, 387 insertions(+), 402 deletions(-) delete mode 100644 src/content/components/common/keymapper.ts create mode 100644 src/content/domains/Key.ts create mode 100644 src/content/domains/KeySequence.ts delete mode 100644 src/shared/utils/keys.ts create mode 100644 test/content/domains/Key.test.ts create mode 100644 test/content/domains/KeySequence.test.ts delete mode 100644 test/shared/utils/keys.test.ts (limited to 'src/content/reducers') diff --git a/src/content/InputDriver.ts b/src/content/InputDriver.ts index 09648c1..cddc825 100644 --- a/src/content/InputDriver.ts +++ b/src/content/InputDriver.ts @@ -1,5 +1,5 @@ import * as dom from '../shared/utils/dom'; -import * as keys from '../shared/utils/keys'; +import Key, * as keys from './domains/Key'; const cancelKey = (e: KeyboardEvent): boolean => { return e.key === 'Escape' || e.key === '[' && e.ctrlKey; @@ -8,7 +8,7 @@ const cancelKey = (e: KeyboardEvent): boolean => { export default class InputDriver { private pressed: {[key: string]: string} = {}; - private onKeyListeners: ((key: keys.Key) => boolean)[] = []; + private onKeyListeners: ((key: Key) => boolean)[] = []; constructor(target: HTMLElement) { this.pressed = {}; @@ -19,7 +19,7 @@ export default class InputDriver { target.addEventListener('keyup', this.onKeyUp.bind(this)); } - onKey(cb: (key: keys.Key) => boolean) { + onKey(cb: (key: Key) => boolean) { this.onKeyListeners.push(cb); } diff --git a/src/content/actions/index.ts b/src/content/actions/index.ts index eb826fc..49f6484 100644 --- a/src/content/actions/index.ts +++ b/src/content/actions/index.ts @@ -1,5 +1,5 @@ import Redux from 'redux'; -import * as keyUtils from '../../shared/utils/keys'; +import Key from '../domains/Key'; // User input export const INPUT_KEY_PRESS = 'input.key.press'; @@ -25,7 +25,7 @@ export const NOOP = 'noop'; export interface InputKeyPressAction extends Redux.Action { type: typeof INPUT_KEY_PRESS; - key: keyUtils.Key; + key: Key; } export interface InputClearKeysAction extends Redux.Action { diff --git a/src/content/actions/input.ts b/src/content/actions/input.ts index 1df6452..24dbb99 100644 --- a/src/content/actions/input.ts +++ b/src/content/actions/input.ts @@ -1,7 +1,7 @@ import * as actions from './index'; -import * as keyUtils from '../../shared/utils/keys'; +import Key from '../domains/Key'; -const keyPress = (key: keyUtils.Key): actions.InputAction => { +const keyPress = (key: Key): actions.InputAction => { return { type: actions.INPUT_KEY_PRESS, key, diff --git a/src/content/client/FollowMasterClient.ts b/src/content/client/FollowMasterClient.ts index 464b52f..c841902 100644 --- a/src/content/client/FollowMasterClient.ts +++ b/src/content/client/FollowMasterClient.ts @@ -1,5 +1,5 @@ import * as messages from '../../shared/messages'; -import { Key } from '../../shared/utils/keys'; +import Key from '../domains/Key'; export default interface FollowMasterClient { startFollow(newTab: boolean, background: boolean): void; diff --git a/src/content/components/common/follow.ts b/src/content/components/common/follow.ts index e0003e3..413244e 100644 --- a/src/content/components/common/follow.ts +++ b/src/content/components/common/follow.ts @@ -1,7 +1,7 @@ import MessageListener from '../../MessageListener'; import { LinkHint, InputHint } from '../../presenters/Hint'; import * as messages from '../../../shared/messages'; -import { Key } from '../../../shared/utils/keys'; +import Key from '../../domains/Key'; import TabsClient, { TabsClientImpl } from '../../client/TabsClient'; import FollowMasterClient, { FollowMasterClientImpl } from '../../client/FollowMasterClient'; diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts index c74020e..1aacf51 100644 --- a/src/content/components/common/index.ts +++ b/src/content/components/common/index.ts @@ -1,11 +1,11 @@ import InputDriver from './../../InputDriver'; import FollowComponent from './follow'; import MarkComponent from './mark'; -import KeymapperComponent from './keymapper'; +// import KeymapperComponent from './keymapper'; import * as messages from '../../../shared/messages'; import MessageListener from '../../MessageListener'; import * as blacklists from '../../../shared/blacklists'; -import * as keys from '../../../shared/utils/keys'; +import Key from '../../domains/Key'; import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase'; import SettingUseCase from '../../usecases/SettingUseCase'; @@ -18,11 +18,11 @@ export default class Common { const input = new InputDriver(win.document.body); const follow = new FollowComponent(); const mark = new MarkComponent(store); - const keymapper = new KeymapperComponent(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)); + input.onKey((key: Key) => follow.key(key)); + input.onKey((key: Key) => mark.key(key)); + // input.onKey((key: Key) => keymapper.key(key)); this.reloadSettings(); diff --git a/src/content/components/common/keymapper.ts b/src/content/components/common/keymapper.ts deleted file mode 100644 index c901ffe..0000000 --- a/src/content/components/common/keymapper.ts +++ /dev/null @@ -1,87 +0,0 @@ -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'; - -import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase'; -import { SettingRepositoryImpl } from '../../repositories/SettingRepository'; -import { Keymaps } from '../../../shared/Settings'; - -type KeymapEntityMap = Map; - -let addonEnabledUseCase = new AddonEnabledUseCase(); -let settingRepository = new SettingRepositoryImpl(); - -const reservedKeymaps: Keymaps = { - '': { type: operations.CANCEL }, - '': { type: operations.CANCEL }, -}; - -const mapStartsWith = ( - mapping: keyUtils.Key[], - keys: keyUtils.Key[], -): boolean => { - if (mapping.length < keys.length) { - return false; - } - for (let i = 0; i < keys.length; ++i) { - if (!keyUtils.equals(mapping[i], keys[i])) { - return false; - } - } - return true; -}; - -export default class KeymapperComponent { - private store: any; - - constructor(store: any) { - this.store = store; - } - - key(key: keyUtils.Key): boolean { - this.store.dispatch(inputActions.keyPress(key)); - - let input = this.store.getState().input; - let keymaps = this.keymapEntityMap(); - let matched = Array.from(keymaps.keys()).filter( - (mapping: keyUtils.Key[]) => { - return mapStartsWith(mapping, input.keys); - }); - if (!addonEnabledUseCase.getEnabled()) { - // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if - // the addon disabled - matched = matched.filter((keys) => { - let type = (keymaps.get(keys) as operations.Operation).type; - return type === operations.ADDON_ENABLE || - type === operations.ADDON_TOGGLE_ENABLED; - }); - } - if (matched.length === 0) { - this.store.dispatch(inputActions.clearKeys()); - return false; - } else if (matched.length > 1 || - matched.length === 1 && input.keys.length < matched[0].length) { - return true; - } - let operation = keymaps.get(matched[0]) as operations.Operation; - let act = operationActions.exec(operation); - this.store.dispatch(act); - this.store.dispatch(inputActions.clearKeys()); - return true; - } - - private keymapEntityMap(): KeymapEntityMap { - let keymaps = { - ...settingRepository.get().keymaps, - ...reservedKeymaps, - }; - let entries = Object.entries(keymaps).map((entry) => { - return [ - keyUtils.fromMapKeys(entry[0]), - entry[1], - ]; - }) as [keyUtils.Key[], operations.Operation][]; - return new Map(entries); - } -} diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts index eec95d6..058b873 100644 --- a/src/content/components/common/mark.ts +++ b/src/content/components/common/mark.ts @@ -1,12 +1,12 @@ import * as markActions from '../../actions/mark'; import * as consoleFrames from '../..//console-frames'; -import * as keyUtils from '../../../shared/utils/keys'; +import Key from '../../domains/Key'; import MarkUseCase from '../../usecases/MarkUseCase'; let markUseCase = new MarkUseCase(); -const cancelKey = (key: keyUtils.Key): boolean => { +const cancelKey = (key: Key): boolean => { return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey); }; @@ -18,7 +18,7 @@ export default class MarkComponent { } // eslint-disable-next-line max-statements - key(key: keyUtils.Key) { + key(key: Key) { let { mark: markState } = this.store.getState(); if (!markState.setMode && !markState.jumpMode) { diff --git a/src/content/controllers/KeymapController.ts b/src/content/controllers/KeymapController.ts index b7a7bc2..424292c 100644 --- a/src/content/controllers/KeymapController.ts +++ b/src/content/controllers/KeymapController.ts @@ -8,7 +8,7 @@ import FocusUseCase from '../usecases/FocusUseCase'; import ClipboardUseCase from '../usecases/ClipboardUseCase'; import BackgroundClient from '../client/BackgroundClient'; import MarkKeyyUseCase from '../usecases/MarkKeyUseCase'; -import { Key } from '../../shared/utils/keys'; +import Key from '../domains/Key'; export default class KeymapController { private keymapUseCase: KeymapUseCase; diff --git a/src/content/controllers/MarkKeyController.ts b/src/content/controllers/MarkKeyController.ts index 9406fbf..395dee3 100644 --- a/src/content/controllers/MarkKeyController.ts +++ b/src/content/controllers/MarkKeyController.ts @@ -1,6 +1,6 @@ import MarkUseCase from '../usecases/MarkUseCase'; import MarkKeyyUseCase from '../usecases/MarkKeyUseCase'; -import * as keys from '../../shared/utils/keys'; +import Key from '../domains/Key'; export default class MarkKeyController { private markUseCase: MarkUseCase; @@ -15,7 +15,7 @@ export default class MarkKeyController { this.markKeyUseCase = markKeyUseCase; } - press(key: keys.Key): boolean { + press(key: Key): boolean { if (this.markKeyUseCase.isSetMode()) { this.markUseCase.set(key.key); this.markKeyUseCase.disableSetMode(); diff --git a/src/content/domains/Key.ts b/src/content/domains/Key.ts new file mode 100644 index 0000000..fbbb4bb --- /dev/null +++ b/src/content/domains/Key.ts @@ -0,0 +1,74 @@ +export default interface Key { + key: string; + shiftKey?: boolean; + ctrlKey?: boolean; + altKey?: boolean; + metaKey?: boolean; + + // eslint-disable-next-line semi +} + +const modifiedKeyName = (name: string): string => { + if (name === ' ') { + return 'Space'; + } + if (name.length === 1) { + return name; + } else if (name === 'Escape') { + return 'Esc'; + } + return name; +}; + +export const fromKeyboardEvent = (e: KeyboardEvent): Key => { + let key = modifiedKeyName(e.key); + let shift = e.shiftKey; + if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) { + // make shift false for symbols to enable key bindings by symbold keys. + // But this limits key bindings by symbol keys with Shift (such as Shift+$>. + shift = false; + } + + return { + key: modifiedKeyName(e.key), + shiftKey: shift, + ctrlKey: e.ctrlKey, + altKey: e.altKey, + metaKey: e.metaKey, + }; +}; + +export const fromMapKey = (key: string): Key => { + if (key.startsWith('<') && key.endsWith('>')) { + let inner = key.slice(1, -1); + let shift = inner.includes('S-'); + let base = inner.slice(inner.lastIndexOf('-') + 1); + if (shift && base.length === 1) { + base = base.toUpperCase(); + } else if (!shift && base.length === 1) { + base = base.toLowerCase(); + } + return { + key: base, + shiftKey: inner.includes('S-'), + ctrlKey: inner.includes('C-'), + altKey: inner.includes('A-'), + metaKey: inner.includes('M-'), + }; + } + return { + key: key, + shiftKey: key.toLowerCase() !== key, + ctrlKey: false, + altKey: false, + metaKey: false, + }; +}; + +export const equals = (e1: Key, e2: Key): boolean => { + return e1.key === e2.key && + e1.ctrlKey === e2.ctrlKey && + e1.metaKey === e2.metaKey && + e1.altKey === e2.altKey && + e1.shiftKey === e2.shiftKey; +}; diff --git a/src/content/domains/KeySequence.ts b/src/content/domains/KeySequence.ts new file mode 100644 index 0000000..6a05c2f --- /dev/null +++ b/src/content/domains/KeySequence.ts @@ -0,0 +1,64 @@ +import Key, * as keyUtils from './Key'; + +export default class KeySequence { + private keys: Key[]; + + private constructor(keys: Key[]) { + this.keys = keys; + } + + static from(keys: Key[]): KeySequence { + return new KeySequence(keys); + } + + push(key: Key): number { + return this.keys.push(key); + } + + length(): number { + return this.keys.length; + } + + startsWith(o: KeySequence): boolean { + if (this.keys.length < o.keys.length) { + return false; + } + for (let i = 0; i < o.keys.length; ++i) { + if (!keyUtils.equals(this.keys[i], o.keys[i])) { + return false; + } + } + return true; + } + + getKeyArray(): Key[] { + return this.keys; + } +} + +export const fromMapKeys = (keys: string): KeySequence => { + const fromMapKeysRecursive = ( + remainings: string, mappedKeys: Key[], + ): Key[] => { + if (remainings.length === 0) { + return mappedKeys; + } + + let nextPos = 1; + if (remainings.startsWith('<')) { + let ltPos = remainings.indexOf('>'); + if (ltPos > 0) { + nextPos = ltPos + 1; + } + } + + return fromMapKeysRecursive( + remainings.slice(nextPos), + mappedKeys.concat([keyUtils.fromMapKey(remainings.slice(0, nextPos))]) + ); + }; + + let data = fromMapKeysRecursive(keys, []); + return KeySequence.from(data); +}; + diff --git a/src/content/reducers/input.ts b/src/content/reducers/input.ts index 35b9075..800a8f0 100644 --- a/src/content/reducers/input.ts +++ b/src/content/reducers/input.ts @@ -1,8 +1,8 @@ import * as actions from '../actions'; -import * as keyUtils from '../../shared/utils/keys'; +import Key from '../domains/Key'; export interface State { - keys: keyUtils.Key[], + keys: Key[], } const defaultState: State = { diff --git a/src/content/repositories/KeymapRepository.ts b/src/content/repositories/KeymapRepository.ts index 081cc54..770ba0b 100644 --- a/src/content/repositories/KeymapRepository.ts +++ b/src/content/repositories/KeymapRepository.ts @@ -1,23 +1,24 @@ -import { Key } from '../../shared/utils/keys'; +import Key from '../domains/Key'; +import KeySequence from '../domains/KeySequence'; export default interface KeymapRepository { - enqueueKey(key: Key): Key[]; + enqueueKey(key: Key): KeySequence; clear(): void; // eslint-disable-next-line semi } -let current: Key[] = []; +let current: KeySequence = KeySequence.from([]); export class KeymapRepositoryImpl { - enqueueKey(key: Key): Key[] { + enqueueKey(key: Key): KeySequence { current.push(key); return current; } clear(): void { - current = []; + current = KeySequence.from([]); } } diff --git a/src/content/usecases/KeymapUseCase.ts b/src/content/usecases/KeymapUseCase.ts index a4f9c36..af0ad77 100644 --- a/src/content/usecases/KeymapUseCase.ts +++ b/src/content/usecases/KeymapUseCase.ts @@ -7,29 +7,16 @@ import AddonEnabledRepository, { AddonEnabledRepositoryImpl } import * as operations from '../../shared/operations'; import { Keymaps } from '../../shared/Settings'; -import * as keyUtils from '../../shared/utils/keys'; +import Key from '../domains/Key'; +import KeySequence, * as keySequenceUtils from '../domains/KeySequence'; -type KeymapEntityMap = Map; +type KeymapEntityMap = Map; const reservedKeymaps: Keymaps = { '': { type: operations.CANCEL }, '': { type: operations.CANCEL }, }; -const mapStartsWith = ( - mapping: keyUtils.Key[], - keys: keyUtils.Key[], -): boolean => { - if (mapping.length < keys.length) { - return false; - } - for (let i = 0; i < keys.length; ++i) { - if (!keyUtils.equals(mapping[i], keys[i])) { - return false; - } - } - return true; -}; export default class KeymapUseCase { private repository: KeymapRepository; @@ -48,13 +35,13 @@ export default class KeymapUseCase { this.addonEnabledRepository = addonEnabledRepository; } - nextOp(key: keyUtils.Key): operations.Operation | null { - let keys = this.repository.enqueueKey(key); + nextOp(key: Key): operations.Operation | null { + let sequence = this.repository.enqueueKey(key); let keymaps = this.keymapEntityMap(); let matched = Array.from(keymaps.keys()).filter( - (mapping: keyUtils.Key[]) => { - return mapStartsWith(mapping, keys); + (mapping: KeySequence) => { + return mapping.startsWith(sequence); }); if (!this.addonEnabledRepository.get()) { // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if @@ -70,7 +57,7 @@ export default class KeymapUseCase { this.repository.clear(); return null; } else if (matched.length > 1 || - matched.length === 1 && keys.length < matched[0].length) { + matched.length === 1 && sequence.length() < matched[0].length()) { // More than one operations are matched return null; } @@ -91,10 +78,10 @@ export default class KeymapUseCase { }; let entries = Object.entries(keymaps).map((entry) => { return [ - keyUtils.fromMapKeys(entry[0]), + keySequenceUtils.fromMapKeys(entry[0]), entry[1], ]; - }) as [keyUtils.Key[], operations.Operation][]; - return new Map(entries); + }) as [KeySequence, operations.Operation][]; + return new Map(entries); } } diff --git a/src/shared/utils/keys.ts b/src/shared/utils/keys.ts deleted file mode 100644 index e9b0365..0000000 --- a/src/shared/utils/keys.ts +++ /dev/null @@ -1,99 +0,0 @@ -export interface Key { - key: string; - shiftKey: boolean | undefined; - ctrlKey: boolean | undefined; - altKey: boolean | undefined; - metaKey: boolean | undefined; -} - -const modifiedKeyName = (name: string): string => { - if (name === ' ') { - return 'Space'; - } - if (name.length === 1) { - return name; - } else if (name === 'Escape') { - return 'Esc'; - } - return name; -}; - -const fromKeyboardEvent = (e: KeyboardEvent): Key => { - let key = modifiedKeyName(e.key); - let shift = e.shiftKey; - if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) { - // make shift false for symbols to enable key bindings by symbold keys. - // But this limits key bindings by symbol keys with Shift (such as Shift+$>. - shift = false; - } - - return { - key: modifiedKeyName(e.key), - shiftKey: shift, - ctrlKey: e.ctrlKey, - altKey: e.altKey, - metaKey: e.metaKey, - }; -}; - -const fromMapKey = (key: string): Key => { - if (key.startsWith('<') && key.endsWith('>')) { - let inner = key.slice(1, -1); - let shift = inner.includes('S-'); - let base = inner.slice(inner.lastIndexOf('-') + 1); - if (shift && base.length === 1) { - base = base.toUpperCase(); - } else if (!shift && base.length === 1) { - base = base.toLowerCase(); - } - return { - key: base, - shiftKey: inner.includes('S-'), - ctrlKey: inner.includes('C-'), - altKey: inner.includes('A-'), - metaKey: inner.includes('M-'), - }; - } - return { - key: key, - shiftKey: key.toLowerCase() !== key, - ctrlKey: false, - altKey: false, - metaKey: false, - }; -}; - -const fromMapKeys = (keys: string): Key[] => { - const fromMapKeysRecursive = ( - remainings: string, mappedKeys: Key[], - ): Key[] => { - if (remainings.length === 0) { - return mappedKeys; - } - - let nextPos = 1; - if (remainings.startsWith('<')) { - let ltPos = remainings.indexOf('>'); - if (ltPos > 0) { - nextPos = ltPos + 1; - } - } - - return fromMapKeysRecursive( - remainings.slice(nextPos), - mappedKeys.concat([fromMapKey(remainings.slice(0, nextPos))]) - ); - }; - - return fromMapKeysRecursive(keys, []); -}; - -const equals = (e1: Key, e2: Key): boolean => { - return e1.key === e2.key && - e1.ctrlKey === e2.ctrlKey && - e1.metaKey === e2.metaKey && - e1.altKey === e2.altKey && - e1.shiftKey === e2.shiftKey; -}; - -export { fromKeyboardEvent, fromMapKey, fromMapKeys, equals }; diff --git a/test/content/domains/Key.test.ts b/test/content/domains/Key.test.ts new file mode 100644 index 0000000..b3f9fb6 --- /dev/null +++ b/test/content/domains/Key.test.ts @@ -0,0 +1,137 @@ +import Key, * as keys from '../../../src/content/domains/Key'; +import { expect } from 'chai' + +describe("Key", () => { + describe('fromKeyboardEvent', () => { + it('returns from keyboard input Ctrl+X', () => { + let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', { + key: 'x', shiftKey: false, ctrlKey: true, altKey: false, metaKey: true, + })); + expect(k.key).to.equal('x'); + expect(k.shiftKey).to.be.false; + expect(k.ctrlKey).to.be.true; + expect(k.altKey).to.be.false; + expect(k.metaKey).to.be.true; + }); + + it('returns from keyboard input Shift+Esc', () => { + let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', { + key: 'Escape', shiftKey: true, ctrlKey: false, altKey: false, metaKey: true + })); + expect(k.key).to.equal('Esc'); + expect(k.shiftKey).to.be.true; + expect(k.ctrlKey).to.be.false; + expect(k.altKey).to.be.false; + expect(k.metaKey).to.be.true; + }); + + it('returns from keyboard input Ctrl+$', () => { + // $ required shift pressing on most keyboards + let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', { + key: '$', shiftKey: true, ctrlKey: true, altKey: false, metaKey: false + })); + expect(k.key).to.equal('$'); + expect(k.shiftKey).to.be.false; + expect(k.ctrlKey).to.be.true; + expect(k.altKey).to.be.false; + expect(k.metaKey).to.be.false; + }); + + it('returns from keyboard input Crtl+Space', () => { + let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', { + key: ' ', shiftKey: false, ctrlKey: true, altKey: false, metaKey: false + })); + expect(k.key).to.equal('Space'); + expect(k.shiftKey).to.be.false; + expect(k.ctrlKey).to.be.true; + expect(k.altKey).to.be.false; + expect(k.metaKey).to.be.false; + }); + }); + + describe('fromMapKey', () => { + it('return for X', () => { + let key = keys.fromMapKey('x'); + expect(key.key).to.equal('x'); + expect(key.shiftKey).to.be.false; + expect(key.ctrlKey).to.be.false; + expect(key.altKey).to.be.false; + expect(key.metaKey).to.be.false; + }); + + it('return for Shift+X', () => { + let key = keys.fromMapKey('X'); + expect(key.key).to.equal('X'); + expect(key.shiftKey).to.be.true; + expect(key.ctrlKey).to.be.false; + expect(key.altKey).to.be.false; + expect(key.metaKey).to.be.false; + }); + + it('return for Ctrl+X', () => { + let key = keys.fromMapKey(''); + expect(key.key).to.equal('x'); + expect(key.shiftKey).to.be.false; + expect(key.ctrlKey).to.be.true; + expect(key.altKey).to.be.false; + expect(key.metaKey).to.be.false; + }); + + it('returns for Ctrl+Meta+X', () => { + let key = keys.fromMapKey(''); + expect(key.key).to.equal('x'); + expect(key.shiftKey).to.be.false; + expect(key.ctrlKey).to.be.true; + expect(key.altKey).to.be.false; + expect(key.metaKey).to.be.true; + }); + + it('returns for Ctrl+Shift+x', () => { + let key = keys.fromMapKey(''); + expect(key.key).to.equal('X'); + expect(key.shiftKey).to.be.true; + expect(key.ctrlKey).to.be.true; + expect(key.altKey).to.be.false; + expect(key.metaKey).to.be.false; + }); + + it('returns for Shift+Esc', () => { + let key = keys.fromMapKey(''); + expect(key.key).to.equal('Esc'); + expect(key.shiftKey).to.be.true; + expect(key.ctrlKey).to.be.false; + expect(key.altKey).to.be.false; + expect(key.metaKey).to.be.false; + }); + + it('returns for Ctrl+Esc', () => { + let key = keys.fromMapKey(''); + expect(key.key).to.equal('Esc'); + expect(key.shiftKey).to.be.false; + expect(key.ctrlKey).to.be.true; + expect(key.altKey).to.be.false; + expect(key.metaKey).to.be.false; + }); + + it('returns for Ctrl+Esc', () => { + let key = keys.fromMapKey(''); + expect(key.key).to.equal('Space'); + expect(key.shiftKey).to.be.false; + expect(key.ctrlKey).to.be.true; + expect(key.altKey).to.be.false; + expect(key.metaKey).to.be.false; + }); + }); + + describe('equals', () => { + expect(keys.equals( + { key: 'x', ctrlKey: true, }, + { key: 'x', ctrlKey: true, }, + )).to.be.true; + + expect(keys.equals( + { key: 'X', shiftKey: true, }, + { key: 'x', ctrlKey: true, }, + )).to.be.false; + }); +}); diff --git a/test/content/domains/KeySequence.test.ts b/test/content/domains/KeySequence.test.ts new file mode 100644 index 0000000..7387c06 --- /dev/null +++ b/test/content/domains/KeySequence.test.ts @@ -0,0 +1,72 @@ +import KeySequence, * as utils from '../../../src/content/domains/KeySequence'; +import { expect } from 'chai' + +describe("KeySequence", () => { + describe('#push', () => { + it('append a key to the sequence', () => { + let seq = KeySequence.from([]); + seq.push({ key: 'g' }); + seq.push({ key: 'u', shiftKey: true }); + + let array = seq.getKeyArray(); + expect(array[0]).to.deep.equal({ key: 'g' }); + expect(array[1]).to.deep.equal({ key: 'u', shiftKey: true }); + }) + }); + + describe('#startsWith', () => { + it('returns true if the key sequence starts with param', () => { + let seq = KeySequence.from([ + { key: 'g' }, + { key: 'u', shiftKey: true }, + ]); + + expect(seq.startsWith(KeySequence.from([ + ]))).to.be.true; + expect(seq.startsWith(KeySequence.from([ + { key: 'g' }, + ]))).to.be.true; + expect(seq.startsWith(KeySequence.from([ + { key: 'g' }, { key: 'u', shiftKey: true }, + ]))).to.be.true; + expect(seq.startsWith(KeySequence.from([ + { key: 'g' }, { key: 'u', shiftKey: true }, { key: 'x' }, + ]))).to.be.false; + expect(seq.startsWith(KeySequence.from([ + { key: 'h' }, + ]))).to.be.false; + }) + + it('returns true if the empty sequence starts with an empty sequence', () => { + let seq = KeySequence.from([]); + + expect(seq.startsWith(KeySequence.from([]))).to.be.true; + expect(seq.startsWith(KeySequence.from([ + { key: 'h' }, + ]))).to.be.false; + }) + }); + + describe('#fromMapKeys', () => { + it('returns mapped keys for Shift+Esc', () => { + let keyArray = utils.fromMapKeys('').getKeyArray(); + expect(keyArray).to.have.lengthOf(1); + expect(keyArray[0].key).to.equal('Esc'); + expect(keyArray[0].shiftKey).to.be.true; + }); + + it('returns mapped keys for ad', () => { + let keyArray = utils.fromMapKeys('ad').getKeyArray(); + expect(keyArray).to.have.lengthOf(5); + expect(keyArray[0].key).to.equal('a'); + expect(keyArray[1].ctrlKey).to.be.true; + expect(keyArray[1].key).to.equal('b'); + expect(keyArray[2].altKey).to.be.true; + expect(keyArray[2].key).to.equal('c'); + expect(keyArray[3].key).to.equal('d'); + expect(keyArray[4].metaKey).to.be.true; + expect(keyArray[4].key).to.equal('e'); + }); + }) + +}); diff --git a/test/shared/utils/keys.test.ts b/test/shared/utils/keys.test.ts deleted file mode 100644 index b2ad3cb..0000000 --- a/test/shared/utils/keys.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -import * as keys from 'shared/utils/keys'; - -describe("keys util", () => { - describe('fromKeyboardEvent', () => { - it('returns from keyboard input Ctrl+X', () => { - let k = keys.fromKeyboardEvent({ - key: 'x', shiftKey: false, ctrlKey: true, altKey: false, metaKey: true - }); - expect(k.key).to.equal('x'); - expect(k.shiftKey).to.be.false; - expect(k.ctrlKey).to.be.true; - expect(k.altKey).to.be.false; - expect(k.metaKey).to.be.true; - }); - - it('returns from keyboard input Shift+Esc', () => { - let k = keys.fromKeyboardEvent({ - key: 'Escape', shiftKey: true, ctrlKey: false, altKey: false, metaKey: true - }); - expect(k.key).to.equal('Esc'); - expect(k.shiftKey).to.be.true; - expect(k.ctrlKey).to.be.false; - expect(k.altKey).to.be.false; - expect(k.metaKey).to.be.true; - }); - - it('returns from keyboard input Ctrl+$', () => { - // $ required shift pressing on most keyboards - let k = keys.fromKeyboardEvent({ - key: '$', shiftKey: true, ctrlKey: true, altKey: false, metaKey: false - }); - expect(k.key).to.equal('$'); - expect(k.shiftKey).to.be.false; - expect(k.ctrlKey).to.be.true; - expect(k.altKey).to.be.false; - expect(k.metaKey).to.be.false; - }); - - it('returns from keyboard input Crtl+Space', () => { - let k = keys.fromKeyboardEvent({ - key: ' ', shiftKey: false, ctrlKey: true, altKey: false, metaKey: false - }); - expect(k.key).to.equal('Space'); - expect(k.shiftKey).to.be.false; - expect(k.ctrlKey).to.be.true; - expect(k.altKey).to.be.false; - expect(k.metaKey).to.be.false; - }); - }); - - describe('fromMapKey', () => { - it('return for X', () => { - let key = keys.fromMapKey('x'); - expect(key.key).to.equal('x'); - expect(key.shiftKey).to.be.false; - expect(key.ctrlKey).to.be.false; - expect(key.altKey).to.be.false; - expect(key.metaKey).to.be.false; - }); - - it('return for Shift+X', () => { - let key = keys.fromMapKey('X'); - expect(key.key).to.equal('X'); - expect(key.shiftKey).to.be.true; - expect(key.ctrlKey).to.be.false; - expect(key.altKey).to.be.false; - expect(key.metaKey).to.be.false; - }); - - it('return for Ctrl+X', () => { - let key = keys.fromMapKey(''); - expect(key.key).to.equal('x'); - expect(key.shiftKey).to.be.false; - expect(key.ctrlKey).to.be.true; - expect(key.altKey).to.be.false; - expect(key.metaKey).to.be.false; - }); - - it('returns for Ctrl+Meta+X', () => { - let key = keys.fromMapKey(''); - expect(key.key).to.equal('x'); - expect(key.shiftKey).to.be.false; - expect(key.ctrlKey).to.be.true; - expect(key.altKey).to.be.false; - expect(key.metaKey).to.be.true; - }); - - it('returns for Ctrl+Shift+x', () => { - let key = keys.fromMapKey(''); - expect(key.key).to.equal('X'); - expect(key.shiftKey).to.be.true; - expect(key.ctrlKey).to.be.true; - expect(key.altKey).to.be.false; - expect(key.metaKey).to.be.false; - }); - - it('returns for Shift+Esc', () => { - let key = keys.fromMapKey(''); - expect(key.key).to.equal('Esc'); - expect(key.shiftKey).to.be.true; - expect(key.ctrlKey).to.be.false; - expect(key.altKey).to.be.false; - expect(key.metaKey).to.be.false; - }); - - it('returns for Ctrl+Esc', () => { - let key = keys.fromMapKey(''); - expect(key.key).to.equal('Esc'); - expect(key.shiftKey).to.be.false; - expect(key.ctrlKey).to.be.true; - expect(key.altKey).to.be.false; - expect(key.metaKey).to.be.false; - }); - - it('returns for Ctrl+Esc', () => { - let key = keys.fromMapKey(''); - expect(key.key).to.equal('Space'); - expect(key.shiftKey).to.be.false; - expect(key.ctrlKey).to.be.true; - expect(key.altKey).to.be.false; - expect(key.metaKey).to.be.false; - }); - }); - - describe('fromMapKeys', () => { - it('returns mapped keys for Shift+Esc', () => { - let keyArray = keys.fromMapKeys(''); - expect(keyArray).to.have.lengthOf(1); - expect(keyArray[0].key).to.equal('Esc'); - expect(keyArray[0].shiftKey).to.be.true; - }); - - it('returns mapped keys for ad', () => { - let keyArray = keys.fromMapKeys('ad'); - expect(keyArray).to.have.lengthOf(5); - expect(keyArray[0].key).to.equal('a'); - expect(keyArray[1].ctrlKey).to.be.true; - expect(keyArray[1].key).to.equal('b'); - expect(keyArray[2].altKey).to.be.true; - expect(keyArray[2].key).to.equal('c'); - expect(keyArray[3].key).to.equal('d'); - expect(keyArray[4].metaKey).to.be.true; - expect(keyArray[4].key).to.equal('e'); - }); - }) - - describe('equals', () => { - expect(keys.equals({ - key: 'x', - ctrlKey: true, - }, { - key: 'x', - ctrlKey: true, - })).to.be.true; - - expect(keys.equals({ - key: 'X', - shiftKey: true, - }, { - key: 'x', - ctrlKey: true, - })).to.be.false; - }); -}); -- cgit v1.2.3 From 4be04628e19392d8da9688d538cc3374e91005d8 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 19 May 2019 09:34:40 +0900 Subject: Remove unused components --- src/content/actions/follow-controller.ts | 32 ---- src/content/actions/index.ts | 81 --------- src/content/actions/input.ts | 17 -- src/content/actions/mark.ts | 17 -- src/content/actions/operation.ts | 112 ------------- src/content/components/common/follow.ts | 104 ------------ src/content/components/common/index.ts | 59 ------- src/content/components/common/mark.ts | 44 ----- src/content/components/frame-content.ts | 3 - .../components/top-content/follow-controller.ts | 181 --------------------- src/content/components/top-content/index.ts | 47 ------ src/content/console-frames.ts | 24 +-- src/content/focuses.ts | 15 -- src/content/index.ts | 9 - src/content/reducers/follow-controller.ts | 40 ----- src/content/reducers/index.ts | 15 -- src/content/reducers/input.ts | 26 --- src/content/reducers/mark.ts | 27 --- src/content/store/index.ts | 8 - test/content/actions/follow-controller.test.ts | 34 ---- test/content/actions/input.test.ts | 19 --- test/content/actions/mark.test.ts | 25 --- test/content/components/common/follow.html | 17 -- test/content/components/common/follow.test.ts | 25 --- test/content/reducers/follow-controller.test.ts | 47 ------ test/content/reducers/input.test.ts | 25 --- test/content/reducers/mark.test.ts | 31 ---- 27 files changed, 1 insertion(+), 1083 deletions(-) delete mode 100644 src/content/actions/follow-controller.ts delete mode 100644 src/content/actions/index.ts delete mode 100644 src/content/actions/input.ts delete mode 100644 src/content/actions/mark.ts delete mode 100644 src/content/actions/operation.ts delete mode 100644 src/content/components/common/follow.ts delete mode 100644 src/content/components/common/index.ts delete mode 100644 src/content/components/common/mark.ts delete mode 100644 src/content/components/frame-content.ts delete mode 100644 src/content/components/top-content/follow-controller.ts delete mode 100644 src/content/components/top-content/index.ts delete mode 100644 src/content/focuses.ts delete mode 100644 src/content/reducers/follow-controller.ts delete mode 100644 src/content/reducers/index.ts delete mode 100644 src/content/reducers/input.ts delete mode 100644 src/content/reducers/mark.ts delete mode 100644 src/content/store/index.ts delete mode 100644 test/content/actions/follow-controller.test.ts delete mode 100644 test/content/actions/input.test.ts delete mode 100644 test/content/actions/mark.test.ts delete mode 100644 test/content/components/common/follow.html delete mode 100644 test/content/components/common/follow.test.ts delete mode 100644 test/content/reducers/follow-controller.test.ts delete mode 100644 test/content/reducers/input.test.ts delete mode 100644 test/content/reducers/mark.test.ts (limited to 'src/content/reducers') diff --git a/src/content/actions/follow-controller.ts b/src/content/actions/follow-controller.ts deleted file mode 100644 index 115b3b6..0000000 --- a/src/content/actions/follow-controller.ts +++ /dev/null @@ -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 }; diff --git a/src/content/actions/index.ts b/src/content/actions/index.ts deleted file mode 100644 index 49f6484..0000000 --- a/src/content/actions/index.ts +++ /dev/null @@ -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; diff --git a/src/content/actions/input.ts b/src/content/actions/input.ts deleted file mode 100644 index 24dbb99..0000000 --- a/src/content/actions/input.ts +++ /dev/null @@ -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 }; diff --git a/src/content/actions/mark.ts b/src/content/actions/mark.ts deleted file mode 100644 index 1068507..0000000 --- a/src/content/actions/mark.ts +++ /dev/null @@ -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, -}; diff --git a/src/content/actions/operation.ts b/src/content/actions/operation.ts deleted file mode 100644 index 657cf47..0000000 --- a/src/content/actions/operation.ts +++ /dev/null @@ -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 => { - 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 }; diff --git a/src/content/components/common/follow.ts b/src/content/components/common/follow.ts deleted file mode 100644 index 413244e..0000000 --- a/src/content/components/common/follow.ts +++ /dev/null @@ -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 { - 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(); - } - } -} diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts deleted file mode 100644 index 1aacf51..0000000 --- a/src/content/components/common/index.ts +++ /dev/null @@ -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); - } - } -} diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts deleted file mode 100644 index 058b873..0000000 --- a/src/content/components/common/mark.ts +++ /dev/null @@ -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; - } -} diff --git a/src/content/components/frame-content.ts b/src/content/components/frame-content.ts deleted file mode 100644 index ca999ba..0000000 --- a/src/content/components/frame-content.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CommonComponent from './common'; - -export default CommonComponent; diff --git a/src/content/components/top-content/follow-controller.ts b/src/content/components/top-content/follow-controller.ts deleted file mode 100644 index 43c917e..0000000 --- a/src/content/components/top-content/follow-controller.ts +++ /dev/null @@ -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)); - } -} diff --git a/src/content/components/top-content/index.ts b/src/content/components/top-content/index.ts deleted file mode 100644 index 0f07653..0000000 --- a/src/content/components/top-content/index.ts +++ /dev/null @@ -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); - } - } -} diff --git a/src/content/console-frames.ts b/src/content/console-frames.ts index bd6b835..b1b9bf6 100644 --- a/src/content/console-frames.ts +++ b/src/content/console-frames.ts @@ -1,5 +1,3 @@ -import * as messages from '../shared/messages'; - const initialize = (doc: Document): HTMLIFrameElement => { let iframe = doc.createElement('iframe'); iframe.src = browser.runtime.getURL('build/console.html'); @@ -15,24 +13,4 @@ const blur = (doc: Document) => { ele.blur(); }; -const postError = (text: string): Promise => { - return browser.runtime.sendMessage({ - type: messages.CONSOLE_FRAME_MESSAGE, - message: { - type: messages.CONSOLE_SHOW_ERROR, - text, - }, - }); -}; - -const postInfo = (text: string): Promise => { - return browser.runtime.sendMessage({ - type: messages.CONSOLE_FRAME_MESSAGE, - message: { - type: messages.CONSOLE_SHOW_INFO, - text, - }, - }); -}; - -export { initialize, blur, postError, postInfo }; +export { initialize, blur }; diff --git a/src/content/focuses.ts b/src/content/focuses.ts deleted file mode 100644 index 8f53881..0000000 --- a/src/content/focuses.ts +++ /dev/null @@ -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 }; diff --git a/src/content/index.ts b/src/content/index.ts index 08bdf6b..06bb34f 100644 --- a/src/content/index.ts +++ b/src/content/index.ts @@ -1,8 +1,5 @@ -// import TopContentComponent from './components/top-content'; -// import FrameContentComponent from './components/frame-content'; import * as consoleFrames from './console-frames'; import consoleFrameStyle from './site-style'; -// import { newStore } from './store'; import MessageListener from './MessageListener'; import FindController from './controllers/FindController'; import MarkController from './controllers/MarkController'; @@ -18,12 +15,8 @@ import * as blacklists from '../shared/blacklists'; import MarkKeyController from './controllers/MarkKeyController'; import AddonEnabledController from './controllers/AddonEnabledController'; -// const store = newStore(); - let listener = new MessageListener(); if (window.self === window.top) { - // new TopContentComponent(window, store); // eslint-disable-line no-new - let findController = new FindController(); let followMasterController = new FollowMasterController(); @@ -63,8 +56,6 @@ if (window.self === window.top) { }); consoleFrames.initialize(window.document); -} else { - // new FrameContentComponent(window, store); // eslint-disable-line no-new } let followSlaveController = new FollowSlaveController(); diff --git a/src/content/reducers/follow-controller.ts b/src/content/reducers/follow-controller.ts deleted file mode 100644 index 6965704..0000000 --- a/src/content/reducers/follow-controller.ts +++ /dev/null @@ -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; - } -} diff --git a/src/content/reducers/index.ts b/src/content/reducers/index.ts deleted file mode 100644 index 812a404..0000000 --- a/src/content/reducers/index.ts +++ /dev/null @@ -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, -}); diff --git a/src/content/reducers/input.ts b/src/content/reducers/input.ts deleted file mode 100644 index 800a8f0..0000000 --- a/src/content/reducers/input.ts +++ /dev/null @@ -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; - } -} diff --git a/src/content/reducers/mark.ts b/src/content/reducers/mark.ts deleted file mode 100644 index a8f2f1b..0000000 --- a/src/content/reducers/mark.ts +++ /dev/null @@ -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; - } -} diff --git a/src/content/store/index.ts b/src/content/store/index.ts deleted file mode 100644 index 5c41744..0000000 --- a/src/content/store/index.ts +++ /dev/null @@ -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), -); diff --git a/test/content/actions/follow-controller.test.ts b/test/content/actions/follow-controller.test.ts deleted file mode 100644 index a4b1710..0000000 --- a/test/content/actions/follow-controller.test.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/test/content/actions/input.test.ts b/test/content/actions/input.test.ts deleted file mode 100644 index 33238a5..0000000 --- a/test/content/actions/input.test.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/test/content/actions/mark.test.ts b/test/content/actions/mark.test.ts deleted file mode 100644 index f2df367..0000000 --- a/test/content/actions/mark.test.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/test/content/components/common/follow.html b/test/content/components/common/follow.html deleted file mode 100644 index b2a2d74..0000000 --- a/test/content/components/common/follow.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - link - invisible 1 - invisible 2 - not link -
link
-
link
-
link
-
- summary link - Some details - not visible -
- - diff --git a/test/content/components/common/follow.test.ts b/test/content/components/common/follow.test.ts deleted file mode 100644 index 90d6cf5..0000000 --- a/test/content/components/common/follow.test.ts +++ /dev/null @@ -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', - ]); - }); - }); -}); diff --git a/test/content/reducers/follow-controller.test.ts b/test/content/reducers/follow-controller.test.ts deleted file mode 100644 index 39f326c..0000000 --- a/test/content/reducers/follow-controller.test.ts +++ /dev/null @@ -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', ''); - }); -}); diff --git a/test/content/reducers/input.test.ts b/test/content/reducers/input.test.ts deleted file mode 100644 index f892201..0000000 --- a/test/content/reducers/input.test.ts +++ /dev/null @@ -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', []); - }); -}); diff --git a/test/content/reducers/mark.test.ts b/test/content/reducers/mark.test.ts deleted file mode 100644 index 918a560..0000000 --- a/test/content/reducers/mark.test.ts +++ /dev/null @@ -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; - }); -}); -- cgit v1.2.3