[wip] implement command usecases

jh-changes
Shin'ya Ueoka 6 years ago
parent 0846587baf
commit c4afd7237b
  1. 89
      src/background/controllers/command.js
  2. 43
      src/background/controllers/completions.js
  3. 13
      src/background/infrastructures/content-message-listener.js
  4. 0
      src/background/presenters/bookmark.js
  5. 16
      src/background/presenters/console.js
  6. 45
      src/background/presenters/tab.js
  7. 5
      src/background/presenters/window.js
  8. 13
      src/background/repositories/bookmark.js
  9. 114
      src/background/usecases/command.js
  10. 2
      src/background/usecases/completions.js

@ -0,0 +1,89 @@
import CompletionsInteractor from '../usecases/completions';
import CommandInteractor from '../usecases/command';
import Completions from '../domains/completions';
export default class CommandController {
constructor() {
this.completionsInteractor = new CompletionsInteractor();
this.commandIndicator = new CommandInteractor();
}
getCompletions(line) {
let trimmed = line.trimStart();
let words = trimmed.split(/ +/);
let name = words[0];
if (words.length === 1) {
return this.completionsInteractor.queryConsoleCommand(name);
}
let keywords = trimmed.slice(name.length).trimStart();
switch (words[0]) {
case 'o':
case 'open':
case 't':
case 'tabopen':
case 'w':
case 'winopen':
return this.completionsInteractor.queryOpen(name, keywords);
case 'b':
case 'buffer':
return this.completionsInteractor.queryBuffer(name, keywords);
case 'bd':
case 'bdel':
case 'bdelete':
case 'bdeletes':
return this.completionsInteractor.queryBdelete(name, keywords);
case 'bd!':
case 'bdel!':
case 'bdelete!':
case 'bdeletes!':
return this.completionsInteractor.queryBdeleteForce(name, keywords);
case 'set':
return this.completionsInteractor.querySet(name, keywords);
}
return Promise.resolve(Completions.empty());
}
// eslint-disable-next-line complexity
exec(line) {
let trimmed = line.trimStart();
let words = trimmed.split(/ +/);
let name = words[0];
let keywords = trimmed.slice(name.length).trimStart();
switch (words[0]) {
case 'o':
case 'open':
return this.commandIndicator.open(keywords);
case 't':
case 'tabopen':
return this.commandIndicator.tabopen(keywords);
case 'w':
case 'winopen':
return this.commandIndicator.winopen(keywords);
case 'b':
case 'buffer':
return this.commandIndicator.buffer(keywords);
case 'bd':
case 'bdel':
case 'bdelete':
return this.commandIndicator.bdelete(false, keywords);
case 'bd!':
case 'bdel!':
case 'bdelete!':
return this.commandIndicator.bdelete(true, keywords);
case 'bdeletes':
return this.commandIndicator.bdeletes(false, keywords);
case 'bdeletes!':
return this.commandIndicator.bdeletes(true, keywords);
case 'addbookmark':
return this.commandIndicator.addbookmark(keywords);
case 'q':
case 'quit':
return this.commandIndicator.quit();
case 'qa':
case 'quitall':
return this.commandIndicator.quitAll();
case 'set':
return this.commandIndicator.set(keywords);
}
}
}

@ -1,43 +0,0 @@
import CompletionsInteractor from '../usecases/completions';
import Completions from '../domains/completions';
export default class ContentMessageController {
constructor() {
this.completionsInteractor = new CompletionsInteractor();
}
getCompletions(line) {
let trimmed = line.trimStart();
let words = trimmed.split(/ +/);
let name = words[0];
if (words.length === 1) {
return this.completionsInteractor.queryConsoleCommand(name);
}
let keywords = trimmed.slice(name.length).trimStart();
switch (words[0]) {
case 'o':
case 'open':
case 't':
case 'tabopen':
case 'w':
case 'winopen':
return this.completionsInteractor.queryOpen(name, keywords);
case 'b':
case 'buffer':
return this.completionsInteractor.queryBuffer(name, keywords);
case 'bd':
case 'bdel':
case 'bdelete':
case 'bdeletes':
return this.completionsInteractor.queryBdelete(name, keywords);
case 'bd!':
case 'bdel!':
case 'bdelete!':
case 'bdeletes!':
return this.completionsInteractor.queryBdeleteForce(name, keywords);
case 'set':
return this.completionsInteractor.querySet(name, keywords);
}
return Promise.resolve(Completions.empty());
}
}

