From c60d0e7392fc708e961614d6b756a045de74f458 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 30 Apr 2019 14:00:07 +0900 Subject: Rename .js/.jsx to .ts/.tsx --- src/content/components/common/follow.js | 200 ----------------------------- src/content/components/common/follow.ts | 200 +++++++++++++++++++++++++++++ src/content/components/common/hint.js | 49 ------- src/content/components/common/hint.ts | 49 +++++++ src/content/components/common/index.js | 55 -------- src/content/components/common/index.ts | 55 ++++++++ src/content/components/common/input.js | 75 ----------- src/content/components/common/input.ts | 75 +++++++++++ src/content/components/common/keymapper.js | 58 --------- src/content/components/common/keymapper.ts | 58 +++++++++ src/content/components/common/mark.js | 74 ----------- src/content/components/common/mark.ts | 74 +++++++++++ 12 files changed, 511 insertions(+), 511 deletions(-) delete mode 100644 src/content/components/common/follow.js create mode 100644 src/content/components/common/follow.ts delete mode 100644 src/content/components/common/hint.js create mode 100644 src/content/components/common/hint.ts delete mode 100644 src/content/components/common/index.js create mode 100644 src/content/components/common/index.ts delete mode 100644 src/content/components/common/input.js create mode 100644 src/content/components/common/input.ts delete mode 100644 src/content/components/common/keymapper.js create mode 100644 src/content/components/common/keymapper.ts delete mode 100644 src/content/components/common/mark.js create mode 100644 src/content/components/common/mark.ts (limited to 'src/content/components/common') diff --git a/src/content/components/common/follow.js b/src/content/components/common/follow.js deleted file mode 100644 index 63ce603..0000000 --- a/src/content/components/common/follow.js +++ /dev/null @@ -1,200 +0,0 @@ -import messages from 'shared/messages'; -import Hint from './hint'; -import * as dom from 'shared/utils/dom'; - -const TARGET_SELECTOR = [ - 'a', 'button', 'input', 'textarea', 'area', - '[contenteditable=true]', '[contenteditable=""]', '[tabindex]', - '[role="button"]', 'summary' -].join(','); - - -const inViewport = (win, element, viewSize, framePosition) => { - let { - top, left, bottom, right - } = dom.viewportRect(element); - let doc = win.document; - let frameWidth = doc.documentElement.clientWidth; - let frameHeight = doc.documentElement.clientHeight; - - if (right < 0 || bottom < 0 || top > frameHeight || left > frameWidth) { - // out of frame - return false; - } - if (right + framePosition.x < 0 || bottom + framePosition.y < 0 || - left + framePosition.x > viewSize.width || - top + framePosition.y > viewSize.height) { - // out of viewport - return false; - } - return true; -}; - -const isAriaHiddenOrAriaDisabled = (win, element) => { - if (!element || win.document.documentElement === element) { - return false; - } - for (let attr of ['aria-hidden', 'aria-disabled']) { - if (element.hasAttribute(attr)) { - let hidden = element.getAttribute(attr).toLowerCase(); - if (hidden === '' || hidden === 'true') { - return true; - } - } - } - return isAriaHiddenOrAriaDisabled(win, element.parentNode); -}; - -export default class Follow { - constructor(win, store) { - this.win = win; - this.store = store; - this.newTab = false; - this.background = false; - this.hints = {}; - this.targets = []; - - messages.onMessage(this.onMessage.bind(this)); - } - - key(key) { - if (Object.keys(this.hints).length === 0) { - return false; - } - this.win.parent.postMessage(JSON.stringify({ - type: messages.FOLLOW_KEY_PRESS, - key: key.key, - ctrlKey: key.ctrlKey, - }), '*'); - return true; - } - - openLink(element) { - // Browser prevent new tab by link with target='_blank' - if (!this.newTab && element.getAttribute('target') !== '_blank') { - element.click(); - return; - } - - let href = element.getAttribute('href'); - - // eslint-disable-next-line no-script-url - if (!href || href === '#' || href.toLowerCase().startsWith('javascript:')) { - return; - } - return browser.runtime.sendMessage({ - type: messages.OPEN_URL, - url: element.href, - newTab: true, - background: this.background, - }); - } - - countHints(sender, viewSize, framePosition) { - this.targets = Follow.getTargetElements(this.win, viewSize, framePosition); - sender.postMessage(JSON.stringify({ - type: messages.FOLLOW_RESPONSE_COUNT_TARGETS, - count: this.targets.length, - }), '*'); - } - - createHints(keysArray, newTab, background) { - if (keysArray.length !== this.targets.length) { - throw new Error('illegal hint count'); - } - - this.newTab = newTab; - this.background = background; - this.hints = {}; - for (let i = 0; i < keysArray.length; ++i) { - let keys = keysArray[i]; - let hint = new Hint(this.targets[i], keys); - this.hints[keys] = hint; - } - } - - showHints(keys) { - Object.keys(this.hints).filter(key => key.startsWith(keys)) - .forEach(key => this.hints[key].show()); - Object.keys(this.hints).filter(key => !key.startsWith(keys)) - .forEach(key => this.hints[key].hide()); - } - - removeHints() { - Object.keys(this.hints).forEach((key) => { - this.hints[key].remove(); - }); - this.hints = {}; - this.targets = []; - } - - activateHints(keys) { - let hint = this.hints[keys]; - if (!hint) { - return; - } - let element = hint.target; - switch (element.tagName.toLowerCase()) { - case 'a': - case 'area': - return this.openLink(element); - 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': - case 'summary': - return element.click(); - default: - if (dom.isContentEditable(element)) { - return element.focus(); - } else if (element.hasAttribute('tabindex')) { - return element.click(); - } - } - } - - onMessage(message, sender) { - switch (message.type) { - case messages.FOLLOW_REQUEST_COUNT_TARGETS: - return this.countHints(sender, message.viewSize, message.framePosition); - case messages.FOLLOW_CREATE_HINTS: - return this.createHints( - message.keysArray, message.newTab, message.background); - case messages.FOLLOW_SHOW_HINTS: - return this.showHints(message.keys); - case messages.FOLLOW_ACTIVATE: - return this.activateHints(message.keys); - case messages.FOLLOW_REMOVE_HINTS: - return this.removeHints(message.keys); - } - } - - static getTargetElements(win, viewSize, framePosition) { - let all = win.document.querySelectorAll(TARGET_SELECTOR); - let filtered = Array.prototype.filter.call(all, (element) => { - let style = win.getComputedStyle(element); - - // AREA's 'display' in Browser style is 'none' - return (element.tagName === 'AREA' || style.display !== 'none') && - style.visibility !== 'hidden' && - element.type !== 'hidden' && - element.offsetHeight > 0 && - !isAriaHiddenOrAriaDisabled(win, element) && - inViewport(win, element, viewSize, framePosition); - }); - return filtered; - } -} diff --git a/src/content/components/common/follow.ts b/src/content/components/common/follow.ts new file mode 100644 index 0000000..63ce603 --- /dev/null +++ b/src/content/components/common/follow.ts @@ -0,0 +1,200 @@ +import messages from 'shared/messages'; +import Hint from './hint'; +import * as dom from 'shared/utils/dom'; + +const TARGET_SELECTOR = [ + 'a', 'button', 'input', 'textarea', 'area', + '[contenteditable=true]', '[contenteditable=""]', '[tabindex]', + '[role="button"]', 'summary' +].join(','); + + +const inViewport = (win, element, viewSize, framePosition) => { + let { + top, left, bottom, right + } = dom.viewportRect(element); + let doc = win.document; + let frameWidth = doc.documentElement.clientWidth; + let frameHeight = doc.documentElement.clientHeight; + + if (right < 0 || bottom < 0 || top > frameHeight || left > frameWidth) { + // out of frame + return false; + } + if (right + framePosition.x < 0 || bottom + framePosition.y < 0 || + left + framePosition.x > viewSize.width || + top + framePosition.y > viewSize.height) { + // out of viewport + return false; + } + return true; +}; + +const isAriaHiddenOrAriaDisabled = (win, element) => { + if (!element || win.document.documentElement === element) { + return false; + } + for (let attr of ['aria-hidden', 'aria-disabled']) { + if (element.hasAttribute(attr)) { + let hidden = element.getAttribute(attr).toLowerCase(); + if (hidden === '' || hidden === 'true') { + return true; + } + } + } + return isAriaHiddenOrAriaDisabled(win, element.parentNode); +}; + +export default class Follow { + constructor(win, store) { + this.win = win; + this.store = store; + this.newTab = false; + this.background = false; + this.hints = {}; + this.targets = []; + + messages.onMessage(this.onMessage.bind(this)); + } + + key(key) { + if (Object.keys(this.hints).length === 0) { + return false; + } + this.win.parent.postMessage(JSON.stringify({ + type: messages.FOLLOW_KEY_PRESS, + key: key.key, + ctrlKey: key.ctrlKey, + }), '*'); + return true; + } + + openLink(element) { + // Browser prevent new tab by link with target='_blank' + if (!this.newTab && element.getAttribute('target') !== '_blank') { + element.click(); + return; + } + + let href = element.getAttribute('href'); + + // eslint-disable-next-line no-script-url + if (!href || href === '#' || href.toLowerCase().startsWith('javascript:')) { + return; + } + return browser.runtime.sendMessage({ + type: messages.OPEN_URL, + url: element.href, + newTab: true, + background: this.background, + }); + } + + countHints(sender, viewSize, framePosition) { + this.targets = Follow.getTargetElements(this.win, viewSize, framePosition); + sender.postMessage(JSON.stringify({ + type: messages.FOLLOW_RESPONSE_COUNT_TARGETS, + count: this.targets.length, + }), '*'); + } + + createHints(keysArray, newTab, background) { + if (keysArray.length !== this.targets.length) { + throw new Error('illegal hint count'); + } + + this.newTab = newTab; + this.background = background; + this.hints = {}; + for (let i = 0; i < keysArray.length; ++i) { + let keys = keysArray[i]; + let hint = new Hint(this.targets[i], keys); + this.hints[keys] = hint; + } + } + + showHints(keys) { + Object.keys(this.hints).filter(key => key.startsWith(keys)) + .forEach(key => this.hints[key].show()); + Object.keys(this.hints).filter(key => !key.startsWith(keys)) + .forEach(key => this.hints[key].hide()); + } + + removeHints() { + Object.keys(this.hints).forEach((key) => { + this.hints[key].remove(); + }); + this.hints = {}; + this.targets = []; + } + + activateHints(keys) { + let hint = this.hints[keys]; + if (!hint) { + return; + } + let element = hint.target; + switch (element.tagName.toLowerCase()) { + case 'a': + case 'area': + return this.openLink(element); + 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': + case 'summary': + return element.click(); + default: + if (dom.isContentEditable(element)) { + return element.focus(); + } else if (element.hasAttribute('tabindex')) { + return element.click(); + } + } + } + + onMessage(message, sender) { + switch (message.type) { + case messages.FOLLOW_REQUEST_COUNT_TARGETS: + return this.countHints(sender, message.viewSize, message.framePosition); + case messages.FOLLOW_CREATE_HINTS: + return this.createHints( + message.keysArray, message.newTab, message.background); + case messages.FOLLOW_SHOW_HINTS: + return this.showHints(message.keys); + case messages.FOLLOW_ACTIVATE: + return this.activateHints(message.keys); + case messages.FOLLOW_REMOVE_HINTS: + return this.removeHints(message.keys); + } + } + + static getTargetElements(win, viewSize, framePosition) { + let all = win.document.querySelectorAll(TARGET_SELECTOR); + let filtered = Array.prototype.filter.call(all, (element) => { + let style = win.getComputedStyle(element); + + // AREA's 'display' in Browser style is 'none' + return (element.tagName === 'AREA' || style.display !== 'none') && + style.visibility !== 'hidden' && + element.type !== 'hidden' && + element.offsetHeight > 0 && + !isAriaHiddenOrAriaDisabled(win, element) && + inViewport(win, element, viewSize, framePosition); + }); + return filtered; + } +} diff --git a/src/content/components/common/hint.js b/src/content/components/common/hint.js deleted file mode 100644 index 1472587..0000000 --- a/src/content/components/common/hint.js +++ /dev/null @@ -1,49 +0,0 @@ -import * as dom from 'shared/utils/dom'; - -const hintPosition = (element) => { - let { left, top, right, bottom } = dom.viewportRect(element); - - if (element.tagName !== 'AREA') { - return { x: left, y: top }; - } - - return { - x: (left + right) / 2, - y: (top + bottom) / 2, - }; -}; - -export default class Hint { - constructor(target, tag) { - if (!(document.body instanceof HTMLElement)) { - throw new TypeError('target is not an HTMLElement'); - } - - this.target = target; - - let doc = target.ownerDocument; - let { x, y } = hintPosition(target); - let { scrollX, scrollY } = window; - - this.element = doc.createElement('span'); - this.element.className = 'vimvixen-hint'; - this.element.textContent = tag; - this.element.style.left = x + scrollX + 'px'; - this.element.style.top = y + scrollY + 'px'; - - this.show(); - doc.body.append(this.element); - } - - show() { - this.element.style.display = 'inline'; - } - - hide() { - this.element.style.display = 'none'; - } - - remove() { - this.element.remove(); - } -} diff --git a/src/content/components/common/hint.ts b/src/content/components/common/hint.ts new file mode 100644 index 0000000..1472587 --- /dev/null +++ b/src/content/components/common/hint.ts @@ -0,0 +1,49 @@ +import * as dom from 'shared/utils/dom'; + +const hintPosition = (element) => { + let { left, top, right, bottom } = dom.viewportRect(element); + + if (element.tagName !== 'AREA') { + return { x: left, y: top }; + } + + return { + x: (left + right) / 2, + y: (top + bottom) / 2, + }; +}; + +export default class Hint { + constructor(target, tag) { + if (!(document.body instanceof HTMLElement)) { + throw new TypeError('target is not an HTMLElement'); + } + + this.target = target; + + let doc = target.ownerDocument; + let { x, y } = hintPosition(target); + let { scrollX, scrollY } = window; + + this.element = doc.createElement('span'); + this.element.className = 'vimvixen-hint'; + this.element.textContent = tag; + this.element.style.left = x + scrollX + 'px'; + this.element.style.top = y + scrollY + 'px'; + + this.show(); + doc.body.append(this.element); + } + + show() { + this.element.style.display = 'inline'; + } + + hide() { + this.element.style.display = 'none'; + } + + remove() { + this.element.remove(); + } +} diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js deleted file mode 100644 index bcab4fa..0000000 --- a/src/content/components/common/index.js +++ /dev/null @@ -1,55 +0,0 @@ -import InputComponent from './input'; -import FollowComponent from './follow'; -import MarkComponent from './mark'; -import KeymapperComponent from './keymapper'; -import * as settingActions from 'content/actions/setting'; -import messages from 'shared/messages'; -import * as addonActions from '../../actions/addon'; -import * as blacklists from 'shared/blacklists'; - -export default class Common { - constructor(win, store) { - const input = new InputComponent(win.document.body, store); - const follow = new FollowComponent(win, store); - const mark = new MarkComponent(win.document.body, store); - const keymapper = new KeymapperComponent(store); - - input.onKey(key => follow.key(key)); - input.onKey(key => mark.key(key)); - input.onKey(key => keymapper.key(key)); - - this.win = win; - this.store = store; - this.prevEnabled = undefined; - this.prevBlacklist = undefined; - - this.reloadSettings(); - - messages.onMessage(this.onMessage.bind(this)); - } - - onMessage(message) { - let { enabled } = this.store.getState().addon; - switch (message.type) { - case messages.SETTINGS_CHANGED: - return this.reloadSettings(); - case messages.ADDON_TOGGLE_ENABLED: - this.store.dispatch(addonActions.setEnabled(!enabled)); - } - } - - reloadSettings() { - try { - this.store.dispatch(settingActions.load()).then(({ value: settings }) => { - let enabled = !blacklists.includes( - settings.blacklist, this.win.location.href - ); - this.store.dispatch(addonActions.setEnabled(enabled)); - }); - } catch (e) { - // Sometime sendMessage fails when background script is not ready. - console.warn(e); - setTimeout(() => this.reloadSettings(), 500); - } - } -} diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts new file mode 100644 index 0000000..bcab4fa --- /dev/null +++ b/src/content/components/common/index.ts @@ -0,0 +1,55 @@ +import InputComponent from './input'; +import FollowComponent from './follow'; +import MarkComponent from './mark'; +import KeymapperComponent from './keymapper'; +import * as settingActions from 'content/actions/setting'; +import messages from 'shared/messages'; +import * as addonActions from '../../actions/addon'; +import * as blacklists from 'shared/blacklists'; + +export default class Common { + constructor(win, store) { + const input = new InputComponent(win.document.body, store); + const follow = new FollowComponent(win, store); + const mark = new MarkComponent(win.document.body, store); + const keymapper = new KeymapperComponent(store); + + input.onKey(key => follow.key(key)); + input.onKey(key => mark.key(key)); + input.onKey(key => keymapper.key(key)); + + this.win = win; + this.store = store; + this.prevEnabled = undefined; + this.prevBlacklist = undefined; + + this.reloadSettings(); + + messages.onMessage(this.onMessage.bind(this)); + } + + onMessage(message) { + let { enabled } = this.store.getState().addon; + switch (message.type) { + case messages.SETTINGS_CHANGED: + return this.reloadSettings(); + case messages.ADDON_TOGGLE_ENABLED: + this.store.dispatch(addonActions.setEnabled(!enabled)); + } + } + + reloadSettings() { + try { + this.store.dispatch(settingActions.load()).then(({ value: settings }) => { + let enabled = !blacklists.includes( + settings.blacklist, this.win.location.href + ); + this.store.dispatch(addonActions.setEnabled(enabled)); + }); + } catch (e) { + // Sometime sendMessage fails when background script is not ready. + console.warn(e); + setTimeout(() => this.reloadSettings(), 500); + } + } +} diff --git a/src/content/components/common/input.js b/src/content/components/common/input.js deleted file mode 100644 index eefaf10..0000000 --- a/src/content/components/common/input.js +++ /dev/null @@ -1,75 +0,0 @@ -import * as dom from 'shared/utils/dom'; -import * as keys from 'shared/utils/keys'; - -const cancelKey = (e) => { - return e.key === 'Escape' || e.key === '[' && e.ctrlKey; -}; - -export default class InputComponent { - 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)); - } - - 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 (cancelKey(e) && e.target.blur) { - e.target.blur(); - } - return; - } - if (['Shift', 'Control', 'Alt', 'OS'].includes(e.key)) { - // pressing only meta key is ignored - return; - } - - let key = keys.fromKeyboardEvent(e); - - for (let listener of this.onKeyListeners) { - let stop = listener(key); - if (stop) { - e.preventDefault(); - e.stopPropagation(); - break; - } - } - } - - fromInput(e) { - if (!e.target) { - return false; - } - return e.target instanceof HTMLInputElement || - e.target instanceof HTMLTextAreaElement || - e.target instanceof HTMLSelectElement || - dom.isContentEditable(e.target); - } -} diff --git a/src/content/components/common/input.ts b/src/content/components/common/input.ts new file mode 100644 index 0000000..eefaf10 --- /dev/null +++ b/src/content/components/common/input.ts @@ -0,0 +1,75 @@ +import * as dom from 'shared/utils/dom'; +import * as keys from 'shared/utils/keys'; + +const cancelKey = (e) => { + return e.key === 'Escape' || e.key === '[' && e.ctrlKey; +}; + +export default class InputComponent { + 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)); + } + + 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 (cancelKey(e) && e.target.blur) { + e.target.blur(); + } + return; + } + if (['Shift', 'Control', 'Alt', 'OS'].includes(e.key)) { + // pressing only meta key is ignored + return; + } + + let key = keys.fromKeyboardEvent(e); + + for (let listener of this.onKeyListeners) { + let stop = listener(key); + if (stop) { + e.preventDefault(); + e.stopPropagation(); + break; + } + } + } + + fromInput(e) { + if (!e.target) { + return false; + } + return e.target instanceof HTMLInputElement || + e.target instanceof HTMLTextAreaElement || + e.target instanceof HTMLSelectElement || + dom.isContentEditable(e.target); + } +} diff --git a/src/content/components/common/keymapper.js b/src/content/components/common/keymapper.js deleted file mode 100644 index ec0d093..0000000 --- a/src/content/components/common/keymapper.js +++ /dev/null @@ -1,58 +0,0 @@ -import * as inputActions from 'content/actions/input'; -import * as operationActions from 'content/actions/operation'; -import operations from 'shared/operations'; -import * as keyUtils from 'shared/utils/keys'; - -const mapStartsWith = (mapping, keys) => { - 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 { - constructor(store) { - this.store = store; - } - - // eslint-disable-next-line max-statements - key(key) { - this.store.dispatch(inputActions.keyPress(key)); - - let state = this.store.getState(); - let input = state.input; - let keymaps = new Map(state.setting.keymaps); - - let matched = Array.from(keymaps.keys()).filter((mapping) => { - return mapStartsWith(mapping, input.keys); - }); - if (!state.addon.enabled) { - // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if - // the addon disabled - matched = matched.filter((keys) => { - let type = keymaps.get(keys).type; - 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]); - let act = operationActions.exec( - operation, state.setting, state.addon.enabled - ); - this.store.dispatch(act); - this.store.dispatch(inputActions.clearKeys()); - return true; - } -} diff --git a/src/content/components/common/keymapper.ts b/src/content/components/common/keymapper.ts new file mode 100644 index 0000000..ec0d093 --- /dev/null +++ b/src/content/components/common/keymapper.ts @@ -0,0 +1,58 @@ +import * as inputActions from 'content/actions/input'; +import * as operationActions from 'content/actions/operation'; +import operations from 'shared/operations'; +import * as keyUtils from 'shared/utils/keys'; + +const mapStartsWith = (mapping, keys) => { + 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 { + constructor(store) { + this.store = store; + } + + // eslint-disable-next-line max-statements + key(key) { + this.store.dispatch(inputActions.keyPress(key)); + + let state = this.store.getState(); + let input = state.input; + let keymaps = new Map(state.setting.keymaps); + + let matched = Array.from(keymaps.keys()).filter((mapping) => { + return mapStartsWith(mapping, input.keys); + }); + if (!state.addon.enabled) { + // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if + // the addon disabled + matched = matched.filter((keys) => { + let type = keymaps.get(keys).type; + 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]); + let act = operationActions.exec( + operation, state.setting, state.addon.enabled + ); + this.store.dispatch(act); + this.store.dispatch(inputActions.clearKeys()); + return true; + } +} diff --git a/src/content/components/common/mark.js b/src/content/components/common/mark.js deleted file mode 100644 index 0f838a9..0000000 --- a/src/content/components/common/mark.js +++ /dev/null @@ -1,74 +0,0 @@ -import * as markActions from 'content/actions/mark'; -import * as scrolls from 'content/scrolls'; -import * as consoleFrames from 'content/console-frames'; -import * as properties from 'shared/settings/properties'; - -const cancelKey = (key) => { - return key.key === 'Esc' || key.key === '[' && key.ctrlKey; -}; - -const globalKey = (key) => { - return (/^[A-Z0-9]$/).test(key); -}; - -export default class MarkComponent { - constructor(body, store) { - this.body = body; - this.store = store; - } - - // eslint-disable-next-line max-statements - key(key) { - let { mark: markStage, setting } = this.store.getState(); - let smoothscroll = setting.properties.smoothscroll || - properties.defaults.smoothscroll; - - if (!markStage.setMode && !markStage.jumpMode) { - return false; - } - - if (cancelKey(key)) { - this.store.dispatch(markActions.cancel()); - return true; - } - - if (key.ctrlKey || key.metaKey || key.altKey) { - consoleFrames.postError('Unknown mark'); - } else if (globalKey(key.key) && markStage.setMode) { - this.doSetGlobal(key); - } else if (globalKey(key.key) && markStage.jumpMode) { - this.doJumpGlobal(key); - } else if (markStage.setMode) { - this.doSet(key); - } else if (markStage.jumpMode) { - this.doJump(markStage.marks, key, smoothscroll); - } - - this.store.dispatch(markActions.cancel()); - return true; - } - - doSet(key) { - let { x, y } = scrolls.getScroll(); - this.store.dispatch(markActions.setLocal(key.key, x, y)); - } - - doJump(marks, key, smoothscroll) { - if (!marks[key.key]) { - consoleFrames.postError('Mark is not set'); - return; - } - - let { x, y } = marks[key.key]; - scrolls.scrollTo(x, y, smoothscroll); - } - - doSetGlobal(key) { - let { x, y } = scrolls.getScroll(); - this.store.dispatch(markActions.setGlobal(key.key, x, y)); - } - - doJumpGlobal(key) { - this.store.dispatch(markActions.jumpGlobal(key.key)); - } -} diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts new file mode 100644 index 0000000..0f838a9 --- /dev/null +++ b/src/content/components/common/mark.ts @@ -0,0 +1,74 @@ +import * as markActions from 'content/actions/mark'; +import * as scrolls from 'content/scrolls'; +import * as consoleFrames from 'content/console-frames'; +import * as properties from 'shared/settings/properties'; + +const cancelKey = (key) => { + return key.key === 'Esc' || key.key === '[' && key.ctrlKey; +}; + +const globalKey = (key) => { + return (/^[A-Z0-9]$/).test(key); +}; + +export default class MarkComponent { + constructor(body, store) { + this.body = body; + this.store = store; + } + + // eslint-disable-next-line max-statements + key(key) { + let { mark: markStage, setting } = this.store.getState(); + let smoothscroll = setting.properties.smoothscroll || + properties.defaults.smoothscroll; + + if (!markStage.setMode && !markStage.jumpMode) { + return false; + } + + if (cancelKey(key)) { + this.store.dispatch(markActions.cancel()); + return true; + } + + if (key.ctrlKey || key.metaKey || key.altKey) { + consoleFrames.postError('Unknown mark'); + } else if (globalKey(key.key) && markStage.setMode) { + this.doSetGlobal(key); + } else if (globalKey(key.key) && markStage.jumpMode) { + this.doJumpGlobal(key); + } else if (markStage.setMode) { + this.doSet(key); + } else if (markStage.jumpMode) { + this.doJump(markStage.marks, key, smoothscroll); + } + + this.store.dispatch(markActions.cancel()); + return true; + } + + doSet(key) { + let { x, y } = scrolls.getScroll(); + this.store.dispatch(markActions.setLocal(key.key, x, y)); + } + + doJump(marks, key, smoothscroll) { + if (!marks[key.key]) { + consoleFrames.postError('Mark is not set'); + return; + } + + let { x, y } = marks[key.key]; + scrolls.scrollTo(x, y, smoothscroll); + } + + doSetGlobal(key) { + let { x, y } = scrolls.getScroll(); + this.store.dispatch(markActions.setGlobal(key.key, x, y)); + } + + doJumpGlobal(key) { + this.store.dispatch(markActions.jumpGlobal(key.key)); + } +} -- cgit v1.2.3