aboutsummaryrefslogtreecommitdiff
path: root/src/content/components/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/content/components/common')
-rw-r--r--src/content/components/common/follow.ts231
-rw-r--r--src/content/components/common/hint.ts62
-rw-r--r--src/content/components/common/index.ts61
-rw-r--r--src/content/components/common/input.ts80
-rw-r--r--src/content/components/common/keymapper.ts68
-rw-r--r--src/content/components/common/mark.ts79
6 files changed, 0 insertions, 581 deletions
diff --git a/src/content/components/common/follow.ts b/src/content/components/common/follow.ts
deleted file mode 100644
index 67f2dd9..0000000
--- a/src/content/components/common/follow.ts
+++ /dev/null
@@ -1,231 +0,0 @@
-import MessageListener from '../../MessageListener';
-import Hint from './hint';
-import * as dom from '../../../shared/utils/dom';
-import * as messages from '../../../shared/messages';
-import * as keyUtils from '../../../shared/utils/keys';
-
-const TARGET_SELECTOR = [
- 'a', 'button', 'input', 'textarea', 'area',
- '[contenteditable=true]', '[contenteditable=""]', '[tabindex]',
- '[role="button"]', 'summary'
-].join(',');
-
-interface Size {
- width: number;
- height: number;
-}
-
-interface Point {
- x: number;
- y: number;
-}
-
-const inViewport = (
- win: Window,
- element: Element,
- viewSize: Size,
- framePosition: Point,
-): boolean => {
- 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: Window, element: Element): boolean => {
- if (!element || win.document.documentElement === element) {
- return false;
- }
- for (let attr of ['aria-hidden', 'aria-disabled']) {
- let value = element.getAttribute(attr);
- if (value !== null) {
- let hidden = value.toLowerCase();
- if (hidden === '' || hidden === 'true') {
- return true;
- }
- }
- }
- return isAriaHiddenOrAriaDisabled(win, element.parentElement as Element);
-};
-
-export default class Follow {
- private win: Window;
-
- private newTab: boolean;
-
- private background: boolean;
-
- private hints: {[key: string]: Hint };
-
- private targets: HTMLElement[] = [];
-
- constructor(win: Window) {
- this.win = win;
- this.newTab = false;
- this.background = false;
- this.hints = {};
- this.targets = [];
-
- new MessageListener().onWebMessage(this.onMessage.bind(this));
- }
-
- key(key: keyUtils.Key): boolean {
- 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: 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({
- type: messages.FOLLOW_RESPONSE_COUNT_TARGETS,
- count: this.targets.length,
- }), '*');
- }
-
- createHints(keysArray: string[], newTab: boolean, background: boolean) {
- 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: string) {
- 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: string) {
- 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();
- }
- 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: messages.Message, sender: any) {
- 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();
- }
- }
-
- static getTargetElements(
- win: Window,
- viewSize:
- Size, framePosition: Point,
- ): HTMLElement[] {
- let all = win.document.querySelectorAll(TARGET_SELECTOR);
- let filtered = Array.prototype.filter.call(all, (element: HTMLElement) => {
- let style = win.getComputedStyle(element);
-
- // AREA's 'display' in Browser style is 'none'
- return (element.tagName === 'AREA' || style.display !== 'none') &&
- style.visibility !== 'hidden' &&
- (element as HTMLInputElement).type !== 'hidden' &&
- element.offsetHeight > 0 &&
- !isAriaHiddenOrAriaDisabled(win, element) &&
- inViewport(win, element, viewSize, framePosition);
- });
- return filtered;
- }
-}
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/components/common/index.ts b/src/content/components/common/index.ts
deleted file mode 100644
index 5b097b6..0000000
--- a/src/content/components/common/index.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import InputComponent from './input';
-import FollowComponent from './follow';
-import MarkComponent from './mark';
-import KeymapperComponent from './keymapper';
-import * as settingActions from '../../actions/setting';
-import * as messages from '../../../shared/messages';
-import MessageListener from '../../MessageListener';
-import * as addonActions from '../../actions/addon';
-import * as blacklists from '../../../shared/blacklists';
-import * as keys from '../../../shared/utils/keys';
-import * as actions from '../../actions';
-
-export default class Common {
- private win: Window;
-
- private store: any;
-
- constructor(win: Window, store: any) {
- const input = new InputComponent(win.document.body);
- const follow = new FollowComponent(win);
- const mark = new MarkComponent(store);
- const keymapper = new KeymapperComponent(store);
-
- input.onKey((key: keys.Key) => follow.key(key));
- input.onKey((key: keys.Key) => mark.key(key));
- input.onKey((key: keys.Key) => keymapper.key(key));
-
- this.win = win;
- this.store = store;
-
- this.reloadSettings();
-
- new MessageListener().onBackgroundMessage(this.onMessage.bind(this));
- }
-
- onMessage(message: messages.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((action: actions.SettingAction) => {
- let enabled = !blacklists.includes(
- action.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.ts b/src/content/components/common/input.ts
deleted file mode 100644
index 1fe34c9..0000000
--- a/src/content/components/common/input.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as dom from '../../../shared/utils/dom';
-import * as keys from '../../../shared/utils/keys';
-
-const cancelKey = (e: KeyboardEvent): boolean => {
- return e.key === 'Escape' || e.key === '[' && e.ctrlKey;
-};
-
-export default class InputComponent {
- private pressed: {[key: string]: string} = {};
-
- private onKeyListeners: ((key: keys.Key) => boolean)[] = [];
-
- constructor(target: HTMLElement) {
- 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: (key: keys.Key) => boolean) {
- this.onKeyListeners.push(cb);
- }
-
- onKeyPress(e: KeyboardEvent) {
- if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {
- return;
- }
- this.pressed[e.key] = 'keypress';
- this.capture(e);
- }
-
- onKeyDown(e: KeyboardEvent) {
- if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {
- return;
- }
- this.pressed[e.key] = 'keydown';
- this.capture(e);
- }
-
- onKeyUp(e: KeyboardEvent) {
- delete this.pressed[e.key];
- }
-
- // eslint-disable-next-line max-statements
- capture(e: KeyboardEvent) {
- let target = e.target;
- if (!(target instanceof HTMLElement)) {
- return;
- }
- if (this.fromInput(target)) {
- if (cancelKey(e) && target.blur) {
- 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: Element) {
- return e instanceof HTMLInputElement ||
- e instanceof HTMLTextAreaElement ||
- e instanceof HTMLSelectElement ||
- dom.isContentEditable(e);
- }
-}
diff --git a/src/content/components/common/keymapper.ts b/src/content/components/common/keymapper.ts
deleted file mode 100644
index c94bae0..0000000
--- a/src/content/components/common/keymapper.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as inputActions from '../../actions/input';
-import * as operationActions from '../../actions/operation';
-import * as operations from '../../../shared/operations';
-import * as keyUtils from '../../../shared/utils/keys';
-
-const mapStartsWith = (
- mapping: keyUtils.Key[],
- keys: keyUtils.Key[],
-): boolean => {
- 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 {
- private store: any;
-
- constructor(store: any) {
- this.store = store;
- }
-
- // eslint-disable-next-line max-statements
- key(key: keyUtils.Key): boolean {
- this.store.dispatch(inputActions.keyPress(key));
-
- let state = this.store.getState();
- let input = state.input;
- let keymaps = new Map<keyUtils.Key[], operations.Operation>(
- state.setting.keymaps.map(
- (e: {key: keyUtils.Key[], op: operations.Operation}) => [e.key, e.op],
- )
- );
-
- let matched = Array.from(keymaps.keys()).filter(
- (mapping: keyUtils.Key[]) => {
- 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) as operations.Operation).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]) as operations.Operation;
- 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.ts b/src/content/components/common/mark.ts
deleted file mode 100644
index 1237385..0000000
--- a/src/content/components/common/mark.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as markActions from '../../actions/mark';
-import * as scrolls from '../..//scrolls';
-import * as consoleFrames from '../..//console-frames';
-import * as keyUtils from '../../../shared/utils/keys';
-import Mark from '../../Mark';
-
-const cancelKey = (key: keyUtils.Key): boolean => {
- return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey);
-};
-
-const globalKey = (key: string): boolean => {
- return (/^[A-Z0-9]$/).test(key);
-};
-
-export default class MarkComponent {
- private store: any;
-
- constructor(store: any) {
- this.store = store;
- }
-
- // eslint-disable-next-line max-statements
- key(key: keyUtils.Key) {
- let { mark: markState, setting } = this.store.getState();
- let smoothscroll = setting.properties.smoothscroll;
-
- if (!markState.setMode && !markState.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) && markState.setMode) {
- this.doSetGlobal(key);
- } else if (globalKey(key.key) && markState.jumpMode) {
- this.doJumpGlobal(key);
- } else if (markState.setMode) {
- this.doSet(key);
- } else if (markState.jumpMode) {
- this.doJump(markState.marks, key, smoothscroll);
- }
-
- this.store.dispatch(markActions.cancel());
- return true;
- }
-
- doSet(key: keyUtils.Key) {
- let { x, y } = scrolls.getScroll();
- this.store.dispatch(markActions.setLocal(key.key, x, y));
- }
-
- doJump(
- marks: { [key: string]: Mark },
- key: keyUtils.Key,
- smoothscroll: boolean,
- ) {
- if (!marks[key.key]) {
- consoleFrames.postError('Mark is not set');
- return;
- }
-
- let { x, y } = marks[key.key];
- scrolls.scrollTo(x, y, smoothscroll);
- }
-
- doSetGlobal(key: keyUtils.Key) {
- let { x, y } = scrolls.getScroll();
- this.store.dispatch(markActions.setGlobal(key.key, x, y));
- }
-
- doJumpGlobal(key: keyUtils.Key) {
- this.store.dispatch(markActions.jumpGlobal(key.key));
- }
-}