diff options
author | Shin'ya Ueoka <ueokande@i-beam.org> | 2019-05-26 16:24:14 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-26 16:24:14 +0900 |
commit | cd584c8e243bafa8fc284279f716e8113607cd65 (patch) | |
tree | bc39bc30369f149e4ba4b6dc9c353b2906c4ef90 /src/background | |
parent | 07897df636ca3e732490d53fd2acf947738bf16e (diff) | |
parent | 34a96cdc9c5d7c8a11c6f1ae512fbc97724f61c4 (diff) |
Merge pull request #592 from ueokande/repeat-last-operation
Add "repeat last operation" command
Diffstat (limited to 'src/background')
-rw-r--r-- | src/background/clients/NavigateClient.ts | 29 | ||||
-rw-r--r-- | src/background/controllers/OperationController.ts | 36 | ||||
-rw-r--r-- | src/background/presenters/TabPresenter.ts | 4 | ||||
-rw-r--r-- | src/background/repositories/RepeatRepository.ts | 22 | ||||
-rw-r--r-- | src/background/usecases/CommandUseCase.ts | 17 | ||||
-rw-r--r-- | src/background/usecases/NavigateUseCase.ts | 57 | ||||
-rw-r--r-- | src/background/usecases/RepeatUseCase.ts | 50 | ||||
-rw-r--r-- | src/background/usecases/TabUseCase.ts | 15 |
8 files changed, 228 insertions, 2 deletions
diff --git a/src/background/clients/NavigateClient.ts b/src/background/clients/NavigateClient.ts new file mode 100644 index 0000000..bdd94ec --- /dev/null +++ b/src/background/clients/NavigateClient.ts @@ -0,0 +1,29 @@ +import { injectable } from 'tsyringe'; +import * as messages from '../../shared/messages'; + +@injectable() +export default class NavigateClient { + async historyNext(tabId: number): Promise<void> { + await browser.tabs.sendMessage(tabId, { + type: messages.NAVIGATE_HISTORY_NEXT, + }); + } + + async historyPrev(tabId: number): Promise<void> { + await browser.tabs.sendMessage(tabId, { + type: messages.NAVIGATE_HISTORY_PREV, + }); + } + + async linkNext(tabId: number): Promise<void> { + await browser.tabs.sendMessage(tabId, { + type: messages.NAVIGATE_LINK_NEXT, + }); + } + + async linkPrev(tabId: number): Promise<void> { + await browser.tabs.sendMessage(tabId, { + type: messages.NAVIGATE_LINK_PREV, + }); + } +} diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts index de6f8cb..51cff28 100644 --- a/src/background/controllers/OperationController.ts +++ b/src/background/controllers/OperationController.ts @@ -5,6 +5,8 @@ import ConsoleUseCase from '../usecases/ConsoleUseCase'; import TabUseCase from '../usecases/TabUseCase'; import TabSelectUseCase from '../usecases/TabSelectUseCase'; import ZoomUseCase from '../usecases/ZoomUseCase'; +import NavigateUseCase from '../usecases/NavigateUseCase'; +import RepeatUseCase from '../usecases/RepeatUseCase'; @injectable() export default class OperationController { @@ -14,11 +16,20 @@ export default class OperationController { private tabUseCase: TabUseCase, private tabSelectUseCase: TabSelectUseCase, private zoomUseCase: ZoomUseCase, + private navigateUseCase: NavigateUseCase, + private repeatUseCase: RepeatUseCase, ) { } + async exec(op: operations.Operation): Promise<any> { + await this.doOperation(op); + if (this.repeatUseCase.isRepeatable(op)) { + this.repeatUseCase.storeLastOperation(op); + } + } + // eslint-disable-next-line complexity, max-lines-per-function - exec(operation: operations.Operation): Promise<any> { + doOperation(operation: operations.Operation): Promise<any> { switch (operation.type) { case operations.TAB_CLOSE: return this.tabUseCase.close(false); @@ -74,6 +85,29 @@ export default class OperationController { return this.findUseCase.findStart(); case operations.CANCEL: return this.consoleUseCase.hideConsole(); + case operations.NAVIGATE_HISTORY_PREV: + return this.navigateUseCase.openHistoryPrev(); + case operations.NAVIGATE_HISTORY_NEXT: + return this.navigateUseCase.openHistoryNext(); + case operations.NAVIGATE_LINK_PREV: + return this.navigateUseCase.openLinkPrev(); + case operations.NAVIGATE_LINK_NEXT: + return this.navigateUseCase.openLinkNext(); + case operations.NAVIGATE_PARENT: + return this.navigateUseCase.openParent(); + case operations.NAVIGATE_ROOT: + return this.navigateUseCase.openRoot(); + case operations.REPEAT_LAST: + { + let last = this.repeatUseCase.getLastOperation(); + if (typeof last !== 'undefined') { + return this.doOperation(last); + } + return Promise.resolve(); + } + case operations.INTERNAL_OPEN_URL: + return this.tabUseCase.openURL( + operation.url, operation.newTab, operation.newWindow); } throw new Error('unknown operation: ' + operation.type); } diff --git a/src/background/presenters/TabPresenter.ts b/src/background/presenters/TabPresenter.ts index 5665bf0..ed88f26 100644 --- a/src/background/presenters/TabPresenter.ts +++ b/src/background/presenters/TabPresenter.ts @@ -36,7 +36,9 @@ export default class TabPresenter { return tabId; } - async getByKeyword(keyword: string, excludePinned = false): Promise<Tab[]> { + async getByKeyword( + keyword: string, excludePinned: boolean = false, + ): Promise<Tab[]> { let tabs = await browser.tabs.query({ currentWindow: true }); return tabs.filter((t) => { return t.url && t.url.toLowerCase().includes(keyword.toLowerCase()) || diff --git a/src/background/repositories/RepeatRepository.ts b/src/background/repositories/RepeatRepository.ts new file mode 100644 index 0000000..c7f7a71 --- /dev/null +++ b/src/background/repositories/RepeatRepository.ts @@ -0,0 +1,22 @@ +import { injectable } from 'tsyringe'; +import { Operation } from '../../shared/operations'; +import MemoryStorage from '../infrastructures/MemoryStorage'; + +const REPEAT_KEY = 'repeat'; + +@injectable() +export default class RepeatRepository { + private cache: MemoryStorage; + + constructor() { + this.cache = new MemoryStorage(); + } + + getLastOperation(): Operation | undefined { + return this.cache.get(REPEAT_KEY); + } + + setLastOperation(op: Operation): void { + this.cache.set(REPEAT_KEY, op); + } +} diff --git a/src/background/usecases/CommandUseCase.ts b/src/background/usecases/CommandUseCase.ts index 921a779..a526cfc 100644 --- a/src/background/usecases/CommandUseCase.ts +++ b/src/background/usecases/CommandUseCase.ts @@ -1,4 +1,5 @@ import { injectable } from 'tsyringe'; +import * as operations from '../../shared/operations'; import * as parsers from './parsers'; import * as urls from '../../shared/urls'; import TabPresenter from '../presenters/TabPresenter'; @@ -7,6 +8,7 @@ import SettingRepository from '../repositories/SettingRepository'; import BookmarkRepository from '../repositories/BookmarkRepository'; import ConsoleClient from '../infrastructures/ConsoleClient'; import ContentMessageClient from '../infrastructures/ContentMessageClient'; +import RepeatUseCase from '../usecases/RepeatUseCase'; @injectable() export default class CommandIndicator { @@ -17,21 +19,36 @@ export default class CommandIndicator { private bookmarkRepository: BookmarkRepository, private consoleClient: ConsoleClient, private contentMessageClient: ContentMessageClient, + private repeatUseCase: RepeatUseCase, ) { } async open(keywords: string): Promise<browser.tabs.Tab> { let url = await this.urlOrSearch(keywords); + this.repeatUseCase.storeLastOperation({ + type: operations.INTERNAL_OPEN_URL, + url, + }); return this.tabPresenter.open(url); } async tabopen(keywords: string): Promise<browser.tabs.Tab> { let url = await this.urlOrSearch(keywords); + this.repeatUseCase.storeLastOperation({ + type: operations.INTERNAL_OPEN_URL, + url, + newTab: true, + }); return this.tabPresenter.create(url); } async winopen(keywords: string): Promise<browser.windows.Window> { let url = await this.urlOrSearch(keywords); + this.repeatUseCase.storeLastOperation({ + type: operations.INTERNAL_OPEN_URL, + url, + newWindow: true, + }); return this.windowPresenter.create(url); } diff --git a/src/background/usecases/NavigateUseCase.ts b/src/background/usecases/NavigateUseCase.ts new file mode 100644 index 0000000..152339a --- /dev/null +++ b/src/background/usecases/NavigateUseCase.ts @@ -0,0 +1,57 @@ +import { injectable } from 'tsyringe'; +import NavigateClient from '../clients/NavigateClient'; +import TabPresenter from '../presenters/TabPresenter'; + +@injectable() +export default class NavigateUseCase { + constructor( + private tabPresenter: TabPresenter, + private navigateClient: NavigateClient, + ) { + } + + async openHistoryNext(): Promise<void> { + let tab = await this.tabPresenter.getCurrent(); + await this.navigateClient.historyNext(tab.id!!); + } + + async openHistoryPrev(): Promise<void> { + let tab = await this.tabPresenter.getCurrent(); + await this.navigateClient.historyPrev(tab.id!!); + } + + async openLinkNext(): Promise<void> { + let tab = await this.tabPresenter.getCurrent(); + await this.navigateClient.linkNext(tab.id!!); + } + + async openLinkPrev(): Promise<void> { + let tab = await this.tabPresenter.getCurrent(); + await this.navigateClient.linkPrev(tab.id!!); + } + + async openParent(): Promise<void> { + let tab = await this.tabPresenter.getCurrent(); + let url = new URL(tab.url!!); + if (url.hash.length > 0) { + url.hash = ''; + } else if (url.search.length > 0) { + url.search = ''; + } else { + const basenamePattern = /\/[^/]+$/; + const lastDirPattern = /\/[^/]+\/$/; + if (basenamePattern.test(url.pathname)) { + url.pathname = url.pathname.replace(basenamePattern, '/'); + } else if (lastDirPattern.test(url.pathname)) { + url.pathname = url.pathname.replace(lastDirPattern, '/'); + } + } + await this.tabPresenter.open(url.href); + } + + async openRoot(): Promise<void> { + let tab = await this.tabPresenter.getCurrent(); + let url = new URL(tab.url!!); + await this.tabPresenter.open(url.origin); + } +} diff --git a/src/background/usecases/RepeatUseCase.ts b/src/background/usecases/RepeatUseCase.ts new file mode 100644 index 0000000..d78de34 --- /dev/null +++ b/src/background/usecases/RepeatUseCase.ts @@ -0,0 +1,50 @@ +import { injectable } from 'tsyringe'; +import * as operations from '../../shared/operations'; +import RepeatRepository from '../repositories/RepeatRepository'; + +type Operation = operations.Operation; + +@injectable() +export default class RepeatUseCase { + constructor( + private repeatRepository: RepeatRepository, + ) { + } + + storeLastOperation(op: Operation): void { + this.repeatRepository.setLastOperation(op); + } + + getLastOperation(): operations.Operation | undefined { + return this.repeatRepository.getLastOperation(); + } + + // eslint-disable-next-line complexity + isRepeatable(op: Operation): boolean { + switch (op.type) { + case operations.NAVIGATE_HISTORY_PREV: + case operations.NAVIGATE_HISTORY_NEXT: + case operations.NAVIGATE_LINK_PREV: + case operations.NAVIGATE_LINK_NEXT: + case operations.NAVIGATE_PARENT: + case operations.NAVIGATE_ROOT: + case operations.PAGE_SOURCE: + case operations.PAGE_HOME: + case operations.TAB_CLOSE: + case operations.TAB_CLOSE_FORCE: + case operations.TAB_CLOSE_RIGHT: + case operations.TAB_REOPEN: + case operations.TAB_RELOAD: + case operations.TAB_PIN: + case operations.TAB_UNPIN: + case operations.TAB_TOGGLE_PINNED: + case operations.TAB_DUPLICATE: + case operations.ZOOM_IN: + case operations.ZOOM_OUT: + case operations.ZOOM_NEUTRAL: + case operations.INTERNAL_OPEN_URL: + return true; + } + return false; + } +} diff --git a/src/background/usecases/TabUseCase.ts b/src/background/usecases/TabUseCase.ts index 0239a87..31112a9 100644 --- a/src/background/usecases/TabUseCase.ts +++ b/src/background/usecases/TabUseCase.ts @@ -1,11 +1,13 @@ import { injectable } from 'tsyringe'; import TabPresenter from '../presenters/TabPresenter'; +import WindowPresenter from '../presenters/WindowPresenter'; import BrowserSettingRepository from '../repositories/BrowserSettingRepository'; @injectable() export default class TabUseCase { constructor( private tabPresenter: TabPresenter, + private windowPresenter: WindowPresenter, private browserSettingRepository: BrowserSettingRepository, ) { } @@ -77,4 +79,17 @@ export default class TabUseCase { this.tabPresenter.create(url); } } + + async openURL( + url: string, newTab?: boolean, newWindow?: boolean, + ): Promise<void> { + if (newWindow) { + await this.windowPresenter.create(url); + } else if (newTab) { + await this.tabPresenter.create(url); + } else { + let tab = await this.tabPresenter.getCurrent(); + await this.tabPresenter.open(url, tab.id); + } + } } |