aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/background/controllers/mark.js15
-rw-r--r--src/background/domains/global-mark.js24
-rw-r--r--src/background/infrastructures/content-message-client.js8
-rw-r--r--src/background/infrastructures/content-message-listener.js14
-rw-r--r--src/background/repositories/mark.js33
-rw-r--r--src/background/usecases/mark.js39
-rw-r--r--src/content/actions/index.js6
-rw-r--r--src/content/actions/mark.js46
-rw-r--r--src/content/actions/operation.js13
-rw-r--r--src/content/components/common/index.js7
-rw-r--r--src/content/components/common/mark.js74
-rw-r--r--src/content/components/top-content/index.js3
-rw-r--r--src/content/reducers/index.js3
-rw-r--r--src/content/reducers/mark.js25
-rw-r--r--src/content/scrolls.js30
-rw-r--r--src/settings/components/form/keymaps-form.jsx3
-rw-r--r--src/shared/messages.js5
-rw-r--r--src/shared/operations.js4
-rw-r--r--src/shared/settings/default.js2
19 files changed, 338 insertions, 16 deletions
diff --git a/src/background/controllers/mark.js b/src/background/controllers/mark.js
new file mode 100644
index 0000000..8d0cefd
--- /dev/null
+++ b/src/background/controllers/mark.js
@@ -0,0 +1,15 @@
+import MarkInteractor from '../usecases/mark';
+
+export default class MarkController {
+ constructor() {
+ this.markInteractor = new MarkInteractor();
+ }
+
+ setGlobal(key, x, y) {
+ this.markInteractor.setGlobal(key, x, y);
+ }
+
+ jumpGlobal(key) {
+ this.markInteractor.jumpGlobal(key);
+ }
+}
diff --git a/src/background/domains/global-mark.js b/src/background/domains/global-mark.js
new file mode 100644
index 0000000..f0586f1
--- /dev/null
+++ b/src/background/domains/global-mark.js
@@ -0,0 +1,24 @@
+export default class GlobalMark {
+ constructor(tabId, url, x, y) {
+ this.tabId0 = tabId;
+ this.url0 = url;
+ this.x0 = x;
+ this.y0 = y;
+ }
+
+ get tabId() {
+ return this.tabId0;
+ }
+
+ get url() {
+ return this.url0;
+ }
+
+ get x() {
+ return this.x0;
+ }
+
+ get y() {
+ return this.y0;
+ }
+}
diff --git a/src/background/infrastructures/content-message-client.js b/src/background/infrastructures/content-message-client.js
index d659560..7e7e602 100644
--- a/src/background/infrastructures/content-message-client.js
+++ b/src/background/infrastructures/content-message-client.js
@@ -22,4 +22,12 @@ export default class ContentMessageClient {
type: messages.ADDON_TOGGLE_ENABLED,
});
}
+
+ scrollTo(tabId, x, y) {
+ return browser.tabs.sendMessage(tabId, {
+ type: messages.TAB_SCROLL_TO,
+ x,
+ y,
+ });
+ }
}
diff --git a/src/background/infrastructures/content-message-listener.js b/src/background/infrastructures/content-message-listener.js
index 4fcc6a6..beb52fe 100644
--- a/src/background/infrastructures/content-message-listener.js
+++ b/src/background/infrastructures/content-message-listener.js
@@ -5,6 +5,7 @@ import FindController from '../controllers/find';
import AddonEnabledController from '../controllers/addon-enabled';
import LinkController from '../controllers/link';
import OperationController from '../controllers/operation';
+import MarkController from '../controllers/mark';
export default class ContentMessageListener {
constructor() {
@@ -14,6 +15,7 @@ export default class ContentMessageListener {
this.addonEnabledController = new AddonEnabledController();
this.linkController = new LinkController();
this.backgroundOperationController = new OperationController();
+ this.markController = new MarkController();
}
run() {
@@ -59,6 +61,10 @@ export default class ContentMessageListener {
message.newTab, message.url, sender.tab.id, message.background);
case messages.BACKGROUND_OPERATION:
return this.onBackgroundOperation(message.operation);
+ case messages.MARK_SET_GLOBAL:
+ return this.onMarkSetGlobal(message.key, message.x, message.y);
+ case messages.MARK_JUMP_GLOBAL:
+ return this.onMarkJumpGlobal(message.key);
}
}
@@ -102,4 +108,12 @@ export default class ContentMessageListener {
onBackgroundOperation(operation) {
return this.backgroundOperationController.exec(operation);
}
+
+ onMarkSetGlobal(key, x, y) {
+ return this.markController.setGlobal(key, x, y);
+ }
+
+ onMarkJumpGlobal(key) {
+ return this.markController.jumpGlobal(key);
+ }
}
diff --git a/src/background/repositories/mark.js b/src/background/repositories/mark.js
new file mode 100644
index 0000000..339a660
--- /dev/null
+++ b/src/background/repositories/mark.js
@@ -0,0 +1,33 @@
+import MemoryStorage from '../infrastructures/memory-storage';
+import GlobalMark from 'background/domains/global-mark';
+
+const MARK_KEY = 'mark';
+
+export default class MarkRepository {
+ constructor() {
+ this.cache = new MemoryStorage();
+ }
+
+ getMark(key) {
+ let marks = this.getOrEmptyMarks();
+ let data = marks[key];
+ if (!data) {
+ return Promise.resolve(undefined);
+ }
+ let mark = new GlobalMark(data.tabId, data.url, data.x, data.y);
+ return Promise.resolve(mark);
+ }
+
+ setMark(key, mark) {
+ let marks = this.getOrEmptyMarks();
+ marks[key] = { tabId: mark.tabId, url: mark.url, x: mark.x, y: mark.y };
+ this.cache.set(MARK_KEY, marks);
+
+ return Promise.resolve();
+ }
+
+ getOrEmptyMarks() {
+ return this.cache.get(MARK_KEY) || {};
+ }
+}
+
diff --git a/src/background/usecases/mark.js b/src/background/usecases/mark.js
new file mode 100644
index 0000000..34b8a74
--- /dev/null
+++ b/src/background/usecases/mark.js
@@ -0,0 +1,39 @@
+import GlobalMark from '../domains/global-mark';
+import TabPresenter from '../presenters/tab';
+import MarkRepository from '../repositories/mark';
+import ConsolePresenter from '../presenters/console';
+import ContentMessageClient from '../infrastructures/content-message-client';
+
+export default class MarkInteractor {
+ constructor() {
+ this.tabPresenter = new TabPresenter();
+ this.markRepository = new MarkRepository();
+ this.consolePresenter = new ConsolePresenter();
+ this.contentMessageClient = new ContentMessageClient();
+ }
+
+ async setGlobal(key, x, y) {
+ let tab = await this.tabPresenter.getCurrent();
+ let mark = new GlobalMark(tab.id, tab.url, x, y);
+ return this.markRepository.setMark(key, mark);
+ }
+
+ async jumpGlobal(key) {
+ let current = await this.tabPresenter.getCurrent();
+
+ let mark = await this.markRepository.getMark(key);
+ if (!mark) {
+ return this.consolePresenter.showError(current.id, 'Mark is not set');
+ }
+
+ return this.contentMessageClient.scrollTo(
+ mark.tabId, mark.x, mark.y
+ ).then(() => {
+ return this.tabPresenter.select(mark.tabId);
+ }).catch(async() => {
+ let tab = await this.tabPresenter.create(mark.url);
+ let mark2 = new GlobalMark(tab.id, mark.url, mark.x, mark.y);
+ return this.markRepository.setMark(key, mark2);
+ });
+ }
+}
diff --git a/src/content/actions/index.js b/src/content/actions/index.js
index 6976df7..0a16fdf 100644
--- a/src/content/actions/index.js
+++ b/src/content/actions/index.js
@@ -22,4 +22,10 @@ export default {
// Find
FIND_SET_KEYWORD: 'find.set.keyword',
+
+ // Mark
+ MARK_START_SET: 'mark.start.set',
+ MARK_START_JUMP: 'mark.start.jump',
+ MARK_CANCEL: 'mark.cancel',
+ MARK_SET_LOCAL: 'mark.set.local',
};
diff --git a/src/content/actions/mark.js b/src/content/actions/mark.js
new file mode 100644
index 0000000..712a811
--- /dev/null
+++ b/src/content/actions/mark.js
@@ -0,0 +1,46 @@
+import actions from 'content/actions';
+import messages from 'shared/messages';
+
+const startSet = () => {
+ return { type: actions.MARK_START_SET };
+};
+
+const startJump = () => {
+ return { type: actions.MARK_START_JUMP };
+};
+
+const cancel = () => {
+ return { type: actions.MARK_CANCEL };
+};
+
+const setLocal = (key, x, y) => {
+ return {
+ type: actions.MARK_SET_LOCAL,
+ key,
+ x,
+ y,
+ };
+};
+
+const setGlobal = (key, x, y) => {
+ browser.runtime.sendMessage({
+ type: messages.MARK_SET_GLOBAL,
+ key,
+ x,
+ y,
+ });
+ return { type: '' };
+};
+
+const jumpGlobal = (key) => {
+ browser.runtime.sendMessage({
+ type: messages.MARK_JUMP_GLOBAL,
+ key,
+ });
+ return { type: '' };
+};
+
+export {
+ startSet, startJump, cancel, setLocal,
+ setGlobal, jumpGlobal,
+};
diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js
index 89d7fec..1aeb8be 100644
--- a/src/content/actions/operation.js
+++ b/src/content/actions/operation.js
@@ -6,6 +6,7 @@ import * as focuses from 'content/focuses';
import * as urls from 'content/urls';
import * as consoleFrames from 'content/console-frames';
import * as addonActions from './addon';
+import * as markActions from './mark';
import * as properties from 'shared/settings/properties';
// eslint-disable-next-line complexity, max-lines-per-function
@@ -39,16 +40,16 @@ const exec = (operation, repeat, settings, addonEnabled) => {
scrolls.scrollPages(operation.count, smoothscroll, repeat);
break;
case operations.SCROLL_TOP:
- scrolls.scrollTop(smoothscroll, repeat);
+ scrolls.scrollToTop(smoothscroll);
break;
case operations.SCROLL_BOTTOM:
- scrolls.scrollBottom(smoothscroll, repeat);
+ scrolls.scrollToBottom(smoothscroll);
break;
case operations.SCROLL_HOME:
- scrolls.scrollHome(smoothscroll, repeat);
+ scrolls.scrollToHome(smoothscroll);
break;
case operations.SCROLL_END:
- scrolls.scrollEnd(smoothscroll, repeat);
+ scrolls.scrollToEnd(smoothscroll);
break;
case operations.FOLLOW_START:
window.top.postMessage(JSON.stringify({
@@ -57,6 +58,10 @@ const exec = (operation, repeat, settings, addonEnabled) => {
background: operation.background,
}), '*');
break;
+ case operations.MARK_SET_PREFIX:
+ return markActions.startSet();
+ case operations.MARK_JUMP_PREFIX:
+ return markActions.startJump();
case operations.NAVIGATE_HISTORY_PREV:
navigates.historyPrev(window);
break;
diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js
index a1e71a1..bcab4fa 100644
--- a/src/content/components/common/index.js
+++ b/src/content/components/common/index.js
@@ -1,6 +1,7 @@
import InputComponent from './input';
-import KeymapperComponent from './keymapper';
import FollowComponent from './follow';
+import MarkComponent from './mark';
+import KeymapperComponent from './keymapper';
import * as settingActions from 'content/actions/setting';
import messages from 'shared/messages';
import * as addonActions from '../../actions/addon';
@@ -8,11 +9,13 @@ import * as blacklists from 'shared/blacklists';
export default class Common {
constructor(win, store) {
- const follow = new FollowComponent(win, store);
const input = new InputComponent(win.document.body, store);
+ const follow = new FollowComponent(win, store);
+ const mark = new MarkComponent(win.document.body, store);
const keymapper = new KeymapperComponent(store);
input.onKey(key => follow.key(key));
+ input.onKey(key => mark.key(key));
input.onKey(key => keymapper.key(key));
this.win = win;
diff --git a/src/content/components/common/mark.js b/src/content/components/common/mark.js
new file mode 100644
index 0000000..1ed636b
--- /dev/null
+++ b/src/content/components/common/mark.js
@@ -0,0 +1,74 @@
+import * as markActions from 'content/actions/mark';
+import * as scrolls from 'content/scrolls';
+import * as consoleFrames from 'content/console-frames';
+import * as properties from 'shared/settings/properties';
+
+const cancelKey = (key) => {
+ return key.key === 'Esc' || key.key === '[' && key.ctrlKey;
+};
+
+const globalKey = (key) => {
+ return (/^[A-Z0-9]$/).test(key);
+};
+
+export default class MarkComponent {
+ constructor(body, store) {
+ this.body = body;
+ this.store = store;
+ }
+
+ // eslint-disable-next-line max-statements
+ key(key) {
+ let { mark: markStage, setting } = this.store.getState();
+ let smoothscroll = setting.properties.smoothscroll ||
+ properties.defaults.smoothscroll;
+
+ if (!markStage.setMode && !markStage.jumpMode) {
+ return false;
+ }
+
+ if (cancelKey(key)) {
+ this.store.dispatch(markActions.cancel());
+ return true;
+ }
+
+ if (key.ctrlKey || key.metaKey || key.altKey) {
+ consoleFrames.postError(window.document, 'Unknown mark');
+ } else if (globalKey(key.key) && markStage.setMode) {
+ this.doSetGlobal(key);
+ } else if (globalKey(key.key) && markStage.jumpMode) {
+ this.doJumpGlobal(key);
+ } else if (markStage.setMode) {
+ this.doSet(key);
+ } else if (markStage.jumpMode) {
+ this.doJump(markStage.marks, key, smoothscroll);
+ }
+
+ this.store.dispatch(markActions.cancel());
+ return true;
+ }
+
+ doSet(key) {
+ let { x, y } = scrolls.getScroll();
+ this.store.dispatch(markActions.setLocal(key.key, x, y));
+ }
+
+ doJump(marks, key, smoothscroll) {
+ if (!marks[key.key]) {
+ consoleFrames.postError(window.document, 'Mark is not set');
+ return;
+ }
+
+ let { x, y } = marks[key.key];
+ scrolls.scrollTo(x, y, smoothscroll);
+ }
+
+ doSetGlobal(key) {
+ let { x, y } = scrolls.getScroll();
+ this.store.dispatch(markActions.setGlobal(key.key, x, y));
+ }
+
+ doJumpGlobal(key) {
+ this.store.dispatch(markActions.jumpGlobal(key.key));
+ }
+}
diff --git a/src/content/components/top-content/index.js b/src/content/components/top-content/index.js
index e22e957..1aaef1b 100644
--- a/src/content/components/top-content/index.js
+++ b/src/content/components/top-content/index.js
@@ -3,6 +3,7 @@ import FollowController from './follow-controller';
import FindComponent from './find';
import * as consoleFrames from '../../console-frames';
import messages from 'shared/messages';
+import * as scrolls from 'content/scrolls';
export default class TopContent {
@@ -33,6 +34,8 @@ export default class TopContent {
type: messages.ADDON_ENABLED_RESPONSE,
enabled: addonState.enabled,
});
+ case messages.TAB_SCROLL_TO:
+ return scrolls.scrollTo(message.x, message.y, false);
}
}
}
diff --git a/src/content/reducers/index.js b/src/content/reducers/index.js
index 6e6a147..bf612a3 100644
--- a/src/content/reducers/index.js
+++ b/src/content/reducers/index.js
@@ -4,7 +4,8 @@ import find from './find';
import setting from './setting';
import input from './input';
import followController from './follow-controller';
+import mark from './mark';
export default combineReducers({
- addon, find, setting, input, followController,
+ addon, find, setting, input, followController, mark,
});
diff --git a/src/content/reducers/mark.js b/src/content/reducers/mark.js
new file mode 100644
index 0000000..2c96cc5
--- /dev/null
+++ b/src/content/reducers/mark.js
@@ -0,0 +1,25 @@
+import actions from 'content/actions';
+
+const defaultState = {
+ setMode: false,
+ jumpMode: false,
+ marks: {},
+};
+
+export default function reducer(state = defaultState, action = {}) {
+ switch (action.type) {
+ case actions.MARK_START_SET:
+ return { ...state, setMode: true };
+ case actions.MARK_START_JUMP:
+ return { ...state, jumpMode: true };
+ case actions.MARK_CANCEL:
+ return { ...state, setMode: false, jumpMode: false };
+ case actions.MARK_SET_LOCAL: {
+ let marks = { ...state.marks };
+ marks[action.key] = { x: action.x, y: action.y };
+ return { ...state, setMode: false, marks };
+ }
+ default:
+ return state;
+ }
+}
diff --git a/src/content/scrolls.js b/src/content/scrolls.js
index 0d1f7c8..b9a4bc3 100644
--- a/src/content/scrolls.js
+++ b/src/content/scrolls.js
@@ -130,6 +130,11 @@ const scroller = (element, smooth, repeat) => {
return new RoughtScroller(element);
};
+const getScroll = () => {
+ let target = scrollTarget();
+ return { x: target.scrollLeft, y: target.scrollTop };
+};
+
const scrollVertically = (count, smooth, repeat) => {
let target = scrollTarget();
let x = target.scrollLeft;
@@ -158,35 +163,42 @@ const scrollPages = (count, smooth, repeat) => {
scroller(target, smooth, repeat).scroll(x, y);
};
-const scrollTop = (smooth, repeat) => {
+const scrollTo = (x, y, smooth) => {
+ let target = scrollTarget();
+ scroller(target, smooth, false).scroll(x, y);
+};
+
+const scrollToTop = (smooth) => {
let target = scrollTarget();
let x = target.scrollLeft;
let y = 0;
- scroller(target, smooth, repeat).scroll(x, y);
+ scroller(target, smooth, false).scroll(x, y);
};
-const scrollBottom = (smooth, repeat) => {
+const scrollToBottom = (smooth) => {
let target = scrollTarget();
let x = target.scrollLeft;
let y = target.scrollHeight;
- scroller(target, smooth, repeat).scroll(x, y);
+ scroller(target, smooth, false).scroll(x, y);
};
-const scrollHome = (smooth, repeat) => {
+const scrollToHome = (smooth) => {
let target = scrollTarget();
let x = 0;
let y = target.scrollTop;
- scroller(target, smooth, repeat).scroll(x, y);
+ scroller(target, smooth, false).scroll(x, y);
};
-const scrollEnd = (smooth, repeat) => {
+const scrollToEnd = (smooth) => {
let target = scrollTarget();
let x = target.scrollWidth;
let y = target.scrollTop;
- scroller(target, smooth, repeat).scroll(x, y);
+ scroller(target, smooth, false).scroll(x, y);
};
export {
+ getScroll,
scrollVertically, scrollHorizonally, scrollPages,
- scrollTop, scrollBottom, scrollHome, scrollEnd
+ scrollTo,
+ scrollToTop, scrollToBottom, scrollToHome, scrollToEnd
};
diff --git a/src/settings/components/form/keymaps-form.jsx b/src/settings/components/form/keymaps-form.jsx
index dcf65d9..8c6be54 100644
--- a/src/settings/components/form/keymaps-form.jsx
+++ b/src/settings/components/form/keymaps-form.jsx
@@ -17,6 +17,9 @@ const KeyMapFields = [
['scroll.pages?{"count":-1}', 'Scroll up by a screen'],
['scroll.pages?{"count":1}', 'Scroll down by a screen'],
], [
+ ['mark.set.prefix', 'Set mark at current position'],
+ ['mark.jump.prefix', 'Jump to the mark'],
+ ], [
['tabs.close', 'Close a tab'],
['tabs.reopen', 'Reopen closed tab'],
['tabs.next?{"count":1}', 'Select next Tab'],
diff --git a/src/shared/messages.js b/src/shared/messages.js
index 1f9c816..dad2b7a 100644
--- a/src/shared/messages.js
+++ b/src/shared/messages.js
@@ -43,6 +43,11 @@ export default {
FOLLOW_ACTIVATE: 'follow.activate',
FOLLOW_KEY_PRESS: 'follow.key.press',
+ MARK_SET_GLOBAL: 'mark.set.global',
+ MARK_JUMP_GLOBAL: 'mark.jump.global',
+
+ TAB_SCROLL_TO: 'tab.scroll.to',
+
FIND_NEXT: 'find.next',
FIND_PREV: 'find.prev',
FIND_GET_KEYWORD: 'find.get.keyword',
diff --git a/src/shared/operations.js b/src/shared/operations.js
index b022537..778b1cf 100644
--- a/src/shared/operations.js
+++ b/src/shared/operations.js
@@ -69,4 +69,8 @@ export default {
FIND_START: 'find.start',
FIND_NEXT: 'find.next',
FIND_PREV: 'find.prev',
+
+ // Mark
+ MARK_SET_PREFIX: 'mark.set.prefix',
+ MARK_JUMP_PREFIX: 'mark.jump.prefix',
};
diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js
index 6feb9ec..9ba2d64 100644
--- a/src/shared/settings/default.js
+++ b/src/shared/settings/default.js
@@ -42,6 +42,8 @@ export default {
"zz": { "type": "zoom.neutral" },
"f": { "type": "follow.start", "newTab": false },
"F": { "type": "follow.start", "newTab": true, "background": false },
+ "m": { "type": "mark.set.prefix" },
+ "'": { "type": "mark.jump.prefix" },
"H": { "type": "navigate.history.prev" },
"L": { "type": "navigate.history.next" },
"[[": { "type": "navigate.link.prev" },