Clipbaord as a clean architecture

jh-changes
Shin'ya Ueoka 5 years ago
parent c6288f19d9
commit 8cef5981b8
  1. 12
      src/content/actions/operation.ts
  2. 18
      src/content/client/TabsClient.ts
  3. 46
      src/content/repositories/ClipboardRepository.ts
  4. 41
      src/content/urls.ts
  5. 44
      src/content/usecases/ClipboardUseCase.ts
  6. 10
      src/shared/urls.ts
  7. 76
      test/content/usecases/ClipboardUseCase.test.ts

@ -3,15 +3,15 @@ import * as actions from './index';
import * as messages from '../../shared/messages';
import * as navigates from '../navigates';
import * as focuses from '../focuses';
import * as urls from '../urls';
import * as consoleFrames from '../console-frames';
import * as markActions from './mark';
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
import ClipboardUseCase from '../usecases/ClipboardUseCase';
import { SettingRepositoryImpl } from '../repositories/SettingRepository';
import { ScrollPresenterImpl } from '../presenters/ScrollPresenter';
let addonEnabledUseCase = new AddonEnabledUseCase();
let clipbaordUseCase = new ClipboardUseCase();
let settingRepository = new SettingRepositoryImpl();
let scrollPresenter = new ScrollPresenterImpl();
@ -95,13 +95,11 @@ const exec = async(
focuses.focusInput();
break;
case operations.URLS_YANK:
urls.yank(window);
consoleFrames.postInfo('Yanked ' + window.location.href);
await clipbaordUseCase.yankCurrentURL();
break;
case operations.URLS_PASTE:
urls.paste(
window, operation.newTab ? operation.newTab : false,
settings.search,
await clipbaordUseCase.openOrSearch(
operation.newTab ? operation.newTab : false,
);
break;
default:

@ -0,0 +1,18 @@
import * as messages from '../../shared/messages';
export default interface TabsClient {
openUrl(url: string, newTab: boolean): Promise<void>;
// eslint-disable-next-line semi
}
export class TabsClientImpl {
async openUrl(url: string, newTab: boolean): Promise<void> {
await browser.runtime.sendMessage({
type: messages.OPEN_URL,
url,
newTab,
});
}
}

@ -0,0 +1,46 @@
export default interface ClipboardRepository {
read(): string;
write(text: string): void;
// eslint-disable-next-line semi
}
export class ClipboardRepositoryImpl {
read(): string {
let textarea = window.document.createElement('textarea');
window.document.body.append(textarea);
textarea.style.position = 'fixed';
textarea.style.top = '-100px';
textarea.contentEditable = 'true';
textarea.focus();
let ok = window.document.execCommand('paste');
let value = textarea.textContent!!;
textarea.remove();
if (!ok) {
throw new Error('failed to access clipbaord');
}
return value;
}
write(text: string): void {
let input = window.document.createElement('input');
window.document.body.append(input);
input.style.position = 'fixed';
input.style.top = '-100px';
input.value = text;
input.select();
let ok = window.document.execCommand('copy');
input.remove();
if (!ok) {
throw new Error('failed to access clipbaord');
}
}
}

@ -1,41 +0,0 @@
import * as messages from '../shared/messages';
import * as urls from '../shared/urls';
import { Search } from '../shared/Settings';
const yank = (win: Window) => {
let input = win.document.createElement('input');
win.document.body.append(input);
input.style.position = 'fixed';
input.style.top = '-100px';
input.value = win.location.href;
input.select();
win.document.execCommand('copy');
input.remove();
};
const paste = (win: Window, newTab: boolean, search: Search) => {
let textarea = win.document.createElement('textarea');
win.document.body.append(textarea);
textarea.style.position = 'fixed';
textarea.style.top = '-100px';
textarea.contentEditable = 'true';
textarea.focus();
if (win.document.execCommand('paste')) {
let value = textarea.textContent as string;
let url = urls.searchUrl(value, search);
browser.runtime.sendMessage({
type: messages.OPEN_URL,
url,
newTab,
});
}
textarea.remove();
};
export { yank, paste };

@ -0,0 +1,44 @@
import * as urls from '../../shared/urls';
import ClipboardRepository, { ClipboardRepositoryImpl }
from '../repositories/ClipboardRepository';
import SettingRepository, { SettingRepositoryImpl }
from '../repositories/SettingRepository';
import TabsClient, { TabsClientImpl }
from '../client/TabsClient';
import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient';
export default class ClipboardUseCase {
private repository: ClipboardRepository;
private settingRepository: SettingRepository;
private client: TabsClient;
private consoleClient: ConsoleClient;
constructor({
repository = new ClipboardRepositoryImpl(),
settingRepository = new SettingRepositoryImpl(),
client = new TabsClientImpl(),
consoleClient = new ConsoleClientImpl(),
} = {}) {
this.repository = repository;
this.settingRepository = settingRepository;
this.client = client;
this.consoleClient = consoleClient;
}
async yankCurrentURL(): Promise<string> {
let url = window.location.href;
this.repository.write(url);
await this.consoleClient.info('Yanked ' + url);
return Promise.resolve(url);
}
async openOrSearch(newTab: boolean): Promise<void> {
let search = this.settingRepository.get().search;
let text = this.repository.read();
let url = urls.searchUrl(text, search);
await this.client.openUrl(url, newTab);
}
}

@ -1,3 +1,5 @@
import { Search } from './Settings';
const trimStart = (str: string): string => {
// NOTE String.trimStart is available on Firefox 61
return str.replace(/^\s+/, '');
@ -5,7 +7,7 @@ const trimStart = (str: string): string => {
const SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ftp:', 'mailto:', 'about:'];
const searchUrl = (keywords: string, searchSettings: any): string => {
const searchUrl = (keywords: string, search: Search): string => {
try {
let u = new URL(keywords);
if (SUPPORTED_PROTOCOLS.includes(u.protocol.toLowerCase())) {
@ -17,12 +19,12 @@ const searchUrl = (keywords: string, searchSettings: any): string => {
if (keywords.includes('.') && !keywords.includes(' ')) {
return 'http://' + keywords;
}
let template = searchSettings.engines[searchSettings.default];
let template = search.engines[search.default];
let query = keywords;
let first = trimStart(keywords).split(' ')[0];
if (Object.keys(searchSettings.engines).includes(first)) {
template = searchSettings.engines[first];
if (Object.keys(search.engines).includes(first)) {
template = search.engines[first];
query = trimStart(trimStart(keywords).slice(first.length));
}
return template.replace('{}', encodeURIComponent(query));

@ -0,0 +1,76 @@
import ClipboardRepository from '../../../src/content/repositories/ClipboardRepository';
import TabsClient from '../../../src/content/client/TabsClient';
import MockConsoleClient from '../mock/MockConsoleClient';
import ClipboardUseCase from '../../../src/content/usecases/ClipboardUseCase';
import { expect } from 'chai';
class MockClipboardRepository implements ClipboardRepository {
public clipboard: string;
constructor() {
this.clipboard = '';
}
read(): string {
return this.clipboard;
}
write(text: string): void {
this.clipboard = text;
}
}
class MockTabsClient implements TabsClient {
public last: string;
constructor() {
this.last = '';
}
openUrl(url: string, _newTab: boolean): Promise<void> {
this.last = url;
return Promise.resolve();
}
}
describe('ClipboardUseCase', () => {
let repository: MockClipboardRepository;
let client: MockTabsClient;
let consoleClient: MockConsoleClient;
let sut: ClipboardUseCase;
beforeEach(() => {
repository = new MockClipboardRepository();
client = new MockTabsClient();
consoleClient = new MockConsoleClient();
sut = new ClipboardUseCase({ repository, client: client, consoleClient });
});
describe('#yankCurrentURL', () => {
it('yanks current url', async () => {
let yanked = await sut.yankCurrentURL();
expect(yanked).to.equal(window.location.href);
expect(repository.clipboard).to.equal(yanked);
expect(consoleClient.text).to.equal('Yanked ' + yanked);
});
});
describe('#openOrSearch', () => {
it('opens url from the clipboard', async () => {
let url = 'https://github.com/ueokande/vim-vixen'
repository.clipboard = url;
await sut.openOrSearch(true);
expect(client.last).to.equal(url);
});
it('opens search results from the clipboard', async () => {
repository.clipboard = 'banana';
await sut.openOrSearch(true);
expect(client.last).to.equal('https://google.com/search?q=banana');
});
});
});