From 48e005dc825a5211b4d3e92ed06ad15e01c23d13 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 25 May 2019 21:55:45 +0900 Subject: [PATCH] Repeat open, tabopen and winopen command --- .../controllers/OperationController.ts | 3 ++ src/background/usecases/CommandUseCase.ts | 17 ++++++++++ src/background/usecases/RepeatUseCase.ts | 1 + src/background/usecases/TabUseCase.ts | 15 +++++++++ src/content/client/OperationClient.ts | 18 ++++++++++ src/content/usecases/ClipboardUseCase.ts | 10 ++++-- src/shared/operations.ts | 33 ++++++++++++++++++- 7 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts index 7233c0e..51cff28 100644 --- a/src/background/controllers/OperationController.ts +++ b/src/background/controllers/OperationController.ts @@ -105,6 +105,9 @@ export default class OperationController { } 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/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 { 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 { 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 { 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/RepeatUseCase.ts b/src/background/usecases/RepeatUseCase.ts index a005682..d78de34 100644 --- a/src/background/usecases/RepeatUseCase.ts +++ b/src/background/usecases/RepeatUseCase.ts @@ -42,6 +42,7 @@ export default class RepeatUseCase { 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 { + 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); + } + } } diff --git a/src/content/client/OperationClient.ts b/src/content/client/OperationClient.ts index d699fec..5dbe555 100644 --- a/src/content/client/OperationClient.ts +++ b/src/content/client/OperationClient.ts @@ -3,6 +3,10 @@ import * as messages from '../../shared/messages'; export default interface OperationClient { execBackgroundOp(op: operations.Operation): Promise; + + internalOpenUrl( + url: string, newTab?: boolean, background?: boolean, + ): Promise; } export class OperationClientImpl implements OperationClient { @@ -12,4 +16,18 @@ export class OperationClientImpl implements OperationClient { operation: op, }); } + + internalOpenUrl( + url: string, newTab?: boolean, background?: boolean, + ): Promise { + return browser.runtime.sendMessage({ + type: messages.BACKGROUND_OPERATION, + operation: { + type: operations.INTERNAL_OPEN_URL, + url, + newTab, + background, + }, + }); + } } diff --git a/src/content/usecases/ClipboardUseCase.ts b/src/content/usecases/ClipboardUseCase.ts index 8c4d621..c8fe719 100644 --- a/src/content/usecases/ClipboardUseCase.ts +++ b/src/content/usecases/ClipboardUseCase.ts @@ -2,16 +2,16 @@ import { injectable, inject } from 'tsyringe'; import * as urls from '../../shared/urls'; import ClipboardRepository from '../repositories/ClipboardRepository'; import SettingRepository from '../repositories/SettingRepository'; -import TabsClient from '../client/TabsClient'; import ConsoleClient from '../client/ConsoleClient'; +import OperationClient from '../client/OperationClient'; @injectable() export default class ClipboardUseCase { constructor( @inject('ClipboardRepository') private repository: ClipboardRepository, @inject('SettingRepository') private settingRepository: SettingRepository, - @inject('TabsClient') private client: TabsClient, @inject('ConsoleClient') private consoleClient: ConsoleClient, + @inject('OperationClient') private operationClinet: OperationClient, ) { } @@ -26,6 +26,10 @@ export default class ClipboardUseCase { let search = this.settingRepository.get().search; let text = this.repository.read(); let url = urls.searchUrl(text, search); - await this.client.openUrl(url, newTab); + + // TODO: Repeat pasting from clipboard instead of opening a certain url. + // 'Repeat last' command is implemented in the background script and cannot + // access to clipboard until Firefox 63. + await this.operationClinet.internalOpenUrl(url, newTab); } } diff --git a/src/shared/operations.ts b/src/shared/operations.ts index 0f0d0c0..2b03d9d 100644 --- a/src/shared/operations.ts +++ b/src/shared/operations.ts @@ -78,6 +78,9 @@ export const MARK_JUMP_PREFIX = 'mark.jump.prefix'; // Repeat export const REPEAT_LAST = 'repeat.last'; +// Internal +export const INTERNAL_OPEN_URL = 'internal.open.url'; + export interface CancelOperation { type: typeof CANCEL; } @@ -298,6 +301,14 @@ export interface RepeatLastOperation { type: typeof REPEAT_LAST; } +export interface InternalOpenUrl { + type: typeof INTERNAL_OPEN_URL; + url: string; + newTab?: boolean; + newWindow?: boolean; + background?: boolean; +} + export type Operation = CancelOperation | AddonEnableOperation | @@ -350,7 +361,8 @@ export type Operation = FindPrevOperation | MarkSetPrefixOperation | MarkJumpPrefixOperation | - RepeatLastOperation; + RepeatLastOperation | + InternalOpenUrl; const assertOptionalBoolean = (obj: any, name: string) => { if (Object.prototype.hasOwnProperty.call(obj, name) && @@ -366,6 +378,13 @@ const assertRequiredNumber = (obj: any, name: string) => { } }; +const assertRequiredString = (obj: any, name: string) => { + if (!Object.prototype.hasOwnProperty.call(obj, name) || + typeof obj[name] !== 'string') { + throw new TypeError(`Missing string parameter '${name}`); + } +}; + // eslint-disable-next-line complexity, max-lines-per-function export const valueOf = (o: any): Operation => { if (!Object.prototype.hasOwnProperty.call(o, 'type')) { @@ -409,6 +428,18 @@ export const valueOf = (o: any): Operation => { type: URLS_PASTE, newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab), }; + case INTERNAL_OPEN_URL: + assertOptionalBoolean(o, 'newTab'); + assertOptionalBoolean(o, 'newWindow'); + assertOptionalBoolean(o, 'background'); + assertRequiredString(o, 'url'); + return { + type: INTERNAL_OPEN_URL, + url: o.url, + newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab), + newWindow: Boolean(typeof o.newWindow === undefined ? false : o.newWindow), // eslint-disable-line max-len + background: Boolean(typeof o.background === undefined ? true : o.background), // eslint-disable-line max-len + }; case CANCEL: case ADDON_ENABLE: case ADDON_DISABLE: