diff options
author | Shin'ya Ueoka <ueokande@i-beam.org> | 2017-10-02 21:35:52 +0900 |
---|---|---|
committer | Shin'ya Ueoka <ueokande@i-beam.org> | 2017-10-02 21:38:23 +0900 |
commit | 0a7ae631cd72100abdc26b84b06006bb5b166cba (patch) | |
tree | 3bef6ca26a12e3b3cbedb3d1dbb9d0a39a7fd466 /src/content | |
parent | 6f857e2c81b2d31a86d39c02b010345d3ff23a38 (diff) |
follow as redux
Diffstat (limited to 'src/content')
-rw-r--r-- | src/content/follow.js | 149 | ||||
-rw-r--r-- | src/content/index.js | 68 |
2 files changed, 15 insertions, 202 deletions
diff --git a/src/content/follow.js b/src/content/follow.js deleted file mode 100644 index b1d2f5c..0000000 --- a/src/content/follow.js +++ /dev/null @@ -1,149 +0,0 @@ -import Hint from './hint'; -import HintKeyProducer from './hint-key-producer'; - -const DEFAULT_HINT_CHARSET = 'abcdefghijklmnopqrstuvwxyz'; - -export default class Follow { - constructor(doc) { - this.doc = doc; - this.hintElements = {}; - this.keys = []; - this.onActivatedCallbacks = []; - - let links = Follow.getTargetElements(doc); - - this.addHints(links); - - this.boundKeydown = this.handleKeydown.bind(this); - doc.addEventListener('keydown', this.boundKeydown); - } - - addHints(elements) { - let producer = new HintKeyProducer(DEFAULT_HINT_CHARSET); - Array.prototype.forEach.call(elements, (ele) => { - let keys = producer.produce(); - let hint = new Hint(ele, keys); - - this.hintElements[keys] = hint; - }); - } - - handleKeydown(e) { - let { keyCode } = e; - if (keyCode === KeyboardEvent.DOM_VK_ESCAPE) { - this.remove(); - return; - } else if (keyCode === KeyboardEvent.DOM_VK_ENTER || - keyCode === KeyboardEvent.DOM_VK_RETURN) { - let chars = Follow.codeChars(this.keys); - this.activate(this.hintElements[chars].target); - return; - } else if (Follow.availableKey(keyCode)) { - this.keys.push(keyCode); - } else if (keyCode === KeyboardEvent.DOM_VK_BACK_SPACE || - keyCode === KeyboardEvent.DOM_VK_DELETE) { - this.keys.pop(); - } - - e.stopPropagation(); - e.preventDefault(); - - this.refreshKeys(); - } - - refreshKeys() { - let chars = Follow.codeChars(this.keys); - let shown = Object.keys(this.hintElements).filter((key) => { - return key.startsWith(chars); - }); - let hidden = Object.keys(this.hintElements).filter((key) => { - return !key.startsWith(chars); - }); - if (shown.length === 0) { - this.remove(); - return; - } else if (shown.length === 1) { - this.remove(); - this.activate(this.hintElements[chars].target); - } - - shown.forEach((key) => { - this.hintElements[key].show(); - }); - hidden.forEach((key) => { - this.hintElements[key].hide(); - }); - } - - remove() { - this.doc.removeEventListener('keydown', this.boundKeydown); - Object.keys(this.hintElements).forEach((key) => { - this.hintElements[key].remove(); - }); - } - - activate(element) { - this.onActivatedCallbacks.forEach(f => f(element)); - } - - onActivated(f) { - this.onActivatedCallbacks.push(f); - } - - static availableKey(keyCode) { - return ( - KeyboardEvent.DOM_VK_0 <= keyCode && keyCode <= KeyboardEvent.DOM_VK_9 || - KeyboardEvent.DOM_VK_A <= keyCode && keyCode <= KeyboardEvent.DOM_VK_Z - ); - } - - static isNumericKey(code) { - return KeyboardEvent.DOM_VK_0 <= code && code <= KeyboardEvent.DOM_VK_9; - } - - static isAlphabeticKey(code) { - return KeyboardEvent.DOM_VK_A <= code && code <= KeyboardEvent.DOM_VK_Z; - } - - static codeChars(codes) { - const CHARCODE_ZERO = '0'.charCodeAt(0); - const CHARCODE_A = 'a'.charCodeAt(0); - - let chars = ''; - - for (let code of codes) { - if (Follow.isNumericKey(code)) { - chars += String.fromCharCode( - code - KeyboardEvent.DOM_VK_0 + CHARCODE_ZERO); - } else if (Follow.isAlphabeticKey(code)) { - chars += String.fromCharCode( - code - KeyboardEvent.DOM_VK_A + CHARCODE_A); - } - } - return chars; - } - - static 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) - ); - } - - 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 && - Follow.inWindow(window, element); - }); - return filtered; - } -} diff --git a/src/content/index.js b/src/content/index.js index 2e64af2..0dbc8c1 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -2,62 +2,24 @@ import './console-frame.scss'; import * as consoleFrames from './console-frames'; import * as scrolls from '../content/scrolls'; import * as navigates from '../content/navigates'; -import Follow from '../content/follow'; +import * as followActions from '../actions/follow'; +import * as store from '../store'; +import FollowComponent from '../components/follow'; +import followReducer from '../reducers/follow'; import operations from '../operations'; import messages from './messages'; -consoleFrames.initialize(window.document); - -const startFollows = (newTab) => { - let follow = new Follow(window.document); - follow.onActivated((element) => { - switch (element.tagName.toLowerCase()) { - case 'a': - if (newTab) { - // getAttribute() to avoid to resolve absolute path - let href = element.getAttribute('href'); +const followStore = store.createStore(followReducer); +const followComponent = new FollowComponent(window.document.body, followStore); +followStore.subscribe(() => { + try { + followComponent.update(); + } catch (e) { + console.error(e); + } +}); - // 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 - }); - } - 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 - }); - } - 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(); - } - }); -}; +consoleFrames.initialize(window.document); window.addEventListener('keypress', (e) => { if (e.target instanceof HTMLInputElement || @@ -90,7 +52,7 @@ const execOperation = (operation) => { case operations.SCROLL_END: return scrolls.scrollRight(window); case operations.FOLLOW_START: - return startFollows(operation.newTab); + return followStore.dispatch(followActions.enable(false)); case operations.NAVIGATE_HISTORY_PREV: return navigates.historyPrev(window); case operations.NAVIGATE_HISTORY_NEXT: |