diff options
Diffstat (limited to 'src/content')
-rw-r--r-- | src/content/actions/find.js | 55 | ||||
-rw-r--r-- | src/content/actions/index.js | 3 | ||||
-rw-r--r-- | src/content/actions/operation.js | 9 | ||||
-rw-r--r-- | src/content/actions/setting.js | 15 | ||||
-rw-r--r-- | src/content/components/common/follow.js | 2 | ||||
-rw-r--r-- | src/content/components/common/hint.css | 2 | ||||
-rw-r--r-- | src/content/components/common/input.js | 21 | ||||
-rw-r--r-- | src/content/components/common/keymapper.js | 25 | ||||
-rw-r--r-- | src/content/components/top-content/find.js | 54 | ||||
-rw-r--r-- | src/content/components/top-content/follow-controller.js | 2 | ||||
-rw-r--r-- | src/content/components/top-content/index.js | 6 | ||||
-rw-r--r-- | src/content/console-frame.scss | 3 | ||||
-rw-r--r-- | src/content/console-frames.js | 10 | ||||
-rw-r--r-- | src/content/navigates.js | 31 | ||||
-rw-r--r-- | src/content/reducers/find.js | 18 | ||||
-rw-r--r-- | src/content/reducers/index.js | 3 | ||||
-rw-r--r-- | src/content/reducers/input.js | 6 | ||||
-rw-r--r-- | src/content/reducers/setting.js | 3 | ||||
-rw-r--r-- | src/content/scrolls.js | 4 |
19 files changed, 220 insertions, 52 deletions
diff --git a/src/content/actions/find.js b/src/content/actions/find.js new file mode 100644 index 0000000..80d6210 --- /dev/null +++ b/src/content/actions/find.js @@ -0,0 +1,55 @@ +// +// 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 actions from 'content/actions'; +import * as consoleFrames from '../console-frames'; + +const postPatternNotFound = (pattern) => { + return consoleFrames.postError( + window.document, + 'Pattern not found: ' + pattern); +}; + +const find = (string, backwards) => { + let caseSensitive = false; + let wrapScan = true; + + + // NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work + // because of same origin policy + return window.find(string, caseSensitive, backwards, wrapScan); +}; + +const findNext = (keyword, reset, backwards) => { + if (reset) { + window.getSelection().removeAllRanges(); + } + + let found = find(keyword, backwards); + if (!found) { + window.getSelection().removeAllRanges(); + found = find(keyword, backwards); + } + if (!found) { + postPatternNotFound(keyword); + } + return { + type: actions.FIND_SET_KEYWORD, + keyword, + found, + }; +}; + +const next = (keyword, reset) => { + return findNext(keyword, reset, false); +}; + +const prev = (keyword, reset) => { + return findNext(keyword, reset, true); +}; + +export { next, prev }; diff --git a/src/content/actions/index.js b/src/content/actions/index.js index 8cc2303..7e32e12 100644 --- a/src/content/actions/index.js +++ b/src/content/actions/index.js @@ -21,4 +21,7 @@ export default { FOLLOW_CONTROLLER_DISABLE: 'follow.controller.disable', FOLLOW_CONTROLLER_KEY_PRESS: 'follow.controller.key.press', FOLLOW_CONTROLLER_BACKSPACE: 'follow.controller.backspace', + + // Find + FIND_SET_KEYWORD: 'find.set.keyword', }; diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js index 897f361..767f14b 100644 --- a/src/content/actions/operation.js +++ b/src/content/actions/operation.js @@ -6,6 +6,7 @@ import * as urls from 'content/urls'; import * as consoleFrames from 'content/console-frames'; import * as addonActions from './addon'; +// eslint-disable-next-line complexity const exec = (operation) => { switch (operation.type) { case operations.ADDON_ENABLE: @@ -14,6 +15,14 @@ const exec = (operation) => { return addonActions.disable(); case operations.ADDON_TOGGLE_ENABLED: return addonActions.toggleEnabled(); + case operations.FIND_NEXT: + return window.top.postMessage(JSON.stringify({ + type: messages.FIND_NEXT, + }), '*'); + case operations.FIND_PREV: + return window.top.postMessage(JSON.stringify({ + type: messages.FIND_PREV, + }), '*'); case operations.SCROLL_VERTICALLY: return scrolls.scrollVertically(window, operation.count); case operations.SCROLL_HORIZONALLY: diff --git a/src/content/actions/setting.js b/src/content/actions/setting.js index c874294..0238c71 100644 --- a/src/content/actions/setting.js +++ b/src/content/actions/setting.js @@ -1,9 +1,22 @@ import actions from 'content/actions'; +import * as keyUtils from 'shared/utils/keys'; const set = (value) => { + let entries = []; + if (value.keymaps) { + entries = Object.entries(value.keymaps).map((entry) => { + return [ + keyUtils.fromMapKeys(entry[0]), + entry[1], + ]; + }); + } + return { type: actions.SETTING_SET, - value, + value: Object.assign({}, value, { + keymaps: entries, + }) }; }; diff --git a/src/content/components/common/follow.js b/src/content/components/common/follow.js index 15b2a98..7717154 100644 --- a/src/content/components/common/follow.js +++ b/src/content/components/common/follow.js @@ -47,7 +47,7 @@ export default class Follow { } this.win.parent.postMessage(JSON.stringify({ type: messages.FOLLOW_KEY_PRESS, - key, + key: key.key, }), '*'); return true; } diff --git a/src/content/components/common/hint.css b/src/content/components/common/hint.css index 119dd21..1f2ab20 100644 --- a/src/content/components/common/hint.css +++ b/src/content/components/common/hint.css @@ -4,7 +4,7 @@ font-weight: bold; position: absolute; text-transform: uppercase; - z-index: 100000; + z-index: 2147483647; font-size: 12px; color: black; } diff --git a/src/content/components/common/input.js b/src/content/components/common/input.js index 8b1d35d..22b0a91 100644 --- a/src/content/components/common/input.js +++ b/src/content/components/common/input.js @@ -1,22 +1,5 @@ import * as dom from 'shared/utils/dom'; - -const modifierdKeyName = (name) => { - if (name.length === 1) { - return name.toUpperCase(); - } else if (name === 'Escape') { - return 'Esc'; - } - return name; -}; - -const mapKey = (e) => { - if (e.ctrlKey) { - return '<C-' + modifierdKeyName(e.key) + '>'; - } else if (e.shiftKey && e.key.length !== 1) { - return '<S-' + modifierdKeyName(e.key) + '>'; - } - return e.key; -}; +import * as keys from 'shared/utils/keys'; export default class InputComponent { constructor(target) { @@ -64,7 +47,7 @@ export default class InputComponent { return; } - let key = mapKey(e); + let key = keys.fromKeyboardEvent(e); for (let listener of this.onKeyListeners) { let stop = listener(key); diff --git a/src/content/components/common/keymapper.js b/src/content/components/common/keymapper.js index 1da3c0d..fb8fabe 100644 --- a/src/content/components/common/keymapper.js +++ b/src/content/components/common/keymapper.js @@ -1,6 +1,19 @@ 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) { @@ -12,16 +25,16 @@ export default class KeymapperComponent { let state = this.store.getState(); let input = state.input; - let keymaps = state.setting.keymaps; + let keymaps = new Map(state.setting.keymaps); - let matched = Object.keys(keymaps).filter((keyStr) => { - return keyStr.startsWith(input.keys); + 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[keys].type; + let type = keymaps.get(keys).type; return type === operations.ADDON_ENABLE || type === operations.ADDON_TOGGLE_ENABLED; }); @@ -30,10 +43,10 @@ export default class KeymapperComponent { this.store.dispatch(inputActions.clearKeys()); return false; } else if (matched.length > 1 || - matched.length === 1 && input.keys !== matched[0]) { + matched.length === 1 && input.keys.length < matched[0].length) { return true; } - let operation = keymaps[matched]; + let operation = keymaps.get(matched[0]); this.store.dispatch(operationActions.exec(operation)); this.store.dispatch(inputActions.clearKeys()); return true; diff --git a/src/content/components/top-content/find.js b/src/content/components/top-content/find.js new file mode 100644 index 0000000..bccf040 --- /dev/null +++ b/src/content/components/top-content/find.js @@ -0,0 +1,54 @@ +import * as findActions from 'content/actions/find'; +import messages from 'shared/messages'; +import * as consoleFrames from '../../console-frames'; + +export default class FindComponent { + constructor(win, store) { + this.win = win; + this.store = store; + + messages.onMessage(this.onMessage.bind(this)); + } + + onMessage(message) { + switch (message.type) { + case messages.CONSOLE_ENTER_FIND: + return this.start(message.text); + case messages.FIND_NEXT: + return this.next(); + case messages.FIND_PREV: + return this.prev(); + } + } + + start(text) { + let state = this.store.getState().find; + + if (text.length === 0) { + return this.store.dispatch(findActions.next(state.keyword, true)); + } + return this.store.dispatch(findActions.next(text, true)); + } + + next() { + let state = this.store.getState().find; + + if (!state.found) { + return consoleFrames.postError( + window.document, + 'Pattern not found: ' + state.keyword); + } + return this.store.dispatch(findActions.next(state.keyword, false)); + } + + prev() { + let state = this.store.getState().find; + + if (!state.found) { + return consoleFrames.postError( + window.document, + 'Pattern not found: ' + state.keyword); + } + return this.store.dispatch(findActions.prev(state.keyword, false)); + } +} diff --git a/src/content/components/top-content/follow-controller.js b/src/content/components/top-content/follow-controller.js index 38869e6..d373177 100644 --- a/src/content/components/top-content/follow-controller.js +++ b/src/content/components/top-content/follow-controller.js @@ -76,7 +76,7 @@ export default class FollowController { this.activate(); this.store.dispatch(followControllerActions.disable()); break; - case 'Escape': + case 'Esc': this.store.dispatch(followControllerActions.disable()); break; case 'Backspace': diff --git a/src/content/components/top-content/index.js b/src/content/components/top-content/index.js index f6afbfa..cf21ec4 100644 --- a/src/content/components/top-content/index.js +++ b/src/content/components/top-content/index.js @@ -1,5 +1,6 @@ import CommonComponent from '../common'; import FollowController from './follow-controller'; +import FindComponent from './find'; import * as consoleFrames from '../../console-frames'; import * as addonActions from '../../actions/addon'; import messages from 'shared/messages'; @@ -14,11 +15,14 @@ export default class TopContent { new CommonComponent(win, store); // eslint-disable-line no-new new FollowController(win, store); // eslint-disable-line no-new + new FindComponent(win, store); // eslint-disable-line no-new // TODO make component consoleFrames.initialize(this.win.document); messages.onMessage(this.onMessage.bind(this)); + + this.store.subscribe(() => this.update()); } update() { @@ -45,7 +49,7 @@ export default class TopContent { onMessage(message) { switch (message.type) { - case messages.CONSOLE_HIDE_COMMAND: + case messages.CONSOLE_UNFOCUS: this.win.focus(); consoleFrames.blur(window.document); return Promise.resolve(); diff --git a/src/content/console-frame.scss b/src/content/console-frame.scss index 33bfff3..dece648 100644 --- a/src/content/console-frame.scss +++ b/src/content/console-frame.scss @@ -6,7 +6,8 @@ width: 100%; height: 100%; position: fixed; - z-index: 10000; + z-index: 2147483647; border: none; + background-color: unset; pointer-events:none; } diff --git a/src/content/console-frames.js b/src/content/console-frames.js index 35b975f..515ae09 100644 --- a/src/content/console-frames.js +++ b/src/content/console-frames.js @@ -1,4 +1,5 @@ import './console-frame.scss'; +import messages from 'shared/messages'; const initialize = (doc) => { let iframe = doc.createElement('iframe'); @@ -20,4 +21,11 @@ const postMessage = (doc, message) => { iframe.contentWindow.postMessage(JSON.stringify(message), '*'); }; -export { initialize, blur, postMessage }; +const postError = (doc, message) => { + return postMessage(doc, { + type: messages.CONSOLE_SHOW_ERROR, + text: message, + }); +}; + +export { initialize, blur, postMessage, postError }; diff --git a/src/content/navigates.js b/src/content/navigates.js index 64e5fc0..3e12a6f 100644 --- a/src/content/navigates.js +++ b/src/content/navigates.js @@ -2,13 +2,14 @@ const PREV_LINK_PATTERNS = [ /\bprev\b/i, /\bprevious\b/i, /\bback\b/i, /</, /\u2039/, /\u2190/, /\xab/, /\u226a/, /<</ ]; + const NEXT_LINK_PATTERNS = [ /\bnext\b/i, />/, /\u203a/, /\u2192/, /\xbb/, /\u226b/, />>/ ]; const findLinkByPatterns = (win, patterns) => { - let links = win.document.getElementsByTagName('a'); + const links = win.document.getElementsByTagName('a'); return Array.prototype.find.call(links, (link) => { return patterns.some(ptn => ptn.test(link.textContent)); }); @@ -22,30 +23,32 @@ const historyNext = (win) => { win.history.forward(); }; -const linkPrev = (win) => { - let link = win.document.querySelector('a[rel=prev]'); +const linkCommon = (win, rel, patterns) => { + let link = win.document.querySelector(`link[rel~=${rel}][href]`); + if (link) { - return link.click(); + win.location = link.getAttribute('href'); + return; } - link = findLinkByPatterns(win, PREV_LINK_PATTERNS); + + link = win.document.querySelector(`a[rel~=${rel}]`) || + findLinkByPatterns(win, patterns); + if (link) { link.click(); } }; +const linkPrev = (win) => { + linkCommon(win, 'prev', PREV_LINK_PATTERNS); +}; + const linkNext = (win) => { - let link = win.document.querySelector('a[rel=next]'); - if (link) { - return link.click(); - } - link = findLinkByPatterns(win, NEXT_LINK_PATTERNS); - if (link) { - link.click(); - } + linkCommon(win, 'next', NEXT_LINK_PATTERNS); }; const parent = (win) => { - let loc = win.location; + const loc = win.location; if (loc.hash !== '') { loc.hash = ''; return; diff --git a/src/content/reducers/find.js b/src/content/reducers/find.js new file mode 100644 index 0000000..eb43c37 --- /dev/null +++ b/src/content/reducers/find.js @@ -0,0 +1,18 @@ +import actions from 'content/actions'; + +const defaultState = { + keyword: '', + found: false, +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.FIND_SET_KEYWORD: + return Object.assign({}, state, { + keyword: action.keyword, + found: action.found, + }); + default: + return state; + } +} diff --git a/src/content/reducers/index.js b/src/content/reducers/index.js index 17c0429..2487d85 100644 --- a/src/content/reducers/index.js +++ b/src/content/reducers/index.js @@ -1,4 +1,5 @@ import addonReducer from './addon'; +import findReducer from './find'; import settingReducer from './setting'; import inputReducer from './input'; import followControllerReducer from './follow-controller'; @@ -6,6 +7,7 @@ import followControllerReducer from './follow-controller'; // Make setting reducer instead of re-use const defaultState = { addon: addonReducer(undefined, {}), + find: findReducer(undefined, {}), setting: settingReducer(undefined, {}), input: inputReducer(undefined, {}), followController: followControllerReducer(undefined, {}), @@ -14,6 +16,7 @@ const defaultState = { export default function reducer(state = defaultState, action = {}) { return Object.assign({}, state, { addon: addonReducer(state.addon, action), + find: findReducer(state.find, action), setting: settingReducer(state.setting, action), input: inputReducer(state.input, action), followController: followControllerReducer(state.followController, action), diff --git a/src/content/reducers/input.js b/src/content/reducers/input.js index 9457604..134aa95 100644 --- a/src/content/reducers/input.js +++ b/src/content/reducers/input.js @@ -1,18 +1,18 @@ import actions from 'content/actions'; const defaultState = { - keys: '' + 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 + keys: state.keys.concat([action.key]), }); case actions.INPUT_CLEAR_KEYS: return Object.assign({}, state, { - keys: '', + keys: [], }); default: return state; diff --git a/src/content/reducers/setting.js b/src/content/reducers/setting.js index b6f6c58..a23027f 100644 --- a/src/content/reducers/setting.js +++ b/src/content/reducers/setting.js @@ -1,7 +1,8 @@ import actions from 'content/actions'; const defaultState = { - keymaps: {}, + // keymaps is and arrays of key-binding pairs, which is entries of Map + keymaps: [], }; export default function reducer(state = defaultState, action = {}) { diff --git a/src/content/scrolls.js b/src/content/scrolls.js index d88320f..ef38273 100644 --- a/src/content/scrolls.js +++ b/src/content/scrolls.js @@ -104,14 +104,14 @@ const scrollBottom = (win) => { const scrollHome = (win) => { let target = scrollTarget(win); let x = 0; - let y = target.scrollLeft; + let y = target.scrollTop; target.scrollTo(x, y); }; const scrollEnd = (win) => { let target = scrollTarget(win); let x = target.scrollWidth; - let y = target.scrollLeft; + let y = target.scrollTop; target.scrollTo(x, y); }; |