commit
8b72aac09a
24 changed files with 451 additions and 18 deletions
@ -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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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) || {}; |
||||
} |
||||
} |
||||
|
@ -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); |
||||
}); |
||||
} |
||||
} |
@ -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, |
||||
}; |
@ -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)); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
import GlobalMark from 'background/domains/global-mark'; |
||||
|
||||
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); |
||||
}); |
||||
}); |
@ -0,0 +1,26 @@ |
||||
import MarkRepository from 'background/repositories/mark'; |
||||
import GlobalMark from 'background/domains/global-mark'; |
||||
|
||||
describe('background/repositories/mark', () => { |
||||
let repository; |
||||
|
||||
beforeEach(() => { |
||||
repository = new MarkRepository; |
||||
}); |
||||
|
||||
it('get and set', async() => { |
||||
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; |
||||
}); |
||||
}); |
@ -0,0 +1,35 @@ |
||||
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', 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); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,41 @@ |
||||
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.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.setMode).to.be.true; |
||||
}); |
||||
|
||||
it('starts jump mode', () => { |
||||
let action = { type: actions.MARK_START_JUMP }; |
||||
let state = reducer(undefined, action); |
||||
expect(state.jumpMode).to.be.true; |
||||
}); |
||||
|
||||
it('cancels set and jump mode', () => { |
||||
let action = { type: actions.MARK_CANCEL }; |
||||
let state = reducer({ setMode: true }, action); |
||||
expect(state.setMode).to.be.false; |
||||
|
||||
state = reducer({ jumpMode: true }, action); |
||||
expect(state.jumpMode).to.be.false; |
||||
}); |
||||
|
||||
it('stores local mark', () => { |
||||
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'].x).to.equal(20) |
||||
expect(state.marks['a'].y).to.equal(30) |
||||
}); |
||||
}); |
Reference in new issue