commit
						8b72aac09a
					
				
					 24 changed files with 451 additions and 18 deletions
				
			
		
							
								
								
									
										15
									
								
								src/background/controllers/mark.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/background/controllers/mark.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								src/background/domains/global-mark.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/background/domains/global-mark.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -22,4 +22,12 @@ export default class ContentMessageClient { | ||||||
|       type: messages.ADDON_TOGGLE_ENABLED, |       type: messages.ADDON_TOGGLE_ENABLED, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   scrollTo(tabId, x, y) { | ||||||
|  |     return browser.tabs.sendMessage(tabId, { | ||||||
|  |       type: messages.TAB_SCROLL_TO, | ||||||
|  |       x, | ||||||
|  |       y, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import FindController from '../controllers/find'; | ||||||
| import AddonEnabledController from '../controllers/addon-enabled'; | import AddonEnabledController from '../controllers/addon-enabled'; | ||||||
| import LinkController from '../controllers/link'; | import LinkController from '../controllers/link'; | ||||||
| import OperationController from '../controllers/operation'; | import OperationController from '../controllers/operation'; | ||||||
|  | import MarkController from '../controllers/mark'; | ||||||
| 
 | 
 | ||||||
| export default class ContentMessageListener { | export default class ContentMessageListener { | ||||||
|   constructor() { |   constructor() { | ||||||
|  | @ -14,6 +15,7 @@ export default class ContentMessageListener { | ||||||
|     this.addonEnabledController = new AddonEnabledController(); |     this.addonEnabledController = new AddonEnabledController(); | ||||||
|     this.linkController = new LinkController(); |     this.linkController = new LinkController(); | ||||||
|     this.backgroundOperationController = new OperationController(); |     this.backgroundOperationController = new OperationController(); | ||||||
|  |     this.markController = new MarkController(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   run() { |   run() { | ||||||
|  | @ -59,6 +61,10 @@ export default class ContentMessageListener { | ||||||
|         message.newTab, message.url, sender.tab.id, message.background); |         message.newTab, message.url, sender.tab.id, message.background); | ||||||
|     case messages.BACKGROUND_OPERATION: |     case messages.BACKGROUND_OPERATION: | ||||||
|       return this.onBackgroundOperation(message.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) { |   onBackgroundOperation(operation) { | ||||||
|     return this.backgroundOperationController.exec(operation); |     return this.backgroundOperationController.exec(operation); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   onMarkSetGlobal(key, x, y) { | ||||||
|  |     return this.markController.setGlobal(key, x, y); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   onMarkJumpGlobal(key) { | ||||||
|  |     return this.markController.jumpGlobal(key); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										33
									
								
								src/background/repositories/mark.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/background/repositories/mark.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) || {}; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										39
									
								
								src/background/usecases/mark.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/background/usecases/mark.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -22,4 +22,10 @@ export default { | ||||||
| 
 | 
 | ||||||
|   // Find
 |   // Find
 | ||||||
|   FIND_SET_KEYWORD: 'find.set.keyword', |   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', | ||||||
| }; | }; | ||||||
|  |  | ||||||
							
								
								
									
										46
									
								
								src/content/actions/mark.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/content/actions/mark.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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, | ||||||
|  | }; | ||||||
|  | @ -6,6 +6,7 @@ import * as focuses from 'content/focuses'; | ||||||
| import * as urls from 'content/urls'; | import * as urls from 'content/urls'; | ||||||
| import * as consoleFrames from 'content/console-frames'; | import * as consoleFrames from 'content/console-frames'; | ||||||
| import * as addonActions from './addon'; | import * as addonActions from './addon'; | ||||||
|  | import * as markActions from './mark'; | ||||||
| import * as properties from 'shared/settings/properties'; | import * as properties from 'shared/settings/properties'; | ||||||
| 
 | 
 | ||||||
| // eslint-disable-next-line complexity, max-lines-per-function
 | // 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); |     scrolls.scrollPages(operation.count, smoothscroll, repeat); | ||||||
|     break; |     break; | ||||||
|   case operations.SCROLL_TOP: |   case operations.SCROLL_TOP: | ||||||
|     scrolls.scrollTop(smoothscroll, repeat); |     scrolls.scrollToTop(smoothscroll); | ||||||
|     break; |     break; | ||||||
|   case operations.SCROLL_BOTTOM: |   case operations.SCROLL_BOTTOM: | ||||||
|     scrolls.scrollBottom(smoothscroll, repeat); |     scrolls.scrollToBottom(smoothscroll); | ||||||
|     break; |     break; | ||||||
|   case operations.SCROLL_HOME: |   case operations.SCROLL_HOME: | ||||||
|     scrolls.scrollHome(smoothscroll, repeat); |     scrolls.scrollToHome(smoothscroll); | ||||||
|     break; |     break; | ||||||
|   case operations.SCROLL_END: |   case operations.SCROLL_END: | ||||||
|     scrolls.scrollEnd(smoothscroll, repeat); |     scrolls.scrollToEnd(smoothscroll); | ||||||
|     break; |     break; | ||||||
|   case operations.FOLLOW_START: |   case operations.FOLLOW_START: | ||||||
|     window.top.postMessage(JSON.stringify({ |     window.top.postMessage(JSON.stringify({ | ||||||
|  | @ -57,6 +58,10 @@ const exec = (operation, repeat, settings, addonEnabled) => { | ||||||
|       background: operation.background, |       background: operation.background, | ||||||
|     }), '*'); |     }), '*'); | ||||||
|     break; |     break; | ||||||
|  |   case operations.MARK_SET_PREFIX: | ||||||
|  |     return markActions.startSet(); | ||||||
|  |   case operations.MARK_JUMP_PREFIX: | ||||||
|  |     return markActions.startJump(); | ||||||
|   case operations.NAVIGATE_HISTORY_PREV: |   case operations.NAVIGATE_HISTORY_PREV: | ||||||
|     navigates.historyPrev(window); |     navigates.historyPrev(window); | ||||||
|     break; |     break; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import InputComponent from './input'; | import InputComponent from './input'; | ||||||
| import KeymapperComponent from './keymapper'; |  | ||||||
| import FollowComponent from './follow'; | import FollowComponent from './follow'; | ||||||
|  | import MarkComponent from './mark'; | ||||||
|  | import KeymapperComponent from './keymapper'; | ||||||
| import * as settingActions from 'content/actions/setting'; | import * as settingActions from 'content/actions/setting'; | ||||||
| import messages from 'shared/messages'; | import messages from 'shared/messages'; | ||||||
| import * as addonActions from '../../actions/addon'; | import * as addonActions from '../../actions/addon'; | ||||||
|  | @ -8,11 +9,13 @@ import * as blacklists from 'shared/blacklists'; | ||||||
| 
 | 
 | ||||||
| export default class Common { | export default class Common { | ||||||
|   constructor(win, store) { |   constructor(win, store) { | ||||||
|     const follow = new FollowComponent(win, store); |  | ||||||
|     const input = new InputComponent(win.document.body, 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); |     const keymapper = new KeymapperComponent(store); | ||||||
| 
 | 
 | ||||||
|     input.onKey(key => follow.key(key)); |     input.onKey(key => follow.key(key)); | ||||||
|  |     input.onKey(key => mark.key(key)); | ||||||
|     input.onKey(key => keymapper.key(key)); |     input.onKey(key => keymapper.key(key)); | ||||||
| 
 | 
 | ||||||
|     this.win = win; |     this.win = win; | ||||||
|  |  | ||||||
							
								
								
									
										74
									
								
								src/content/components/common/mark.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/content/components/common/mark.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -3,6 +3,7 @@ import FollowController from './follow-controller'; | ||||||
| import FindComponent from './find'; | import FindComponent from './find'; | ||||||
| import * as consoleFrames from '../../console-frames'; | import * as consoleFrames from '../../console-frames'; | ||||||
| import messages from 'shared/messages'; | import messages from 'shared/messages'; | ||||||
|  | import * as scrolls from 'content/scrolls'; | ||||||
| 
 | 
 | ||||||
| export default class TopContent { | export default class TopContent { | ||||||
| 
 | 
 | ||||||
|  | @ -33,6 +34,8 @@ export default class TopContent { | ||||||
|         type: messages.ADDON_ENABLED_RESPONSE, |         type: messages.ADDON_ENABLED_RESPONSE, | ||||||
|         enabled: addonState.enabled, |         enabled: addonState.enabled, | ||||||
|       }); |       }); | ||||||
|  |     case messages.TAB_SCROLL_TO: | ||||||
|  |       return scrolls.scrollTo(message.x, message.y, false); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,7 +4,8 @@ import find from './find'; | ||||||
| import setting from './setting'; | import setting from './setting'; | ||||||
| import input from './input'; | import input from './input'; | ||||||
| import followController from './follow-controller'; | import followController from './follow-controller'; | ||||||
|  | import mark from './mark'; | ||||||
| 
 | 
 | ||||||
| export default combineReducers({ | export default combineReducers({ | ||||||
|   addon, find, setting, input, followController, |   addon, find, setting, input, followController, mark, | ||||||
| }); | }); | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								src/content/reducers/mark.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/content/reducers/mark.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -130,6 +130,11 @@ const scroller = (element, smooth, repeat) => { | ||||||
|   return new RoughtScroller(element); |   return new RoughtScroller(element); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | const getScroll = () => { | ||||||
|  |   let target = scrollTarget(); | ||||||
|  |   return { x: target.scrollLeft, y: target.scrollTop }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| const scrollVertically = (count, smooth, repeat) => { | const scrollVertically = (count, smooth, repeat) => { | ||||||
|   let target = scrollTarget(); |   let target = scrollTarget(); | ||||||
|   let x = target.scrollLeft; |   let x = target.scrollLeft; | ||||||
|  | @ -158,35 +163,42 @@ const scrollPages = (count, smooth, repeat) => { | ||||||
|   scroller(target, smooth, repeat).scroll(x, y); |   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 target = scrollTarget(); | ||||||
|   let x = target.scrollLeft; |   let x = target.scrollLeft; | ||||||
|   let y = 0; |   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 target = scrollTarget(); | ||||||
|   let x = target.scrollLeft; |   let x = target.scrollLeft; | ||||||
|   let y = target.scrollHeight; |   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 target = scrollTarget(); | ||||||
|   let x = 0; |   let x = 0; | ||||||
|   let y = target.scrollTop; |   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 target = scrollTarget(); | ||||||
|   let x = target.scrollWidth; |   let x = target.scrollWidth; | ||||||
|   let y = target.scrollTop; |   let y = target.scrollTop; | ||||||
|   scroller(target, smooth, repeat).scroll(x, y); |   scroller(target, smooth, false).scroll(x, y); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export { | export { | ||||||
|  |   getScroll, | ||||||
|   scrollVertically, scrollHorizonally, scrollPages, |   scrollVertically, scrollHorizonally, scrollPages, | ||||||
|   scrollTop, scrollBottom, scrollHome, scrollEnd |   scrollTo, | ||||||
|  |   scrollToTop, scrollToBottom, scrollToHome, scrollToEnd | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -16,6 +16,9 @@ const KeyMapFields = [ | ||||||
|     ['scroll.pages?{"count":0.5}', 'Scroll down by half of screen'], |     ['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 up by a screen'], | ||||||
|     ['scroll.pages?{"count":1}', 'Scroll down 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.close', 'Close a tab'], | ||||||
|     ['tabs.reopen', 'Reopen closed tab'], |     ['tabs.reopen', 'Reopen closed tab'], | ||||||
|  |  | ||||||
|  | @ -43,6 +43,11 @@ export default { | ||||||
|   FOLLOW_ACTIVATE: 'follow.activate', |   FOLLOW_ACTIVATE: 'follow.activate', | ||||||
|   FOLLOW_KEY_PRESS: 'follow.key.press', |   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_NEXT: 'find.next', | ||||||
|   FIND_PREV: 'find.prev', |   FIND_PREV: 'find.prev', | ||||||
|   FIND_GET_KEYWORD: 'find.get.keyword', |   FIND_GET_KEYWORD: 'find.get.keyword', | ||||||
|  |  | ||||||
|  | @ -69,4 +69,8 @@ export default { | ||||||
|   FIND_START: 'find.start', |   FIND_START: 'find.start', | ||||||
|   FIND_NEXT: 'find.next', |   FIND_NEXT: 'find.next', | ||||||
|   FIND_PREV: 'find.prev', |   FIND_PREV: 'find.prev', | ||||||
|  | 
 | ||||||
|  |   // Mark
 | ||||||
|  |   MARK_SET_PREFIX: 'mark.set.prefix', | ||||||
|  |   MARK_JUMP_PREFIX: 'mark.jump.prefix', | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -42,6 +42,8 @@ export default { | ||||||
|     "zz": { "type": "zoom.neutral" }, |     "zz": { "type": "zoom.neutral" }, | ||||||
|     "f": { "type": "follow.start", "newTab": false }, |     "f": { "type": "follow.start", "newTab": false }, | ||||||
|     "F": { "type": "follow.start", "newTab": true, "background": false }, |     "F": { "type": "follow.start", "newTab": true, "background": false }, | ||||||
|  |     "m": { "type": "mark.set.prefix" }, | ||||||
|  |     "'": { "type": "mark.jump.prefix" }, | ||||||
|     "H": { "type": "navigate.history.prev" }, |     "H": { "type": "navigate.history.prev" }, | ||||||
|     "L": { "type": "navigate.history.next" }, |     "L": { "type": "navigate.history.next" }, | ||||||
|     "[[": { "type": "navigate.link.prev" }, |     "[[": { "type": "navigate.link.prev" }, | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								test/background/domains/global-mark.test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/background/domains/global-mark.test.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -1,8 +1,6 @@ | ||||||
| import MemoryStorage from 'background/infrastructures/memory-storage'; | import MemoryStorage from 'background/infrastructures/memory-storage'; | ||||||
| 
 | 
 | ||||||
| describe("background/infrastructures/memory-storage", () => { | describe("background/infrastructures/memory-storage", () => { | ||||||
|   let versionRepository; |  | ||||||
| 
 |  | ||||||
|   it('stores values', () => { |   it('stores values', () => { | ||||||
|     let cache = new MemoryStorage(); |     let cache = new MemoryStorage(); | ||||||
|     cache.set('number', 123); |     cache.set('number', 123); | ||||||
|  |  | ||||||
							
								
								
									
										26
									
								
								test/background/repositories/mark.test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								test/background/repositories/mark.test.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										35
									
								
								test/content/actions/mark.test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								test/content/actions/mark.test.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										41
									
								
								test/content/reducers/mark.test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								test/content/reducers/mark.test.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 a new issue