From bf8cfbcb35365f83c94851a78bb0ea797e9d9ae2 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Oct 2018 15:00:07 +0900 Subject: [PATCH 01/11] Add mark keymaps --- src/shared/operations.js | 4 ++++ src/shared/settings/default.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/shared/operations.js b/src/shared/operations.js index b022537..3a94ade 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..b2dffc4 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 }, + "'": { "type": "mark.set.prefix" }, + "m": { "type": "mark.jump.prefix" }, "H": { "type": "navigate.history.prev" }, "L": { "type": "navigate.history.next" }, "[[": { "type": "navigate.link.prev" }, From bf283c732ef9424d365ca1b09ce8b174f61c336c Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Oct 2018 15:00:50 +0900 Subject: [PATCH 02/11] Add mark action and reducer --- src/content/actions/index.js | 6 +++++ src/content/actions/mark.js | 25 +++++++++++++++++++ src/content/reducers/mark.js | 25 +++++++++++++++++++ test/content/actions/mark.test.js | 34 ++++++++++++++++++++++++++ test/content/reducers/mark.test.js | 39 ++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 src/content/actions/mark.js create mode 100644 src/content/reducers/mark.js create mode 100644 test/content/actions/mark.test.js create mode 100644 test/content/reducers/mark.test.js 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..baf5825 --- /dev/null +++ b/src/content/actions/mark.js @@ -0,0 +1,25 @@ +import actions from 'content/actions'; + +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, y) => { + return { + type: actions.MARK_SET_LOCAL, + key, + y, + }; +}; + +export { + startSet, startJump, cancel, setLocal, +}; diff --git a/src/content/reducers/mark.js b/src/content/reducers/mark.js new file mode 100644 index 0000000..b6a071f --- /dev/null +++ b/src/content/reducers/mark.js @@ -0,0 +1,25 @@ +import actions from 'content/actions'; + +const defaultState = { + set: false, + jump: false, + marks: {}, +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.MARK_START_SET: + return { ...state, set: true }; + case actions.MARK_START_JUMP: + return { ...state, jump: true }; + case actions.MARK_CANCEL: + return { ...state, set: false, jump: false }; + case actions.MARK_SET_LOCAL: { + let marks = { ...state.marks }; + marks[action.key] = { y: action.y }; + return { ...state, marks }; + } + default: + return state; + } +} diff --git a/test/content/actions/mark.test.js b/test/content/actions/mark.test.js new file mode 100644 index 0000000..47d31cd --- /dev/null +++ b/test/content/actions/mark.test.js @@ -0,0 +1,34 @@ +import actions from 'content/actions'; +import * as markActions from 'content/actions/mark'; + +describe('mark actions', () => { + describe('startSet', () => { + it('create MARK_START_SET action', () => { + let action = markActions.startSet(); + expect(action.type).to.equal(actions.MARK_START_SET); + }); + }); + + describe('startJump', () => { + it('create MARK_START_JUMP action', () => { + let action = markActions.startJump(); + expect(action.type).to.equal(actions.MARK_START_JUMP); + }); + }); + + describe('cancel', () => { + it('create MARK_CANCEL action', () => { + let action = markActions.cancel(); + expect(action.type).to.equal(actions.MARK_CANCEL); + }); + }); + + describe('setLocal', () => { + it('create setLocal action', () => { + let action = markActions.setLocal('a', 30); + expect(action.type).to.equal(actions.MARK_SET_LOCAL); + expect(action.key).to.equal('a'); + expect(action.y).to.equal(30); + }); + }); +}); diff --git a/test/content/reducers/mark.test.js b/test/content/reducers/mark.test.js new file mode 100644 index 0000000..39c04cd --- /dev/null +++ b/test/content/reducers/mark.test.js @@ -0,0 +1,39 @@ +import actions from 'content/actions'; +import reducer from 'content/reducers/mark'; + +describe("mark reducer", () => { + it('return the initial state', () => { + let state = reducer(undefined, {}); + expect(state.set).to.be.false; + expect(state.jump).to.be.false; + expect(state.marks).to.be.empty; + }); + + it('starts set mode', () => { + let action = { type: actions.MARK_START_SET }; + let state = reducer(undefined, action); + expect(state.set).to.be.true; + }); + + it('starts jump mode', () => { + let action = { type: actions.MARK_START_JUMP }; + let state = reducer(undefined, action); + expect(state.jump).to.be.true; + }); + + it('cancels set and jump mode', () => { + let action = { type: actions.MARK_CANCEL }; + let state = reducer({ set: true }, action); + expect(state.set).to.be.false; + + state = reducer({ jump: true }, action); + expect(state.jump).to.be.false; + }); + + it('stores local mark', () => { + let action = { type: actions.MARK_SET_LOCAL, key: 'a', y: 10 }; + let state = reducer(undefined, action); + expect(state.marks['a']).to.be.an('object') + expect(state.marks['a'].y).to.equal(10) + }); +}); From 2055dfb2fb0f3fa86d359984583789db155903a1 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Oct 2018 15:35:28 +0900 Subject: [PATCH 03/11] Rename mode acton and reducer --- src/content/actions/operation.js | 5 +++++ src/content/reducers/mark.js | 10 +++++----- test/content/reducers/mark.test.js | 16 ++++++++-------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js index 89d7fec..94aa356 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 @@ -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/reducers/mark.js b/src/content/reducers/mark.js index b6a071f..700c03f 100644 --- a/src/content/reducers/mark.js +++ b/src/content/reducers/mark.js @@ -1,19 +1,19 @@ import actions from 'content/actions'; const defaultState = { - set: false, - jump: false, + setMode: false, + jumpMode: false, marks: {}, }; export default function reducer(state = defaultState, action = {}) { switch (action.type) { case actions.MARK_START_SET: - return { ...state, set: true }; + return { ...state, setMode: true }; case actions.MARK_START_JUMP: - return { ...state, jump: true }; + return { ...state, jumpMode: true }; case actions.MARK_CANCEL: - return { ...state, set: false, jump: false }; + return { ...state, setMode: false, jumpMode: false }; case actions.MARK_SET_LOCAL: { let marks = { ...state.marks }; marks[action.key] = { y: action.y }; diff --git a/test/content/reducers/mark.test.js b/test/content/reducers/mark.test.js index 39c04cd..6358bf1 100644 --- a/test/content/reducers/mark.test.js +++ b/test/content/reducers/mark.test.js @@ -4,30 +4,30 @@ import reducer from 'content/reducers/mark'; describe("mark reducer", () => { it('return the initial state', () => { let state = reducer(undefined, {}); - expect(state.set).to.be.false; - expect(state.jump).to.be.false; + expect(state.setMode).to.be.false; + expect(state.jumpMode).to.be.false; expect(state.marks).to.be.empty; }); it('starts set mode', () => { let action = { type: actions.MARK_START_SET }; let state = reducer(undefined, action); - expect(state.set).to.be.true; + expect(state.setMode).to.be.true; }); it('starts jump mode', () => { let action = { type: actions.MARK_START_JUMP }; let state = reducer(undefined, action); - expect(state.jump).to.be.true; + expect(state.jumpMode).to.be.true; }); it('cancels set and jump mode', () => { let action = { type: actions.MARK_CANCEL }; - let state = reducer({ set: true }, action); - expect(state.set).to.be.false; + let state = reducer({ setMode: true }, action); + expect(state.setMode).to.be.false; - state = reducer({ jump: true }, action); - expect(state.jump).to.be.false; + state = reducer({ jumpMode: true }, action); + expect(state.jumpMode).to.be.false; }); it('stores local mark', () => { From 76a2250291ba1fd7c07b85a229055025c051ddcf Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Oct 2018 15:37:25 +0900 Subject: [PATCH 04/11] lint --- src/shared/operations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/operations.js b/src/shared/operations.js index 3a94ade..778b1cf 100644 --- a/src/shared/operations.js +++ b/src/shared/operations.js @@ -70,7 +70,7 @@ export default { FIND_NEXT: 'find.next', FIND_PREV: 'find.prev', - /// Mark + // Mark MARK_SET_PREFIX: 'mark.set.prefix', MARK_JUMP_PREFIX: 'mark.jump.prefix', }; From c89b92d9bb03a607704cd1ccf45d34a8b9001f1a Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Oct 2018 17:02:17 +0900 Subject: [PATCH 05/11] Make setMode false on mark is set --- src/content/reducers/mark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/reducers/mark.js b/src/content/reducers/mark.js index 700c03f..b6137f3 100644 --- a/src/content/reducers/mark.js +++ b/src/content/reducers/mark.js @@ -17,7 +17,7 @@ export default function reducer(state = defaultState, action = {}) { case actions.MARK_SET_LOCAL: { let marks = { ...state.marks }; marks[action.key] = { y: action.y }; - return { ...state, marks }; + return { ...state, setMode: false, marks }; } default: return state; From e6d990966a99f4a80606ef42da8da9e009826f43 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Oct 2018 17:02:50 +0900 Subject: [PATCH 06/11] Fix default key mappings --- src/shared/settings/default.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js index b2dffc4..9ba2d64 100644 --- a/src/shared/settings/default.js +++ b/src/shared/settings/default.js @@ -42,8 +42,8 @@ export default { "zz": { "type": "zoom.neutral" }, "f": { "type": "follow.start", "newTab": false }, "F": { "type": "follow.start", "newTab": true, "background": false }, - "'": { "type": "mark.set.prefix" }, - "m": { "type": "mark.jump.prefix" }, + "m": { "type": "mark.set.prefix" }, + "'": { "type": "mark.jump.prefix" }, "H": { "type": "navigate.history.prev" }, "L": { "type": "navigate.history.next" }, "[[": { "type": "navigate.link.prev" }, From f66e75575dd26b7f60e70c9856d8cd56770f74e7 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Oct 2018 17:20:02 +0900 Subject: [PATCH 07/11] Store x position into marks --- src/content/actions/mark.js | 3 ++- src/content/reducers/mark.js | 2 +- test/content/actions/mark.test.js | 3 ++- test/content/reducers/mark.test.js | 8 +++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/content/actions/mark.js b/src/content/actions/mark.js index baf5825..1f5174e 100644 --- a/src/content/actions/mark.js +++ b/src/content/actions/mark.js @@ -12,10 +12,11 @@ const cancel = () => { return { type: actions.MARK_CANCEL }; }; -const setLocal = (key, y) => { +const setLocal = (key, x, y) => { return { type: actions.MARK_SET_LOCAL, key, + x, y, }; }; diff --git a/src/content/reducers/mark.js b/src/content/reducers/mark.js index b6137f3..2c96cc5 100644 --- a/src/content/reducers/mark.js +++ b/src/content/reducers/mark.js @@ -16,7 +16,7 @@ export default function reducer(state = defaultState, action = {}) { return { ...state, setMode: false, jumpMode: false }; case actions.MARK_SET_LOCAL: { let marks = { ...state.marks }; - marks[action.key] = { y: action.y }; + marks[action.key] = { x: action.x, y: action.y }; return { ...state, setMode: false, marks }; } default: diff --git a/test/content/actions/mark.test.js b/test/content/actions/mark.test.js index 47d31cd..adbf06b 100644 --- a/test/content/actions/mark.test.js +++ b/test/content/actions/mark.test.js @@ -25,9 +25,10 @@ describe('mark actions', () => { describe('setLocal', () => { it('create setLocal action', () => { - let action = markActions.setLocal('a', 30); + let action = markActions.setLocal('a', 20, 30); expect(action.type).to.equal(actions.MARK_SET_LOCAL); expect(action.key).to.equal('a'); + expect(action.x).to.equal(20); expect(action.y).to.equal(30); }); }); diff --git a/test/content/reducers/mark.test.js b/test/content/reducers/mark.test.js index 6358bf1..76efbf7 100644 --- a/test/content/reducers/mark.test.js +++ b/test/content/reducers/mark.test.js @@ -31,9 +31,11 @@ describe("mark reducer", () => { }); it('stores local mark', () => { - let action = { type: actions.MARK_SET_LOCAL, key: 'a', y: 10 }; - let state = reducer(undefined, action); + let action = { type: actions.MARK_SET_LOCAL, key: 'a', x: 20, y: 30}; + let state = reducer({ setMode: true }, action); + expect(state.setMode).to.be.false; expect(state.marks['a']).to.be.an('object') - expect(state.marks['a'].y).to.equal(10) + expect(state.marks['a'].x).to.equal(20) + expect(state.marks['a'].y).to.equal(30) }); }); From 6e6e306275a8ee5632a22b1e30c807bd5ae9cc7e Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Oct 2018 17:42:38 +0900 Subject: [PATCH 08/11] Store local marks --- src/content/actions/operation.js | 8 ++-- src/content/components/common/index.js | 7 ++- src/content/components/common/mark.js | 59 ++++++++++++++++++++++++++ src/content/reducers/index.js | 3 +- src/content/scrolls.js | 30 +++++++++---- 5 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 src/content/components/common/mark.js diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js index 94aa356..1aeb8be 100644 --- a/src/content/actions/operation.js +++ b/src/content/actions/operation.js @@ -40,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({ 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..06b2657 --- /dev/null +++ b/src/content/components/common/mark.js @@ -0,0 +1,59 @@ +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; +}; + +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 (key.shiftKey) { + consoleFrames.postError(window.document, 'Globa marks not supported'); + } 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); + } +} 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/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 }; From 003742ec51aa7aea9214442bc0b611e2eb5eaf6e Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Fri, 12 Oct 2018 10:14:33 +0900 Subject: [PATCH 09/11] Support global marks which select a tab --- src/background/controllers/mark.js | 15 +++++++++ src/background/domains/global-mark.js | 19 +++++++++++ .../content-message-listener.js | 14 ++++++++ src/background/repositories/mark.js | 33 +++++++++++++++++++ src/background/usecases/mark.js | 29 ++++++++++++++++ src/content/actions/mark.js | 20 +++++++++++ src/content/components/common/mark.js | 15 +++++++-- src/shared/messages.js | 3 ++ test/background/domains/global-mark.test.js | 10 ++++++ .../infrastructures/memory-storage.test.js | 2 -- test/background/repositories/mark.test.js | 23 +++++++++++++ 11 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 src/background/controllers/mark.js create mode 100644 src/background/domains/global-mark.js create mode 100644 src/background/repositories/mark.js create mode 100644 src/background/usecases/mark.js create mode 100644 test/background/domains/global-mark.test.js create mode 100644 test/background/repositories/mark.test.js 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..77afdba --- /dev/null +++ b/src/background/domains/global-mark.js @@ -0,0 +1,19 @@ +export default class GlobalMark { + constructor(tabId, x, y) { + this.tabId0 = tabId; + this.x0 = x; + this.y0 = y; + } + + get tabId() { + return this.tabId0; + } + + get x() { + return this.x0; + } + + get y() { + return this.y0; + } +} 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..a1f6a16 --- /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.x, data.y); + return Promise.resolve(mark); + } + + setMark(key, mark) { + let marks = this.getOrEmptyMarks(); + marks[key] = { tabId: mark.tabId, 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..2cb3b45 --- /dev/null +++ b/src/background/usecases/mark.js @@ -0,0 +1,29 @@ +import GlobalMark from '../domains/global-mark'; +import TabPresenter from '../presenters/tab'; +import MarkRepository from '../repositories/mark'; +import ConsolePresenter from '../presenters/console'; + +export default class MarkInteractor { + constructor() { + this.tabPresenter = new TabPresenter(); + this.markRepository = new MarkRepository(); + this.consolePresenter = new ConsolePresenter(); + } + + async setGlobal(key, x, y) { + let tab = await this.tabPresenter.getCurrent(); + let mark = new GlobalMark(tab.id, 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'); + } + // TODO scroll pages and handle if tab is gone + return this.tabPresenter.select(mark.tabId); + } +} diff --git a/src/content/actions/mark.js b/src/content/actions/mark.js index 1f5174e..712a811 100644 --- a/src/content/actions/mark.js +++ b/src/content/actions/mark.js @@ -1,4 +1,5 @@ import actions from 'content/actions'; +import messages from 'shared/messages'; const startSet = () => { return { type: actions.MARK_START_SET }; @@ -21,6 +22,25 @@ const setLocal = (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/components/common/mark.js b/src/content/components/common/mark.js index 06b2657..ce35afa 100644 --- a/src/content/components/common/mark.js +++ b/src/content/components/common/mark.js @@ -30,8 +30,10 @@ export default class MarkComponent { if (key.ctrlKey || key.metaKey || key.altKey) { consoleFrames.postError(window.document, 'Unknown mark'); - } else if (key.shiftKey) { - consoleFrames.postError(window.document, 'Globa marks not supported'); + } else if (key.shiftKey && markStage.setMode) { + this.doSetGlobal(key); + } else if (key.shiftKey && markStage.jumpMode) { + this.doJumpGlobal(key); } else if (markStage.setMode) { this.doSet(key); } else if (markStage.jumpMode) { @@ -56,4 +58,13 @@ export default class MarkComponent { 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/shared/messages.js b/src/shared/messages.js index 1f9c816..cd076ee 100644 --- a/src/shared/messages.js +++ b/src/shared/messages.js @@ -43,6 +43,9 @@ export default { FOLLOW_ACTIVATE: 'follow.activate', FOLLOW_KEY_PRESS: 'follow.key.press', + MARK_SET_GLOBAL: 'mark.set.global', + MARK_JUMP_GLOBAL: 'mark.jump.global', + FIND_NEXT: 'find.next', FIND_PREV: 'find.prev', FIND_GET_KEYWORD: 'find.get.keyword', diff --git a/test/background/domains/global-mark.test.js b/test/background/domains/global-mark.test.js new file mode 100644 index 0000000..15d492c --- /dev/null +++ b/test/background/domains/global-mark.test.js @@ -0,0 +1,10 @@ +import GlobalMark from 'background/domains/global-mark'; + +describe("background/domains/global-mark", () => { + describe("constructor and getter", () => { + let mark = new GlobalMark(1, 10, 30); + expect(mark.tabId).to.equal(1); + expect(mark.x).to.equal(10); + expect(mark.y).to.equal(30); + }); +}); diff --git a/test/background/infrastructures/memory-storage.test.js b/test/background/infrastructures/memory-storage.test.js index 0fea895..8871749 100644 --- a/test/background/infrastructures/memory-storage.test.js +++ b/test/background/infrastructures/memory-storage.test.js @@ -1,8 +1,6 @@ import MemoryStorage from 'background/infrastructures/memory-storage'; describe("background/infrastructures/memory-storage", () => { - let versionRepository; - it('stores values', () => { let cache = new MemoryStorage(); cache.set('number', 123); diff --git a/test/background/repositories/mark.test.js b/test/background/repositories/mark.test.js new file mode 100644 index 0000000..94048ea --- /dev/null +++ b/test/background/repositories/mark.test.js @@ -0,0 +1,23 @@ +import MarkRepository from 'background/repositories/mark'; +import GlobalMark from 'background/domains/global-mark'; + +describe("background/repositories/version", () => { + let repository; + + beforeEach(() => { + repository = new MarkRepository; + }); + + it('get and set', async() => { + let mark = new GlobalMark(1, 10, 30); + + repository.setMark('A', mark); + + let got = await repository.getMark('A'); + expect(got).to.be.a('object'); + expect(got.tabId).to.equal(1); + + got = await repository.getMark('B'); + expect(got).to.be.undefined; + }); +}); From e248477ecb46596af734589615118ba573971dc0 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Fri, 12 Oct 2018 14:59:45 +0900 Subject: [PATCH 10/11] Scroll on global mark and handle gone tab --- src/background/domains/global-mark.js | 7 ++++++- .../infrastructures/content-message-client.js | 8 ++++++++ src/background/repositories/mark.js | 4 ++-- src/background/usecases/mark.js | 16 +++++++++++++--- src/content/components/common/mark.js | 8 ++++++-- src/content/components/top-content/index.js | 3 +++ src/shared/messages.js | 2 ++ test/background/domains/global-mark.test.js | 7 ++++--- test/background/repositories/mark.test.js | 7 +++++-- 9 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/background/domains/global-mark.js b/src/background/domains/global-mark.js index 77afdba..f0586f1 100644 --- a/src/background/domains/global-mark.js +++ b/src/background/domains/global-mark.js @@ -1,6 +1,7 @@ export default class GlobalMark { - constructor(tabId, x, y) { + constructor(tabId, url, x, y) { this.tabId0 = tabId; + this.url0 = url; this.x0 = x; this.y0 = y; } @@ -9,6 +10,10 @@ export default class GlobalMark { return this.tabId0; } + get url() { + return this.url0; + } + get x() { return this.x0; } 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/repositories/mark.js b/src/background/repositories/mark.js index a1f6a16..339a660 100644 --- a/src/background/repositories/mark.js +++ b/src/background/repositories/mark.js @@ -14,13 +14,13 @@ export default class MarkRepository { if (!data) { return Promise.resolve(undefined); } - let mark = new GlobalMark(data.tabId, data.x, data.y); + 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, x: mark.x, y: mark.y }; + marks[key] = { tabId: mark.tabId, url: mark.url, x: mark.x, y: mark.y }; this.cache.set(MARK_KEY, marks); return Promise.resolve(); diff --git a/src/background/usecases/mark.js b/src/background/usecases/mark.js index 2cb3b45..34b8a74 100644 --- a/src/background/usecases/mark.js +++ b/src/background/usecases/mark.js @@ -2,17 +2,19 @@ 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, x, y); + let mark = new GlobalMark(tab.id, tab.url, x, y); return this.markRepository.setMark(key, mark); } @@ -23,7 +25,15 @@ export default class MarkInteractor { if (!mark) { return this.consolePresenter.showError(current.id, 'Mark is not set'); } - // TODO scroll pages and handle if tab is gone - return this.tabPresenter.select(mark.tabId); + + 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/components/common/mark.js b/src/content/components/common/mark.js index ce35afa..1ed636b 100644 --- a/src/content/components/common/mark.js +++ b/src/content/components/common/mark.js @@ -7,6 +7,10 @@ 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; @@ -30,9 +34,9 @@ export default class MarkComponent { if (key.ctrlKey || key.metaKey || key.altKey) { consoleFrames.postError(window.document, 'Unknown mark'); - } else if (key.shiftKey && markStage.setMode) { + } else if (globalKey(key.key) && markStage.setMode) { this.doSetGlobal(key); - } else if (key.shiftKey && markStage.jumpMode) { + } else if (globalKey(key.key) && markStage.jumpMode) { this.doJumpGlobal(key); } else if (markStage.setMode) { this.doSet(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/shared/messages.js b/src/shared/messages.js index cd076ee..dad2b7a 100644 --- a/src/shared/messages.js +++ b/src/shared/messages.js @@ -46,6 +46,8 @@ export default { 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/test/background/domains/global-mark.test.js b/test/background/domains/global-mark.test.js index 15d492c..bdf1ea6 100644 --- a/test/background/domains/global-mark.test.js +++ b/test/background/domains/global-mark.test.js @@ -1,9 +1,10 @@ import GlobalMark from 'background/domains/global-mark'; -describe("background/domains/global-mark", () => { - describe("constructor and getter", () => { - let mark = new GlobalMark(1, 10, 30); +describe('background/domains/global-mark', () => { + describe('constructor and getter', () => { + let mark = new GlobalMark(1, 'http://example.com', 10, 30); expect(mark.tabId).to.equal(1); + expect(mark.url).to.equal('http://example.com'); expect(mark.x).to.equal(10); expect(mark.y).to.equal(30); }); diff --git a/test/background/repositories/mark.test.js b/test/background/repositories/mark.test.js index 94048ea..4f71f62 100644 --- a/test/background/repositories/mark.test.js +++ b/test/background/repositories/mark.test.js @@ -1,7 +1,7 @@ import MarkRepository from 'background/repositories/mark'; import GlobalMark from 'background/domains/global-mark'; -describe("background/repositories/version", () => { +describe('background/repositories/mark', () => { let repository; beforeEach(() => { @@ -9,13 +9,16 @@ describe("background/repositories/version", () => { }); it('get and set', async() => { - let mark = new GlobalMark(1, 10, 30); + let mark = new GlobalMark(1, 'http://example.com', 10, 30); repository.setMark('A', mark); let got = await repository.getMark('A'); expect(got).to.be.a('object'); expect(got.tabId).to.equal(1); + expect(got.url).to.equal('http://example.com'); + expect(got.x).to.equal(10); + expect(got.y).to.equal(30); got = await repository.getMark('B'); expect(got).to.be.undefined; From 3c40b74a3e8d87ba310b46e24d6465d48766e3e8 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 13 Oct 2018 09:46:26 +0900 Subject: [PATCH 11/11] Add mark fields to setting form --- src/settings/components/form/keymaps-form.jsx | 3 +++ 1 file changed, 3 insertions(+) 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 @@ -16,6 +16,9 @@ const KeyMapFields = [ ['scroll.pages?{"count":0.5}', 'Scroll down by half of screen'], ['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'],