@ -1,5 +1,5 @@
import messages from '../../shared/messages'; import messages from '../../shared/messages';
import CompletionsController from '../controllers/completions'; import CommandController from '../controllers/command';
import SettingController from '../controllers/setting'; import SettingController from '../controllers/setting';
import FindController from '../controllers/find'; import FindController from '../controllers/find';
import AddonEnabledController from '../controllers/addon-enabled'; import AddonEnabledController from '../controllers/addon-enabled';
@ -8,7 +8,7 @@ import LinkController from '../controllers/link';
export default class ContentMessageListener { export default class ContentMessageListener {
constructor() { constructor() {
this.settingController = new SettingController(); this.settingController = new SettingController();
this.completionsController = new CompletionsController(); this.commandController = new CommandController();
this.findController = new FindController(); this.findController = new FindController();
this.addonEnabledController = new AddonEnabledController(); this.addonEnabledController = new AddonEnabledController();
this.linkController = new LinkController(); this.linkController = new LinkController();
@ -31,6 +31,8 @@ export default class ContentMessageListener {
switch (message.type) { switch (message.type) {
case messages.CONSOLE_QUERY_COMPLETIONS: case messages.CONSOLE_QUERY_COMPLETIONS:
return this.onConsoleQueryCompletions(message.text); return this.onConsoleQueryCompletions(message.text);
case messages.CONSOLE_ENTER_COMMAND:
return this.onConsoleEnterCommand(message.text);
case messages.SETTINGS_QUERY: case messages.SETTINGS_QUERY:
return this.onSettingsQuery(); return this.onSettingsQuery();
case messages.SETTINGS_RELOAD: case messages.SETTINGS_RELOAD:
@ -48,10 +50,15 @@ export default class ContentMessageListener {
} }
async onConsoleQueryCompletions(line) { async onConsoleQueryCompletions(line) {
let completions = await this.completionsController.getCompletions(line); let completions = await this.commandController.getCompletions(line);
return Promise.resolve(completions.serialize()); return Promise.resolve(completions.serialize());
} }
onConsoleEnterCommand(text) {
return this.commandController.exec(text);
}
onSettingsQuery() { onSettingsQuery() {
return this.settingController.getSetting(); return this.settingController.getSetting();
} }

@ -0,0 +1,16 @@
import messages from '../../shared/messages';
export default class ConsolePresenter {
showInfo(tabId, message) {
return browser.tabs.sendMessage(tabId, {
type: messages.CONSOLE_SHOW_INFO,
text: message,
});
}
showError(tabId, message) {
return browser.tabs.sendMessage(tabId, {
type: messages.CONSOLE_SHOW_ERROR,
text: message,
});
}
}

@ -3,8 +3,49 @@ export default class TabPresenter {
return browser.tabs.update(tabId, { url }); return browser.tabs.update(tabId, { url });
} }
create(url, { openerTabId, active }) { create(url, opts) {
return browser.tabs.create({ url, openerTabId, active }); return browser.tabs.create({ url, ...opts });
}
async getCurrent() {
let tabs = await browser.tabs.query({
active: true, currentWindow: true
});
return tabs[0];
}
getAll() {
return browser.tabs.query({ currentWindow: true });
}
async getByKeyword(keyword, excludePinned = false) {
let tabs = await browser.tabs.query({ currentWindow: true });
return tabs.filter((t) => {
return t.url.toLowerCase().includes(keyword.toLowerCase()) ||
t.title && t.title.toLowerCase().includes(keyword.toLowerCase());
}).filter((t) => {
return !(excludePinned && t.pinned);
});
}
select(tabId) {
return browser.tabs.update(tabId, { active: true });
}
async selectAt(index) {
let tabs = await browser.tabs.query({ currentWindow: true });
if (tabs.length < 2) {
return;
}
if (index < 0 || tabs.length <= index) {
throw new RangeError(`tab ${index + 1} does not exist`);
}
let id = tabs[index].id;
return browser.tabs.update(id, { active: true });
}
remove(ids) {
return browser.tabs.remove(ids);
} }
async createAdjacent(url, { openerTabId, active }) { async createAdjacent(url, { openerTabId, active }) {

@ -0,0 +1,5 @@
export default class WindowPresenter {
create(url) {
return browser.windows.create({ url });
}
}

@ -0,0 +1,13 @@
export default class BookmarkRepository {
async create(title, url) {
let item = await browser.bookmarks.create({
type: 'bookmark',
title,
url,
});
if (!item) {
throw new Error('Could not create a bookmark');
}
return item;
}
}

@ -0,0 +1,114 @@
import TabPresenter from '../presenters/tab';
import WindowPresenter from '../presenters/window';
import SettingRepository from '../repositories/setting';
import BookmarkRepository from '../repositories/bookmark';
import ConsolePresenter from '../presenters/console';
export default class CommandIndicator {
constructor() {
this.tabPresenter = new TabPresenter();
this.windowPresenter = new WindowPresenter();
this.settingRepository = new SettingRepository();
this.bookmarkRepository = new BookmarkRepository();
this.consolePresenter = new ConsolePresenter();
}
async open(keywords) {
let url = await this.urlOrSearch(keywords);
return this.tabPresenter.open(url);
}
async tabopen(keywords) {
let url = await this.urlOrSearch(keywords);
return this.tabPresenter.create(url);
}
async winopen(keywords) {
let url = await this.urlOrSearch(keywords);
return this.windowPresenter.create(url);
}
async buffer(keywords) {
if (keywords.length === 0) {
return;
}
if (!isNaN(keywords)) {
let index = parseInt(keywords, 10) - 1;
return tabs.selectAt(index);
}
let current = await this.tabPresenter.getCurrent();
let tabs = await this.tabPresenter.getByKeyword(keywords);
if (tabs.length === 0) {
throw new RangeError('No matching buffer for ' + keywords);
}
for (let tab of tabs) {
if (tab.index > current.index) {
return this.tabPresenter.select(tab.id);
}
}
return this.tabPresenter.select(tabs[0].id);
}
async bdelete(force, keywords) {
let excludePinned = !force;
let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned);
if (tabs.length === 0) {
throw new Error('No matching buffer for ' + keywords);
} else if (tabs.length > 1) {
throw new Error('More than one match for ' + keywords);
}
return this.tabPresenter.remove([tabs[0].id]);
}
async bdeletes(force, keywords) {
let excludePinned = !force;
let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned);
let ids = tabs.map(tab => tab.id);
return this.tabPresenter.remove(ids);
}
async quit() {
let tab = await this.tabPresenter.getCurrent();
return this.tabPresenter.remove([tab.id]);
}
async quitall() {
let tabs = await this.tabPresenter.getAll();
let ids = tabs.map(tab => tab.id);
this.tabPresenter.tabPresenter.remove(ids);
}
async addbookmark(title) {
let tab = await this.tabPresenter.getCurrent();
let item = await this.bookmarkRepository.create(title, tab.url);
let message = 'Saved current page: ' + item.url;
return this.consolePresenter.showInfo(tab.id, message);
}
set(keywords) {
// TODO implement set command
}
async urlOrSearch(keywords) {
try {
return new URL(keywords).href;
} catch (e) {
if (keywords.includes('.') && !keywords.includes(' ')) {
return 'http://' + keywords;
}
let settings = await this.settingRepository.get();
let engines = settings.search.engines;
let template = engines[settings.search.default];
let query = keywords;
let first = keywords.trimStart().split(' ')[0];
if (Object.keys(engines).includes(first)) {
template = engines[first];
query = keywords.trimStart().slice(first.length).trimStart();
}
return template.replace('{}', encodeURIComponent(query));
}
}
}

@ -50,7 +50,7 @@ export default class CompletionsInteractor {
} }
queryBuffer(name, keywords) { queryBuffer(name, keywords) {
return this.queryTabs(name, true, keywords); return this.queryTabs(name, false, keywords);
} }
queryBdelete(name, keywords) { queryBdelete(name, keywords) {