aboutsummaryrefslogtreecommitdiff
path: root/src/content
diff options
context:
space:
mode:
Diffstat (limited to 'src/content')
-rw-r--r--src/content/actions/find.js55
-rw-r--r--src/content/actions/index.js3
-rw-r--r--src/content/actions/operation.js9
-rw-r--r--src/content/actions/setting.js15
-rw-r--r--src/content/components/common/follow.js2
-rw-r--r--src/content/components/common/hint.css2
-rw-r--r--src/content/components/common/input.js21
-rw-r--r--src/content/components/common/keymapper.js25
-rw-r--r--src/content/components/top-content/find.js54
-rw-r--r--src/content/components/top-content/follow-controller.js2
-rw-r--r--src/content/components/top-content/index.js6
-rw-r--r--src/content/console-frame.scss3
-rw-r--r--src/content/console-frames.js10
-rw-r--r--src/content/navigates.js31
-rw-r--r--src/content/reducers/find.js18
-rw-r--r--src/content/reducers/index.js3
-rw-r--r--src/content/reducers/input.js6
-rw-r--r--src/content/reducers/setting.js3
-rw-r--r--src/content/scrolls.js4
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);
};