aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2017-09-17 20:04:34 +0900
committerShin'ya Ueoka <ueokande@i-beam.org>2017-09-17 20:04:34 +0900
commitbf07e89126989cc555a10848ddf18589fffe2e49 (patch)
tree829eff07a3dff006ca5c3a98cbe4dd3af3697282
parent78233b25b76df55d519f1d9082267aba876b4835 (diff)
parent1880df95f60f1410a83b6af65ec9b8a74c2f59f2 (diff)
Merge branch 'improve-follow-command'
-rw-r--r--README.md4
-rw-r--r--src/actions/tab.js9
-rw-r--r--src/background/index.js8
-rw-r--r--src/content/follow.js48
-rw-r--r--src/content/hint.js6
-rw-r--r--src/content/index.js34
-rw-r--r--src/messages/index.js4
7 files changed, 87 insertions, 26 deletions
diff --git a/README.md b/README.md
index bc17cf6..c5e2ffe 100644
--- a/README.md
+++ b/README.md
@@ -43,8 +43,8 @@ Firefox by WebExtensions API.
- [x] open root page
- [ ] hints
- [x] open a link
- - [ ] open a link in new tab
- - [ ] activate input form
+ - [x] open a link in new tab
+ - [x] activate input form
- [ ] misc
- [ ] configurable keymaps
- [ ] .rc file
diff --git a/src/actions/tab.js b/src/actions/tab.js
new file mode 100644
index 0000000..e512b6f
--- /dev/null
+++ b/src/actions/tab.js
@@ -0,0 +1,9 @@
+const openNewTab = (url) => {
+ return browser.tabs.create({ url: url });
+};
+
+const openToTab = (url, tab) => {
+ return browser.tabs.update(tab.id, { url: url });
+};
+
+export { openToTab, openNewTab };
diff --git a/src/background/index.js b/src/background/index.js
index a4217c1..9df22fd 100644
--- a/src/background/index.js
+++ b/src/background/index.js
@@ -3,6 +3,7 @@ import * as inputActions from '../actions/input';
import * as operationActions from '../actions/operation';
import * as commandActions from '../actions/command';
import * as consoleActions from '../actions/console';
+import * as tabActions from '../actions/tab';
import reducers from '../reducers';
import messages from '../messages';
import * as store from '../store';
@@ -60,6 +61,13 @@ const handleMessage = (message, sender) => {
case messages.KEYDOWN:
return backgroundStore.dispatch(
inputActions.keyPress(message.code, message.ctrl), sender);
+ case messages.OPEN_URL:
+ if (message.newTab) {
+ return backgroundStore.dispatch(
+ tabActions.openNewTab(message.url), sender);
+ }
+ return backgroundStore.dispatch(
+ tabActions.openToTab(message.url, sender.tab), sender);
case messages.CONSOLE_BLURRED:
return backgroundStore.dispatch(
consoleActions.hide(), sender);
diff --git a/src/content/follow.js b/src/content/follow.js
index 5abeee0..7d69b45 100644
--- a/src/content/follow.js
+++ b/src/content/follow.js
@@ -8,6 +8,7 @@ export default class Follow {
this.doc = doc;
this.hintElements = {};
this.keys = [];
+ this.onActivatedCallbacks = [];
// TODO activate input elements and push button elements
let links = Follow.getTargetElements(doc);
@@ -36,7 +37,7 @@ export default class Follow {
} else if (keyCode === KeyboardEvent.DOM_VK_ENTER ||
keyCode === KeyboardEvent.DOM_VK_RETURN) {
let chars = Follow.codeChars(this.keys);
- this.hintElements[chars].activate();
+ this.activate(this.hintElements[chars].target);
return;
} else if (Follow.availableKey(keyCode)) {
this.keys.push(keyCode);
@@ -45,6 +46,9 @@ export default class Follow {
this.keys.pop();
}
+ e.stopPropagation();
+ e.preventDefault();
+
this.refreshKeys();
}
@@ -61,7 +65,7 @@ export default class Follow {
return;
} else if (shown.length === 1) {
this.remove();
- this.hintElements[chars].activate();
+ this.activate(this.hintElements[chars].target);
}
shown.forEach((key) => {
@@ -72,7 +76,6 @@ export default class Follow {
});
}
-
remove() {
this.doc.removeEventListener('keydown', this.boundKeydown);
Object.keys(this.hintElements).forEach((key) => {
@@ -80,6 +83,14 @@ export default class Follow {
});
}
+ 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 ||
@@ -113,21 +124,26 @@ export default class Follow {
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');
- let filtered = Array.prototype.filter.call(all, (e) => {
- return Follow.isVisibleElement(e);
+ 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' &&
+ Follow.inWindow(window, element);
});
return filtered;
}
-
- static isVisibleElement(element) {
- let style = window.getComputedStyle(element);
- if (style.display === 'none') {
- return false;
- } else if (style.visibility === 'hidden') {
- return false;
- }
- return true;
- }
}
diff --git a/src/content/hint.js b/src/content/hint.js
index c75ca8b..cc46fd6 100644
--- a/src/content/hint.js
+++ b/src/content/hint.js
@@ -33,10 +33,4 @@ export default class Hint {
remove() {
this.element.remove();
}
-
- activate() {
- if (this.target.tagName.toLowerCase() === 'a') {
- this.target.click();
- }
- }
}
diff --git a/src/content/index.js b/src/content/index.js
index a9ccd63..159429e 100644
--- a/src/content/index.js
+++ b/src/content/index.js
@@ -8,6 +8,38 @@ import messages from '../messages';
consoleFrames.initialize(window.document);
+const startFollows = (newTab) => {
+ let follow = new Follow(window.document, newTab);
+ follow.onActivated((element) => {
+ switch (element.tagName.toLowerCase()) {
+ case 'a':
+ return browser.runtime.sendMessage({
+ type: messages.OPEN_URL,
+ url: element.href,
+ newTab
+ });
+ 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();
+ }
+ });
+};
+
window.addEventListener('keypress', (e) => {
if (e.target instanceof HTMLInputElement) {
return;
@@ -34,7 +66,7 @@ const execOperation = (operation) => {
case operations.SCROLL_RIGHT:
return scrolls.scrollRight(window);
case operations.FOLLOW_START:
- return new Follow(window.document, operation.newTab);
+ return startFollows(operation.newTab);
case operations.NAVIGATE_HISTORY_PREV:
return navigates.historyPrev(window);
case operations.NAVIGATE_HISTORY_NEXT:
diff --git a/src/messages/index.js b/src/messages/index.js
index 3bdecca..4e34436 100644
--- a/src/messages/index.js
+++ b/src/messages/index.js
@@ -6,5 +6,7 @@ export default {
CONSOLE_ENTERED: 'console.entered',
CONSOLE_CHANGEED: 'console.changed',
- KEYDOWN: 'keydown'
+ KEYDOWN: 'keydown',
+
+ OPEN_URL: 'open.url'
};