aboutsummaryrefslogtreecommitdiff
path: root/src/background
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2019-05-26 16:24:14 +0900
committerGitHub <noreply@github.com>2019-05-26 16:24:14 +0900
commitcd584c8e243bafa8fc284279f716e8113607cd65 (patch)
treebc39bc30369f149e4ba4b6dc9c353b2906c4ef90 /src/background
parent07897df636ca3e732490d53fd2acf947738bf16e (diff)
parent34a96cdc9c5d7c8a11c6f1ae512fbc97724f61c4 (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.ts29
-rw-r--r--src/background/controllers/OperationController.ts36
-rw-r--r--src/background/presenters/TabPresenter.ts4
-rw-r--r--src/background/repositories/RepeatRepository.ts22
-rw-r--r--src/background/usecases/CommandUseCase.ts17
-rw-r--r--src/background/usecases/NavigateUseCase.ts57
-rw-r--r--src/background/usecases/RepeatUseCase.ts50
-rw-r--r--src/background/usecases/TabUseCase.ts15
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);
+ }
+ }
}