Repeat open, tabopen and winopen command

jh-changes
Shin'ya Ueoka 5 years ago
parent a2ee6897bf
commit 48e005dc82
  1. 3
      src/background/controllers/OperationController.ts
  2. 17
      src/background/usecases/CommandUseCase.ts
  3. 1
      src/background/usecases/RepeatUseCase.ts
  4. 15
      src/background/usecases/TabUseCase.ts
  5. 18
      src/content/client/OperationClient.ts
  6. 10
      src/content/usecases/ClipboardUseCase.ts
  7. 33
      src/shared/operations.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);
}

@ -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);
}

@ -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;

@ -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);
}
}
}

@ -3,6 +3,10 @@ import * as messages from '../../shared/messages';
export default interface OperationClient {
execBackgroundOp(op: operations.Operation): Promise<void>;
internalOpenUrl(
url: string, newTab?: boolean, background?: boolean,
): Promise<void>;
}
export class OperationClientImpl implements OperationClient {
@ -12,4 +16,18 @@ export class OperationClientImpl implements OperationClient {
operation: op,
});
}
internalOpenUrl(
url: string, newTab?: boolean, background?: boolean,
): Promise<void> {
return browser.runtime.sendMessage({
type: messages.BACKGROUND_OPERATION,
operation: {
type: operations.INTERNAL_OPEN_URL,
url,
newTab,
background,
},
});
}
}

@ -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);
}
}

@ -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: