Clipbaord as a clean architecture
This commit is contained in:
parent
c6288f19d9
commit
8cef5981b8
7 changed files with 195 additions and 52 deletions
|
@ -3,15 +3,15 @@ import * as actions from './index';
|
||||||
import * as messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
import * as navigates from '../navigates';
|
import * as navigates from '../navigates';
|
||||||
import * as focuses from '../focuses';
|
import * as focuses from '../focuses';
|
||||||
import * as urls from '../urls';
|
|
||||||
import * as consoleFrames from '../console-frames';
|
|
||||||
import * as markActions from './mark';
|
import * as markActions from './mark';
|
||||||
|
|
||||||
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
|
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
|
||||||
|
import ClipboardUseCase from '../usecases/ClipboardUseCase';
|
||||||
import { SettingRepositoryImpl } from '../repositories/SettingRepository';
|
import { SettingRepositoryImpl } from '../repositories/SettingRepository';
|
||||||
import { ScrollPresenterImpl } from '../presenters/ScrollPresenter';
|
import { ScrollPresenterImpl } from '../presenters/ScrollPresenter';
|
||||||
|
|
||||||
let addonEnabledUseCase = new AddonEnabledUseCase();
|
let addonEnabledUseCase = new AddonEnabledUseCase();
|
||||||
|
let clipbaordUseCase = new ClipboardUseCase();
|
||||||
let settingRepository = new SettingRepositoryImpl();
|
let settingRepository = new SettingRepositoryImpl();
|
||||||
let scrollPresenter = new ScrollPresenterImpl();
|
let scrollPresenter = new ScrollPresenterImpl();
|
||||||
|
|
||||||
|
@ -95,13 +95,11 @@ const exec = async(
|
||||||
focuses.focusInput();
|
focuses.focusInput();
|
||||||
break;
|
break;
|
||||||
case operations.URLS_YANK:
|
case operations.URLS_YANK:
|
||||||
urls.yank(window);
|
await clipbaordUseCase.yankCurrentURL();
|
||||||
consoleFrames.postInfo('Yanked ' + window.location.href);
|
|
||||||
break;
|
break;
|
||||||
case operations.URLS_PASTE:
|
case operations.URLS_PASTE:
|
||||||
urls.paste(
|
await clipbaordUseCase.openOrSearch(
|
||||||
window, operation.newTab ? operation.newTab : false,
|
operation.newTab ? operation.newTab : false,
|
||||||
settings.search,
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
18
src/content/client/TabsClient.ts
Normal file
18
src/content/client/TabsClient.ts
Normal file
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
46
src/content/repositories/ClipboardRepository.ts
Normal file
46
src/content/repositories/ClipboardRepository.ts
Normal file
|
@ -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 };
|
|
44
src/content/usecases/ClipboardUseCase.ts
Normal file
44
src/content/usecases/ClipboardUseCase.ts
Normal file
|
@ -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 => {
|
const trimStart = (str: string): string => {
|
||||||
// NOTE String.trimStart is available on Firefox 61
|
// NOTE String.trimStart is available on Firefox 61
|
||||||
return str.replace(/^\s+/, '');
|
return str.replace(/^\s+/, '');
|
||||||
|
@ -5,7 +7,7 @@ const trimStart = (str: string): string => {
|
||||||
|
|
||||||
const SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ftp:', 'mailto:', 'about:'];
|
const SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ftp:', 'mailto:', 'about:'];
|
||||||
|
|
||||||
const searchUrl = (keywords: string, searchSettings: any): string => {
|
const searchUrl = (keywords: string, search: Search): string => {
|
||||||
try {
|
try {
|
||||||
let u = new URL(keywords);
|
let u = new URL(keywords);
|
||||||
if (SUPPORTED_PROTOCOLS.includes(u.protocol.toLowerCase())) {
|
if (SUPPORTED_PROTOCOLS.includes(u.protocol.toLowerCase())) {
|
||||||
|
@ -17,12 +19,12 @@ const searchUrl = (keywords: string, searchSettings: any): string => {
|
||||||
if (keywords.includes('.') && !keywords.includes(' ')) {
|
if (keywords.includes('.') && !keywords.includes(' ')) {
|
||||||
return 'http://' + keywords;
|
return 'http://' + keywords;
|
||||||
}
|
}
|
||||||
let template = searchSettings.engines[searchSettings.default];
|
let template = search.engines[search.default];
|
||||||
let query = keywords;
|
let query = keywords;
|
||||||
|
|
||||||
let first = trimStart(keywords).split(' ')[0];
|
let first = trimStart(keywords).split(' ')[0];
|
||||||
if (Object.keys(searchSettings.engines).includes(first)) {
|
if (Object.keys(search.engines).includes(first)) {
|
||||||
template = searchSettings.engines[first];
|
template = search.engines[first];
|
||||||
query = trimStart(trimStart(keywords).slice(first.length));
|
query = trimStart(trimStart(keywords).slice(first.length));
|
||||||
}
|
}
|
||||||
return template.replace('{}', encodeURIComponent(query));
|
return template.replace('{}', encodeURIComponent(query));
|
||||||
|
|
76
test/content/usecases/ClipboardUseCase.test.ts
Normal file
76
test/content/usecases/ClipboardUseCase.test.ts
Normal file
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Reference in a new issue