From 39fb5400370b818760dc7bcfe42e74b2512a840d Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Oct 2017 15:04:55 +0900 Subject: separate content --- src/actions/follow.js | 29 ------ src/actions/index.js | 20 ---- src/actions/input.js | 23 ----- src/actions/operation.js | 43 -------- src/components/content-input.js | 67 ------------- src/components/follow.js | 168 -------------------------------- src/components/keymapper.js | 43 -------- src/content/actions/follow.js | 29 ++++++ src/content/actions/index.js | 20 ++++ src/content/actions/input.js | 23 +++++ src/content/actions/operation.js | 43 ++++++++ src/content/components/content-input.js | 67 +++++++++++++ src/content/components/follow.js | 168 ++++++++++++++++++++++++++++++++ src/content/components/keymapper.js | 43 ++++++++ src/content/index.js | 8 +- src/content/reducers/follow.js | 32 ++++++ src/content/reducers/index.js | 18 ++++ src/content/reducers/input.js | 20 ++++ src/reducers/follow.js | 32 ------ src/reducers/index.js | 18 ---- src/reducers/input.js | 20 ---- test/actions/follow.test.js | 35 ------- test/actions/input.test.js | 26 ----- test/components/follow.html | 9 -- test/components/follow.test.js | 15 --- test/content/actions/follow.test.js | 35 +++++++ test/content/actions/input.test.js | 26 +++++ test/content/components/follow.html | 9 ++ test/content/components/follow.test.js | 15 +++ test/content/reducers/follow.test.js | 48 +++++++++ test/content/reducers/input.test.js | 26 +++++ test/reducers/follow.test.js | 48 --------- test/reducers/input.test.js | 26 ----- 33 files changed, 626 insertions(+), 626 deletions(-) delete mode 100644 src/actions/follow.js delete mode 100644 src/actions/index.js delete mode 100644 src/actions/input.js delete mode 100644 src/actions/operation.js delete mode 100644 src/components/content-input.js delete mode 100644 src/components/follow.js delete mode 100644 src/components/keymapper.js create mode 100644 src/content/actions/follow.js create mode 100644 src/content/actions/index.js create mode 100644 src/content/actions/input.js create mode 100644 src/content/actions/operation.js create mode 100644 src/content/components/content-input.js create mode 100644 src/content/components/follow.js create mode 100644 src/content/components/keymapper.js create mode 100644 src/content/reducers/follow.js create mode 100644 src/content/reducers/index.js create mode 100644 src/content/reducers/input.js delete mode 100644 src/reducers/follow.js delete mode 100644 src/reducers/index.js delete mode 100644 src/reducers/input.js delete mode 100644 test/actions/follow.test.js delete mode 100644 test/actions/input.test.js delete mode 100644 test/components/follow.html delete mode 100644 test/components/follow.test.js create mode 100644 test/content/actions/follow.test.js create mode 100644 test/content/actions/input.test.js create mode 100644 test/content/components/follow.html create mode 100644 test/content/components/follow.test.js create mode 100644 test/content/reducers/follow.test.js create mode 100644 test/content/reducers/input.test.js delete mode 100644 test/reducers/follow.test.js delete mode 100644 test/reducers/input.test.js diff --git a/src/actions/follow.js b/src/actions/follow.js deleted file mode 100644 index 708cd95..0000000 --- a/src/actions/follow.js +++ /dev/null @@ -1,29 +0,0 @@ -import actions from 'actions'; - -const enable = (newTab) => { - return { - type: actions.FOLLOW_ENABLE, - newTab, - }; -}; - -const disable = () => { - return { - type: actions.FOLLOW_DISABLE, - }; -}; - -const keyPress = (key) => { - return { - type: actions.FOLLOW_KEY_PRESS, - key: key - }; -}; - -const backspace = () => { - return { - type: actions.FOLLOW_BACKSPACE, - }; -}; - -export { enable, disable, keyPress, backspace }; diff --git a/src/actions/index.js b/src/actions/index.js deleted file mode 100644 index 0b3749d..0000000 --- a/src/actions/index.js +++ /dev/null @@ -1,20 +0,0 @@ -export default { - // User input - INPUT_KEY_PRESS: 'input.key,press', - INPUT_CLEAR_KEYS: 'input.clear.keys', - INPUT_SET_KEYMAPS: 'input.set,keymaps', - - // Completion - COMPLETION_SET_ITEMS: 'completion.set.items', - COMPLETION_SELECT_NEXT: 'completions.select.next', - COMPLETION_SELECT_PREV: 'completions.select.prev', - - // Settings - SETTING_SET_SETTINGS: 'setting.set.settings', - - // Follow - FOLLOW_ENABLE: 'follow.enable', - FOLLOW_DISABLE: 'follow.disable', - FOLLOW_KEY_PRESS: 'follow.key.press', - FOLLOW_BACKSPACE: 'follow.backspace', -}; diff --git a/src/actions/input.js b/src/actions/input.js deleted file mode 100644 index 61acb76..0000000 --- a/src/actions/input.js +++ /dev/null @@ -1,23 +0,0 @@ -import actions from 'actions'; - -const asKeymapChars = (key, ctrl) => { - if (ctrl) { - return ''; - } - return key; -}; - -const keyPress = (key, ctrl) => { - return { - type: actions.INPUT_KEY_PRESS, - key: asKeymapChars(key, ctrl), - }; -}; - -const clearKeys = () => { - return { - type: actions.INPUT_CLEAR_KEYS - }; -}; - -export { keyPress, clearKeys }; diff --git a/src/actions/operation.js b/src/actions/operation.js deleted file mode 100644 index a27cd02..0000000 --- a/src/actions/operation.js +++ /dev/null @@ -1,43 +0,0 @@ -import operations from 'shared/operations'; -import messages from 'shared/messages'; -import * as scrolls from 'content/scrolls'; -import * as navigates from 'content/navigates'; -import * as followActions from 'actions/follow'; - -const exec = (operation) => { - switch (operation.type) { - case operations.SCROLL_LINES: - return scrolls.scrollLines(window, operation.count); - case operations.SCROLL_PAGES: - return scrolls.scrollPages(window, operation.count); - case operations.SCROLL_TOP: - return scrolls.scrollTop(window); - case operations.SCROLL_BOTTOM: - return scrolls.scrollBottom(window); - case operations.SCROLL_HOME: - return scrolls.scrollLeft(window); - case operations.SCROLL_END: - return scrolls.scrollRight(window); - case operations.FOLLOW_START: - return followActions.enable(false); - case operations.NAVIGATE_HISTORY_PREV: - return navigates.historyPrev(window); - case operations.NAVIGATE_HISTORY_NEXT: - return navigates.historyNext(window); - case operations.NAVIGATE_LINK_PREV: - return navigates.linkPrev(window); - case operations.NAVIGATE_LINK_NEXT: - return navigates.linkNext(window); - case operations.NAVIGATE_PARENT: - return navigates.parent(window); - case operations.NAVIGATE_ROOT: - return navigates.root(window); - default: - browser.runtime.sendMessage({ - type: messages.BACKGROUND_OPERATION, - operation, - }); - } -}; - -export { exec }; diff --git a/src/components/content-input.js b/src/components/content-input.js deleted file mode 100644 index 9568caf..0000000 --- a/src/components/content-input.js +++ /dev/null @@ -1,67 +0,0 @@ -export default class ContentInputComponent { - constructor(target) { - this.pressed = {}; - this.onKeyListeners = []; - - target.addEventListener('keypress', this.onKeyPress.bind(this)); - target.addEventListener('keydown', this.onKeyDown.bind(this)); - target.addEventListener('keyup', this.onKeyUp.bind(this)); - } - - update() { - } - - onKey(cb) { - this.onKeyListeners.push(cb); - } - - onKeyPress(e) { - if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') { - return; - } - this.pressed[e.key] = 'keypress'; - this.capture(e); - } - - onKeyDown(e) { - if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') { - return; - } - this.pressed[e.key] = 'keydown'; - this.capture(e); - } - - onKeyUp(e) { - delete this.pressed[e.key]; - } - - capture(e) { - if (this.fromInput(e)) { - if (e.key === 'Escape' && e.target.blur) { - e.target.blur(); - } - return; - } - if (e.key === 'OS') { - return; - } - - let stop = false; - for (let listener of this.onKeyListeners) { - stop = stop || listener(e.key, e.ctrlKey); - if (stop) { - break; - } - } - if (stop) { - e.preventDefault(); - e.stopPropagation(); - } - } - - fromInput(e) { - return e.target instanceof HTMLInputElement || - e.target instanceof HTMLTextAreaElement || - e.target instanceof HTMLSelectElement; - } -} diff --git a/src/components/follow.js b/src/components/follow.js deleted file mode 100644 index 0ec1e87..0000000 --- a/src/components/follow.js +++ /dev/null @@ -1,168 +0,0 @@ -import * as followActions from 'actions/follow'; -import messages from 'shared/messages'; -import Hint from 'content/hint'; -import HintKeyProducer from 'content/hint-key-producer'; - -const DEFAULT_HINT_CHARSET = 'abcdefghijklmnopqrstuvwxyz'; - -const inWindow = (window, element) => { - let { - top, left, bottom, right - } = element.getBoundingClientRect(); - return ( - top >= 0 && left >= 0 && - bottom <= (window.innerHeight || document.documentElement.clientHeight) && - right <= (window.innerWidth || document.documentElement.clientWidth) - ); -}; - -export default class FollowComponent { - constructor(wrapper, store) { - this.wrapper = wrapper; - this.store = store; - this.hintElements = {}; - this.state = {}; - } - - update() { - let prevState = this.state; - this.state = this.store.getState().follow; - if (!prevState.enabled && this.state.enabled) { - this.create(); - } else if (prevState.enabled && !this.state.enabled) { - this.remove(); - } else if (prevState.keys !== this.state.keys) { - this.updateHints(); - } - } - - key(key) { - if (!this.state.enabled) { - return false; - } - - switch (key) { - case 'Enter': - this.activate(this.hintElements[this.state.keys].target); - return; - case 'Escape': - this.store.dispatch(followActions.disable()); - return; - case 'Backspace': - case 'Delete': - this.store.dispatch(followActions.backspace()); - break; - default: - if (DEFAULT_HINT_CHARSET.includes(key)) { - this.store.dispatch(followActions.keyPress(key)); - } - break; - } - return true; - } - - updateHints() { - let keys = this.state.keys; - let shown = Object.keys(this.hintElements).filter((key) => { - return key.startsWith(keys); - }); - let hidden = Object.keys(this.hintElements).filter((key) => { - return !key.startsWith(keys); - }); - if (shown.length === 0) { - this.remove(); - return; - } else if (shown.length === 1) { - this.activate(this.hintElements[keys].target); - this.store.dispatch(followActions.disable()); - } - - shown.forEach((key) => { - this.hintElements[key].show(); - }); - hidden.forEach((key) => { - this.hintElements[key].hide(); - }); - } - - activate(element) { - switch (element.tagName.toLowerCase()) { - case 'a': - if (this.state.newTab) { - // getAttribute() to avoid to resolve absolute path - let href = element.getAttribute('href'); - - // eslint-disable-next-line no-script-url - if (!href || href === '#' || href.startsWith('javascript:')) { - return; - } - return browser.runtime.sendMessage({ - type: messages.OPEN_URL, - url: element.href, - newTab: this.state.newTab, - }); - } - if (element.href.startsWith('http://') || - element.href.startsWith('https://') || - element.href.startsWith('ftp://')) { - return browser.runtime.sendMessage({ - type: messages.OPEN_URL, - url: element.href, - newTab: this.state.newTab, - }); - } - return element.click(); - case 'input': - switch (element.type) { - case 'file': - case 'checkbox': - case 'radio': - case 'submit': - case 'reset': - case 'button': - case 'image': - case 'color': - return element.click(); - default: - return element.focus(); - } - case 'textarea': - return element.focus(); - case 'button': - return element.click(); - } - } - - create() { - let doc = this.wrapper.ownerDocument; - let elements = FollowComponent.getTargetElements(doc); - let producer = new HintKeyProducer(DEFAULT_HINT_CHARSET); - let hintElements = {}; - Array.prototype.forEach.call(elements, (ele) => { - let keys = producer.produce(); - let hint = new Hint(ele, keys); - hintElements[keys] = hint; - }); - this.hintElements = hintElements; - } - - remove() { - let hintElements = this.hintElements; - Object.keys(this.hintElements).forEach((key) => { - hintElements[key].remove(); - }); - } - - static getTargetElements(doc) { - let all = doc.querySelectorAll('a,button,input,textarea'); - let filtered = Array.prototype.filter.call(all, (element) => { - let style = window.getComputedStyle(element); - return style.display !== 'none' && - style.visibility !== 'hidden' && - element.type !== 'hidden' && - element.offsetHeight > 0 && - inWindow(window, element); - }); - return filtered; - } -} diff --git a/src/components/keymapper.js b/src/components/keymapper.js deleted file mode 100644 index 3685a4f..0000000 --- a/src/components/keymapper.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as inputActions from 'actions/input'; -import * as operationActions from 'actions/operation'; - -export default class KeymapperComponent { - constructor(store) { - this.store = store; - } - - update() { - } - - key(key, ctrl) { - let keymaps = this.keymaps(); - if (!keymaps) { - return; - } - this.store.dispatch(inputActions.keyPress(key, ctrl)); - - let input = this.store.getState().input; - let matched = Object.keys(keymaps).filter((keyStr) => { - return keyStr.startsWith(input.keys); - }); - if (matched.length === 0) { - this.store.dispatch(inputActions.clearKeys()); - return false; - } else if (matched.length > 1 || - matched.length === 1 && input.keys !== matched[0]) { - return true; - } - let operation = keymaps[matched]; - this.store.dispatch(operationActions.exec(operation)); - this.store.dispatch(inputActions.clearKeys()); - return true; - } - - keymaps() { - let settings = this.store.getState().setting.settings; - if (!settings || !settings.json) { - return null; - } - return JSON.parse(settings.json).keymaps; - } -} diff --git a/src/content/actions/follow.js b/src/content/actions/follow.js new file mode 100644 index 0000000..5a18dd5 --- /dev/null +++ b/src/content/actions/follow.js @@ -0,0 +1,29 @@ +import actions from 'content/actions'; + +const enable = (newTab) => { + return { + type: actions.FOLLOW_ENABLE, + newTab, + }; +}; + +const disable = () => { + return { + type: actions.FOLLOW_DISABLE, + }; +}; + +const keyPress = (key) => { + return { + type: actions.FOLLOW_KEY_PRESS, + key: key + }; +}; + +const backspace = () => { + return { + type: actions.FOLLOW_BACKSPACE, + }; +}; + +export { enable, disable, keyPress, backspace }; diff --git a/src/content/actions/index.js b/src/content/actions/index.js new file mode 100644 index 0000000..0b3749d --- /dev/null +++ b/src/content/actions/index.js @@ -0,0 +1,20 @@ +export default { + // User input + INPUT_KEY_PRESS: 'input.key,press', + INPUT_CLEAR_KEYS: 'input.clear.keys', + INPUT_SET_KEYMAPS: 'input.set,keymaps', + + // Completion + COMPLETION_SET_ITEMS: 'completion.set.items', + COMPLETION_SELECT_NEXT: 'completions.select.next', + COMPLETION_SELECT_PREV: 'completions.select.prev', + + // Settings + SETTING_SET_SETTINGS: 'setting.set.settings', + + // Follow + FOLLOW_ENABLE: 'follow.enable', + FOLLOW_DISABLE: 'follow.disable', + FOLLOW_KEY_PRESS: 'follow.key.press', + FOLLOW_BACKSPACE: 'follow.backspace', +}; diff --git a/src/content/actions/input.js b/src/content/actions/input.js new file mode 100644 index 0000000..cc4efac --- /dev/null +++ b/src/content/actions/input.js @@ -0,0 +1,23 @@ +import actions from 'content/actions'; + +const asKeymapChars = (key, ctrl) => { + if (ctrl) { + return ''; + } + return key; +}; + +const keyPress = (key, ctrl) => { + return { + type: actions.INPUT_KEY_PRESS, + key: asKeymapChars(key, ctrl), + }; +}; + +const clearKeys = () => { + return { + type: actions.INPUT_CLEAR_KEYS + }; +}; + +export { keyPress, clearKeys }; diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js new file mode 100644 index 0000000..d188a60 --- /dev/null +++ b/src/content/actions/operation.js @@ -0,0 +1,43 @@ +import operations from 'shared/operations'; +import messages from 'shared/messages'; +import * as scrolls from 'content/scrolls'; +import * as navigates from 'content/navigates'; +import * as followActions from 'content/actions/follow'; + +const exec = (operation) => { + switch (operation.type) { + case operations.SCROLL_LINES: + return scrolls.scrollLines(window, operation.count); + case operations.SCROLL_PAGES: + return scrolls.scrollPages(window, operation.count); + case operations.SCROLL_TOP: + return scrolls.scrollTop(window); + case operations.SCROLL_BOTTOM: + return scrolls.scrollBottom(window); + case operations.SCROLL_HOME: + return scrolls.scrollLeft(window); + case operations.SCROLL_END: + return scrolls.scrollRight(window); + case operations.FOLLOW_START: + return followActions.enable(false); + case operations.NAVIGATE_HISTORY_PREV: + return navigates.historyPrev(window); + case operations.NAVIGATE_HISTORY_NEXT: + return navigates.historyNext(window); + case operations.NAVIGATE_LINK_PREV: + return navigates.linkPrev(window); + case operations.NAVIGATE_LINK_NEXT: + return navigates.linkNext(window); + case operations.NAVIGATE_PARENT: + return navigates.parent(window); + case operations.NAVIGATE_ROOT: + return navigates.root(window); + default: + browser.runtime.sendMessage({ + type: messages.BACKGROUND_OPERATION, + operation, + }); + } +}; + +export { exec }; diff --git a/src/content/components/content-input.js b/src/content/components/content-input.js new file mode 100644 index 0000000..9568caf --- /dev/null +++ b/src/content/components/content-input.js @@ -0,0 +1,67 @@ +export default class ContentInputComponent { + constructor(target) { + this.pressed = {}; + this.onKeyListeners = []; + + target.addEventListener('keypress', this.onKeyPress.bind(this)); + target.addEventListener('keydown', this.onKeyDown.bind(this)); + target.addEventListener('keyup', this.onKeyUp.bind(this)); + } + + update() { + } + + onKey(cb) { + this.onKeyListeners.push(cb); + } + + onKeyPress(e) { + if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') { + return; + } + this.pressed[e.key] = 'keypress'; + this.capture(e); + } + + onKeyDown(e) { + if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') { + return; + } + this.pressed[e.key] = 'keydown'; + this.capture(e); + } + + onKeyUp(e) { + delete this.pressed[e.key]; + } + + capture(e) { + if (this.fromInput(e)) { + if (e.key === 'Escape' && e.target.blur) { + e.target.blur(); + } + return; + } + if (e.key === 'OS') { + return; + } + + let stop = false; + for (let listener of this.onKeyListeners) { + stop = stop || listener(e.key, e.ctrlKey); + if (stop) { + break; + } + } + if (stop) { + e.preventDefault(); + e.stopPropagation(); + } + } + + fromInput(e) { + return e.target instanceof HTMLInputElement || + e.target instanceof HTMLTextAreaElement || + e.target instanceof HTMLSelectElement; + } +} diff --git a/src/content/components/follow.js b/src/content/components/follow.js new file mode 100644 index 0000000..c87424d --- /dev/null +++ b/src/content/components/follow.js @@ -0,0 +1,168 @@ +import * as followActions from 'content/actions/follow'; +import messages from 'shared/messages'; +import Hint from 'content/hint'; +import HintKeyProducer from 'content/hint-key-producer'; + +const DEFAULT_HINT_CHARSET = 'abcdefghijklmnopqrstuvwxyz'; + +const inWindow = (window, element) => { + let { + top, left, bottom, right + } = element.getBoundingClientRect(); + return ( + top >= 0 && left >= 0 && + bottom <= (window.innerHeight || document.documentElement.clientHeight) && + right <= (window.innerWidth || document.documentElement.clientWidth) + ); +}; + +export default class FollowComponent { + constructor(wrapper, store) { + this.wrapper = wrapper; + this.store = store; + this.hintElements = {}; + this.state = {}; + } + + update() { + let prevState = this.state; + this.state = this.store.getState().follow; + if (!prevState.enabled && this.state.enabled) { + this.create(); + } else if (prevState.enabled && !this.state.enabled) { + this.remove(); + } else if (prevState.keys !== this.state.keys) { + this.updateHints(); + } + } + + key(key) { + if (!this.state.enabled) { + return false; + } + + switch (key) { + case 'Enter': + this.activate(this.hintElements[this.state.keys].target); + return; + case 'Escape': + this.store.dispatch(followActions.disable()); + return; + case 'Backspace': + case 'Delete': + this.store.dispatch(followActions.backspace()); + break; + default: + if (DEFAULT_HINT_CHARSET.includes(key)) { + this.store.dispatch(followActions.keyPress(key)); + } + break; + } + return true; + } + + updateHints() { + let keys = this.state.keys; + let shown = Object.keys(this.hintElements).filter((key) => { + return key.startsWith(keys); + }); + let hidden = Object.keys(this.hintElements).filter((key) => { + return !key.startsWith(keys); + }); + if (shown.length === 0) { + this.remove(); + return; + } else if (shown.length === 1) { + this.activate(this.hintElements[keys].target); + this.store.dispatch(followActions.disable()); + } + + shown.forEach((key) => { + this.hintElements[key].show(); + }); + hidden.forEach((key) => { + this.hintElements[key].hide(); + }); + } + + activate(element) { + switch (element.tagName.toLowerCase()) { + case 'a': + if (this.state.newTab) { + // getAttribute() to avoid to resolve absolute path + let href = element.getAttribute('href'); + + // eslint-disable-next-line no-script-url + if (!href || href === '#' || href.startsWith('javascript:')) { + return; + } + return browser.runtime.sendMessage({ + type: messages.OPEN_URL, + url: element.href, + newTab: this.state.newTab, + }); + } + if (element.href.startsWith('http://') || + element.href.startsWith('https://') || + element.href.startsWith('ftp://')) { + return browser.runtime.sendMessage({ + type: messages.OPEN_URL, + url: element.href, + newTab: this.state.newTab, + }); + } + return element.click(); + case 'input': + switch (element.type) { + case 'file': + case 'checkbox': + case 'radio': + case 'submit': + case 'reset': + case 'button': + case 'image': + case 'color': + return element.click(); + default: + return element.focus(); + } + case 'textarea': + return element.focus(); + case 'button': + return element.click(); + } + } + + create() { + let doc = this.wrapper.ownerDocument; + let elements = FollowComponent.getTargetElements(doc); + let producer = new HintKeyProducer(DEFAULT_HINT_CHARSET); + let hintElements = {}; + Array.prototype.forEach.call(elements, (ele) => { + let keys = producer.produce(); + let hint = new Hint(ele, keys); + hintElements[keys] = hint; + }); + this.hintElements = hintElements; + } + + remove() { + let hintElements = this.hintElements; + Object.keys(this.hintElements).forEach((key) => { + hintElements[key].remove(); + }); + } + + static getTargetElements(doc) { + let all = doc.querySelectorAll('a,button,input,textarea'); + let filtered = Array.prototype.filter.call(all, (element) => { + let style = window.getComputedStyle(element); + return style.display !== 'none' && + style.visibility !== 'hidden' && + element.type !== 'hidden' && + element.offsetHeight > 0 && + inWindow(window, element); + }); + return filtered; + } +} diff --git a/src/content/components/keymapper.js b/src/content/components/keymapper.js new file mode 100644 index 0000000..8f2cead --- /dev/null +++ b/src/content/components/keymapper.js @@ -0,0 +1,43 @@ +import * as inputActions from 'content/actions/input'; +import * as operationActions from 'content/actions/operation'; + +export default class KeymapperComponent { + constructor(store) { + this.store = store; + } + + update() { + } + + key(key, ctrl) { + let keymaps = this.keymaps(); + if (!keymaps) { + return; + } + this.store.dispatch(inputActions.keyPress(key, ctrl)); + + let input = this.store.getState().input; + let matched = Object.keys(keymaps).filter((keyStr) => { + return keyStr.startsWith(input.keys); + }); + if (matched.length === 0) { + this.store.dispatch(inputActions.clearKeys()); + return false; + } else if (matched.length > 1 || + matched.length === 1 && input.keys !== matched[0]) { + return true; + } + let operation = keymaps[matched]; + this.store.dispatch(operationActions.exec(operation)); + this.store.dispatch(inputActions.clearKeys()); + return true; + } + + keymaps() { + let settings = this.store.getState().setting.settings; + if (!settings || !settings.json) { + return null; + } + return JSON.parse(settings.json).keymaps; + } +} diff --git a/src/content/index.js b/src/content/index.js index edca510..00873cc 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -2,10 +2,10 @@ import './console-frame.scss'; import * as consoleFrames from './console-frames'; import * as settingActions from 'settings/actions/setting'; import { createStore } from 'store'; -import ContentInputComponent from 'components/content-input'; -import KeymapperComponent from 'components/keymapper'; -import FollowComponent from 'components/follow'; -import reducers from 'reducers'; +import ContentInputComponent from 'content/components/content-input'; +import KeymapperComponent from 'content/components/keymapper'; +import FollowComponent from 'content/components/follow'; +import reducers from 'content/reducers'; import messages from 'shared/messages'; const store = createStore(reducers); diff --git a/src/content/reducers/follow.js b/src/content/reducers/follow.js new file mode 100644 index 0000000..b7c0cf3 --- /dev/null +++ b/src/content/reducers/follow.js @@ -0,0 +1,32 @@ +import actions from 'content/actions'; + +const defaultState = { + enabled: false, + newTab: false, + keys: '', +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.FOLLOW_ENABLE: + return Object.assign({}, state, { + enabled: true, + newTab: action.newTab, + keys: '', + }); + case actions.FOLLOW_DISABLE: + return Object.assign({}, state, { + enabled: false, + }); + case actions.FOLLOW_KEY_PRESS: + return Object.assign({}, state, { + keys: state.keys + action.key, + }); + case actions.FOLLOW_BACKSPACE: + return Object.assign({}, state, { + keys: state.keys.slice(0, -1), + }); + default: + return state; + } +} diff --git a/src/content/reducers/index.js b/src/content/reducers/index.js new file mode 100644 index 0000000..a62217f --- /dev/null +++ b/src/content/reducers/index.js @@ -0,0 +1,18 @@ +import settingReducer from 'settings/reducers/setting'; +import inputReducer from './input'; +import followReducer from './follow'; + +// Make setting reducer instead of re-use +const defaultState = { + input: inputReducer(undefined, {}), + setting: settingReducer(undefined, {}), + follow: followReducer(undefined, {}), +}; + +export default function reducer(state = defaultState, action = {}) { + return Object.assign({}, state, { + input: inputReducer(state.input, action), + setting: settingReducer(state.setting, action), + follow: followReducer(state.follow, action), + }); +} diff --git a/src/content/reducers/input.js b/src/content/reducers/input.js new file mode 100644 index 0000000..802020f --- /dev/null +++ b/src/content/reducers/input.js @@ -0,0 +1,20 @@ +import actions from 'content/actions'; + +const defaultState = { + keys: '', +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.INPUT_KEY_PRESS: + return Object.assign({}, state, { + keys: state.keys + action.key + }); + case actions.INPUT_CLEAR_KEYS: + return Object.assign({}, state, { + keys: '', + }); + default: + return state; + } +} diff --git a/src/reducers/follow.js b/src/reducers/follow.js deleted file mode 100644 index ed875e8..0000000 --- a/src/reducers/follow.js +++ /dev/null @@ -1,32 +0,0 @@ -import actions from 'actions'; - -const defaultState = { - enabled: false, - newTab: false, - keys: '', -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.FOLLOW_ENABLE: - return Object.assign({}, state, { - enabled: true, - newTab: action.newTab, - keys: '', - }); - case actions.FOLLOW_DISABLE: - return Object.assign({}, state, { - enabled: false, - }); - case actions.FOLLOW_KEY_PRESS: - return Object.assign({}, state, { - keys: state.keys + action.key, - }); - case actions.FOLLOW_BACKSPACE: - return Object.assign({}, state, { - keys: state.keys.slice(0, -1), - }); - default: - return state; - } -} diff --git a/src/reducers/index.js b/src/reducers/index.js deleted file mode 100644 index 9c86ebf..0000000 --- a/src/reducers/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import inputReducer from 'reducers/input'; -import settingReducer from 'settings/reducers/setting'; -import followReducer from 'reducers/follow'; - -// Make setting reducer instead of re-use -const defaultState = { - input: inputReducer(undefined, {}), - setting: settingReducer(undefined, {}), - follow: followReducer(undefined, {}), -}; - -export default function reducer(state = defaultState, action = {}) { - return Object.assign({}, state, { - input: inputReducer(state.input, action), - setting: settingReducer(state.setting, action), - follow: followReducer(state.follow, action), - }); -} diff --git a/src/reducers/input.js b/src/reducers/input.js deleted file mode 100644 index 2e4bcd8..0000000 --- a/src/reducers/input.js +++ /dev/null @@ -1,20 +0,0 @@ -import actions from 'actions'; - -const defaultState = { - keys: '', -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.INPUT_KEY_PRESS: - return Object.assign({}, state, { - keys: state.keys + action.key - }); - case actions.INPUT_CLEAR_KEYS: - return Object.assign({}, state, { - keys: '', - }); - default: - return state; - } -} diff --git a/test/actions/follow.test.js b/test/actions/follow.test.js deleted file mode 100644 index 32ab9e2..0000000 --- a/test/actions/follow.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import { expect } from "chai"; -import actions from 'actions'; -import * as followActions from 'actions/follow'; - -describe('follow actions', () => { - describe('enable', () => { - it('creates FOLLOW_ENABLE action', () => { - let action = followActions.enable(true); - expect(action.type).to.equal(actions.FOLLOW_ENABLE); - expect(action.newTab).to.equal(true); - }); - }); - - describe('disable', () => { - it('creates FOLLOW_DISABLE action', () => { - let action = followActions.disable(true); - expect(action.type).to.equal(actions.FOLLOW_DISABLE); - }); - }); - - describe('keyPress', () => { - it('creates FOLLOW_KEY_PRESS action', () => { - let action = followActions.keyPress(100); - expect(action.type).to.equal(actions.FOLLOW_KEY_PRESS); - expect(action.key).to.equal(100); - }); - }); - - describe('backspace', () => { - it('creates FOLLOW_BACKSPACE action', () => { - let action = followActions.backspace(100); - expect(action.type).to.equal(actions.FOLLOW_BACKSPACE); - }); - }); -}); diff --git a/test/actions/input.test.js b/test/actions/input.test.js deleted file mode 100644 index 0a2ab18..0000000 --- a/test/actions/input.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { expect } from "chai"; -import actions from 'actions'; -import * as inputActions from 'actions/input'; - -describe("input actions", () => { - describe("keyPress", () => { - it('create INPUT_KEY_PRESS action', () => { - let action = inputActions.keyPress('a', false); - expect(action.type).to.equal(actions.INPUT_KEY_PRESS); - expect(action.key).to.equal('a'); - }); - - it('create INPUT_KEY_PRESS action from key with ctrl', () => { - let action = inputActions.keyPress('b', true); - expect(action.type).to.equal(actions.INPUT_KEY_PRESS); - expect(action.key).to.equal(''); - }); - }); - - describe("clearKeys", () => { - it('create INPUT_CLEAR_KEYSaction', () => { - let action = inputActions.clearKeys(); - expect(action.type).to.equal(actions.INPUT_CLEAR_KEYS); - }); - }); -}); diff --git a/test/components/follow.html b/test/components/follow.html deleted file mode 100644 index 6bd8f87..0000000 --- a/test/components/follow.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - link - invisible 1 - invisible 2 - not link - - diff --git a/test/components/follow.test.js b/test/components/follow.test.js deleted file mode 100644 index 294bfc9..0000000 --- a/test/components/follow.test.js +++ /dev/null @@ -1,15 +0,0 @@ -import { expect } from "chai"; -import FollowComponent from 'components/follow'; - -describe('FollowComponent', () => { - describe('#getTargetElements', () => { - beforeEach(() => { - document.body.innerHTML = __html__['test/components/follow.html']; - }); - - it('returns visible links', () => { - let links = FollowComponent.getTargetElements(window.document); - expect(links).to.have.lengthOf(1); - }); - }); -}); diff --git a/test/content/actions/follow.test.js b/test/content/actions/follow.test.js new file mode 100644 index 0000000..3ac844c --- /dev/null +++ b/test/content/actions/follow.test.js @@ -0,0 +1,35 @@ +import { expect } from "chai"; +import actions from 'content/actions'; +import * as followActions from 'content/actions/follow'; + +describe('follow actions', () => { + describe('enable', () => { + it('creates FOLLOW_ENABLE action', () => { + let action = followActions.enable(true); + expect(action.type).to.equal(actions.FOLLOW_ENABLE); + expect(action.newTab).to.equal(true); + }); + }); + + describe('disable', () => { + it('creates FOLLOW_DISABLE action', () => { + let action = followActions.disable(true); + expect(action.type).to.equal(actions.FOLLOW_DISABLE); + }); + }); + + describe('keyPress', () => { + it('creates FOLLOW_KEY_PRESS action', () => { + let action = followActions.keyPress(100); + expect(action.type).to.equal(actions.FOLLOW_KEY_PRESS); + expect(action.key).to.equal(100); + }); + }); + + describe('backspace', () => { + it('creates FOLLOW_BACKSPACE action', () => { + let action = followActions.backspace(100); + expect(action.type).to.equal(actions.FOLLOW_BACKSPACE); + }); + }); +}); diff --git a/test/content/actions/input.test.js b/test/content/actions/input.test.js new file mode 100644 index 0000000..6031829 --- /dev/null +++ b/test/content/actions/input.test.js @@ -0,0 +1,26 @@ +import { expect } from "chai"; +import 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', false); + expect(action.type).to.equal(actions.INPUT_KEY_PRESS); + expect(action.key).to.equal('a'); + }); + + it('create INPUT_KEY_PRESS action from key with ctrl', () => { + let action = inputActions.keyPress('b', true); + expect(action.type).to.equal(actions.INPUT_KEY_PRESS); + expect(action.key).to.equal(''); + }); + }); + + 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/components/follow.html b/test/content/components/follow.html new file mode 100644 index 0000000..6bd8f87 --- /dev/null +++ b/test/content/components/follow.html @@ -0,0 +1,9 @@ + + + + link + invisible 1 + invisible 2 + not link + + diff --git a/test/content/components/follow.test.js b/test/content/components/follow.test.js new file mode 100644 index 0000000..9c00c79 --- /dev/null +++ b/test/content/components/follow.test.js @@ -0,0 +1,15 @@ +import { expect } from "chai"; +import FollowComponent from 'content/components/follow'; + +describe('FollowComponent', () => { + describe('#getTargetElements', () => { + beforeEach(() => { + document.body.innerHTML = __html__['test/content/components/follow.html']; + }); + + it('returns visible links', () => { + let links = FollowComponent.getTargetElements(window.document); + expect(links).to.have.lengthOf(1); + }); + }); +}); diff --git a/test/content/reducers/follow.test.js b/test/content/reducers/follow.test.js new file mode 100644 index 0000000..e2b3445 --- /dev/null +++ b/test/content/reducers/follow.test.js @@ -0,0 +1,48 @@ +import { expect } from "chai"; +import actions from 'content/actions'; +import followReducer from 'content/reducers/follow'; + +describe('follow reducer', () => { + it ('returns the initial state', () => { + let state = followReducer(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_ENABLE', () => { + let action = { type: actions.FOLLOW_ENABLE, newTab: true }; + let state = followReducer({ 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_DISABLE', () => { + let action = { type: actions.FOLLOW_DISABLE }; + let state = followReducer({ enabled: true }, action); + expect(state).to.have.property('enabled', false); + }); + + it ('returns next state for FOLLOW_KEY_PRESS', () => { + let action = { type: actions.FOLLOW_KEY_PRESS, key: 'a'}; + let state = followReducer({ keys: '' }, action); + expect(state).to.have.deep.property('keys', 'a'); + + action = { type: actions.FOLLOW_KEY_PRESS, key: 'b'}; + state = followReducer(state, action); + expect(state).to.have.deep.property('keys', 'ab'); + }); + + it ('returns next state for FOLLOW_BACKSPACE', () => { + let action = { type: actions.FOLLOW_BACKSPACE }; + let state = followReducer({ keys: 'ab' }, action); + expect(state).to.have.deep.property('keys', 'a'); + + state = followReducer(state, action); + expect(state).to.have.deep.property('keys', ''); + + state = followReducer(state, action); + expect(state).to.have.deep.property('keys', ''); + }); +}); diff --git a/test/content/reducers/input.test.js b/test/content/reducers/input.test.js new file mode 100644 index 0000000..d5e5f6b --- /dev/null +++ b/test/content/reducers/input.test.js @@ -0,0 +1,26 @@ +import { expect } from "chai"; +import 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: '' }; + state = inputReducer(state, action); + expect(state).to.have.deep.property('keys', 'a'); + }); + + it('return next state for INPUT_CLEAR_KEYS', () => { + let action = { type: actions.INPUT_CLEAR_KEYS }; + let state = inputReducer({ keys: 'abc' }, action); + expect(state).to.have.deep.property('keys', ''); + }); +}); diff --git a/test/reducers/follow.test.js b/test/reducers/follow.test.js deleted file mode 100644 index e1db680..0000000 --- a/test/reducers/follow.test.js +++ /dev/null @@ -1,48 +0,0 @@ -import { expect } from "chai"; -import actions from 'actions'; -import followReducer from 'reducers/follow'; - -describe('follow reducer', () => { - it ('returns the initial state', () => { - let state = followReducer(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_ENABLE', () => { - let action = { type: actions.FOLLOW_ENABLE, newTab: true }; - let state = followReducer({ 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_DISABLE', () => { - let action = { type: actions.FOLLOW_DISABLE }; - let state = followReducer({ enabled: true }, action); - expect(state).to.have.property('enabled', false); - }); - - it ('returns next state for FOLLOW_KEY_PRESS', () => { - let action = { type: actions.FOLLOW_KEY_PRESS, key: 'a'}; - let state = followReducer({ keys: '' }, action); - expect(state).to.have.deep.property('keys', 'a'); - - action = { type: actions.FOLLOW_KEY_PRESS, key: 'b'}; - state = followReducer(state, action); - expect(state).to.have.deep.property('keys', 'ab'); - }); - - it ('returns next state for FOLLOW_BACKSPACE', () => { - let action = { type: actions.FOLLOW_BACKSPACE }; - let state = followReducer({ keys: 'ab' }, action); - expect(state).to.have.deep.property('keys', 'a'); - - state = followReducer(state, action); - expect(state).to.have.deep.property('keys', ''); - - state = followReducer(state, action); - expect(state).to.have.deep.property('keys', ''); - }); -}); diff --git a/test/reducers/input.test.js b/test/reducers/input.test.js deleted file mode 100644 index 7b5a89c..0000000 --- a/test/reducers/input.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { expect } from "chai"; -import actions from 'actions'; -import inputReducer from '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: '' }; - state = inputReducer(state, action); - expect(state).to.have.deep.property('keys', 'a'); - }); - - it('return next state for INPUT_CLEAR_KEYS', () => { - let action = { type: actions.INPUT_CLEAR_KEYS }; - let state = inputReducer({ keys: 'abc' }, action); - expect(state).to.have.deep.property('keys', ''); - }); -}); -- cgit v1.2.3