aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/content/client/TabsClient.ts12
-rw-r--r--src/content/components/common/follow.ts79
-rw-r--r--src/content/components/common/hint.ts62
-rw-r--r--src/content/presenters/Hint.ts127
4 files changed, 160 insertions, 120 deletions
diff --git a/src/content/client/TabsClient.ts b/src/content/client/TabsClient.ts
index fe72e11..e1af078 100644
--- a/src/content/client/TabsClient.ts
+++ b/src/content/client/TabsClient.ts
@@ -1,18 +1,22 @@
import * as messages from '../../shared/messages';
export default interface TabsClient {
- openUrl(url: string, newTab: boolean): Promise<void>;
+ openUrl(url: string, newTab: boolean, background?: boolean): Promise<void>;
// eslint-disable-next-line semi
}
-export class TabsClientImpl {
- async openUrl(url: string, newTab: boolean): Promise<void> {
+export class TabsClientImpl implements TabsClient {
+ async openUrl(
+ url: string,
+ newTab: boolean,
+ background?: boolean,
+ ): Promise<void> {
await browser.runtime.sendMessage({
type: messages.OPEN_URL,
url,
newTab,
+ background,
});
}
}
-
diff --git a/src/content/components/common/follow.ts b/src/content/components/common/follow.ts
index 67f2dd9..a30a3d5 100644
--- a/src/content/components/common/follow.ts
+++ b/src/content/components/common/follow.ts
@@ -1,8 +1,11 @@
import MessageListener from '../../MessageListener';
-import Hint from './hint';
+import Hint, { LinkHint, InputHint } from '../../presenters/Hint';
import * as dom from '../../../shared/utils/dom';
import * as messages from '../../../shared/messages';
import * as keyUtils from '../../../shared/utils/keys';
+import TabsClient, { TabsClientImpl } from '../../client/TabsClient';
+
+let tabsClient: TabsClient = new TabsClientImpl();
const TARGET_SELECTOR = [
'a', 'button', 'input', 'textarea', 'area',
@@ -95,27 +98,6 @@ export default class Follow {
return true;
}
- openLink(element: HTMLAreaElement|HTMLAnchorElement) {
- // 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: any, viewSize: Size, framePosition: Point) {
this.targets = Follow.getTargetElements(this.win, viewSize, framePosition);
sender.postMessage(JSON.stringify({
@@ -134,8 +116,13 @@ export default class Follow {
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;
+ let target = this.targets[i];
+ if (target instanceof HTMLAnchorElement ||
+ target instanceof HTMLAreaElement) {
+ this.hints[keys] = new LinkHint(target, keys);
+ } else {
+ this.hints[keys] = new InputHint(target, keys);
+ }
}
}
@@ -154,42 +141,26 @@ export default class Follow {
this.targets = [];
}
- activateHints(keys: string) {
+ async activateHints(keys: string): Promise<void> {
let hint = this.hints[keys];
if (!hint) {
return;
}
- let element = hint.getTarget();
- switch (element.tagName.toLowerCase()) {
- case 'a':
- return this.openLink(element as HTMLAnchorElement);
- case 'area':
- return this.openLink(element as HTMLAreaElement);
- case 'input':
- switch ((element as HTMLInputElement).type) {
- case 'file':
- case 'checkbox':
- case 'radio':
- case 'submit':
- case 'reset':
- case 'button':
- case 'image':
- case 'color':
- return element.click();
- default:
- return element.focus();
+
+ if (hint instanceof LinkHint) {
+ let url = hint.getLink();
+ // ignore taget='_blank'
+ if (!this.newTab && hint.getLinkTarget() !== '_blank') {
+ hint.click();
+ return;
}
- 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();
+ // eslint-disable-next-line no-script-url
+ if (!url || url === '#' || url.toLowerCase().startsWith('javascript:')) {
+ return;
}
+ await tabsClient.openUrl(url, this.newTab, this.background);
+ } else if (hint instanceof InputHint) {
+ hint.activate();
}
}
diff --git a/src/content/components/common/hint.ts b/src/content/components/common/hint.ts
deleted file mode 100644
index 2fcbb0f..0000000
--- a/src/content/components/common/hint.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as dom from '../../../shared/utils/dom';
-
-interface Point {
- x: number;
- y: number;
-}
-
-const hintPosition = (element: Element): Point => {
- 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 {
- private target: HTMLElement;
-
- private element: HTMLElement;
-
- constructor(target: HTMLElement, tag: string) {
- let doc = target.ownerDocument;
- if (doc === null) {
- throw new TypeError('ownerDocument is null');
- }
-
- let { x, y } = hintPosition(target);
- let { scrollX, scrollY } = window;
-
- this.target = target;
-
- 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(): void {
- this.element.style.display = 'inline';
- }
-
- hide(): void {
- this.element.style.display = 'none';
- }
-
- remove(): void {
- this.element.remove();
- }
-
- getTarget(): HTMLElement {
- return this.target;
- }
-}
diff --git a/src/content/presenters/Hint.ts b/src/content/presenters/Hint.ts
new file mode 100644
index 0000000..60c0f4c
--- /dev/null
+++ b/src/content/presenters/Hint.ts
@@ -0,0 +1,127 @@
+import * as doms from '../../shared/utils/dom';
+
+interface Point {
+ x: number;
+ y: number;
+}
+
+const hintPosition = (element: Element): Point => {
+ let { left, top, right, bottom } = doms.viewportRect(element);
+
+ if (element.tagName !== 'AREA') {
+ return { x: left, y: top };
+ }
+
+ return {
+ x: (left + right) / 2,
+ y: (top + bottom) / 2,
+ };
+};
+
+export default abstract class Hint {
+ private hint: HTMLElement;
+
+ private tag: string;
+
+ constructor(target: HTMLElement, tag: string) {
+ this.tag = tag;
+
+ let doc = target.ownerDocument;
+ if (doc === null) {
+ throw new TypeError('ownerDocument is null');
+ }
+
+ let { x, y } = hintPosition(target);
+ let { scrollX, scrollY } = window;
+
+ let hint = doc.createElement('span');
+ hint.className = 'vimvixen-hint';
+ hint.textContent = tag;
+ hint.style.left = x + scrollX + 'px';
+ hint.style.top = y + scrollY + 'px';
+
+ doc.body.append(hint);
+
+ this.hint = hint;
+ this.show();
+ }
+
+ show(): void {
+ this.hint.style.display = 'inline';
+ }
+
+ hide(): void {
+ this.hint.style.display = 'none';
+ }
+
+ remove(): void {
+ this.hint.remove();
+ }
+
+ getTag(): string {
+ return this.tag;
+ }
+}
+
+export class LinkHint extends Hint {
+ private target: HTMLAnchorElement | HTMLAreaElement;
+
+ constructor(target: HTMLAnchorElement | HTMLAreaElement, tag: string) {
+ super(target, tag);
+
+ this.target = target;
+ }
+
+ getLink(): string {
+ return this.target.href;
+ }
+
+ getLinkTarget(): string | null {
+ return this.target.getAttribute('target');
+ }
+
+ click(): void {
+ this.target.click();
+ }
+}
+
+export class InputHint extends Hint {
+ private target: HTMLElement;
+
+ constructor(target: HTMLElement, tag: string) {
+ super(target, tag);
+
+ this.target = target;
+ }
+
+ activate(): void {
+ let target = this.target;
+ switch (target.tagName.toLowerCase()) {
+ case 'input':
+ switch ((target as HTMLInputElement).type) {
+ case 'file':
+ case 'checkbox':
+ case 'radio':
+ case 'submit':
+ case 'reset':
+ case 'button':
+ case 'image':
+ case 'color':
+ return target.click();
+ default:
+ return target.focus();
+ }
+ case 'textarea':
+ return target.focus();
+ case 'button':
+ case 'summary':
+ return target.click();
+ default:
+ if (doms.isContentEditable(target)) {
+ return target.focus();
+ } else if (target.hasAttribute('tabindex')) {
+ return target.click();
+ }
+ }
+ }
+}