[wip] implement command usecases
This commit is contained in:
parent
0846587baf
commit
c4afd7237b
10 changed files with 291 additions and 49 deletions
89
src/background/controllers/command.js
Normal file
89
src/background/controllers/command.js
Normal file
|
@ -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 CompletionsController from '../controllers/completions';
|
||||
import CommandController from '../controllers/command';
|
||||
import SettingController from '../controllers/setting';
|
||||
import FindController from '../controllers/find';
|
||||
import AddonEnabledController from '../controllers/addon-enabled';
|
||||
|
@ -8,7 +8,7 @@ import LinkController from '../controllers/link';
|
|||
export default class ContentMessageListener {
|
||||
constructor() {
|
||||
this.settingController = new SettingController();
|
||||
this.completionsController = new CompletionsController();
|
||||
this.commandController = new CommandController();
|
||||
this.findController = new FindController();
|
||||
this.addonEnabledController = new AddonEnabledController();
|
||||
this.linkController = new LinkController();
|
||||
|
@ -31,6 +31,8 @@ export default class ContentMessageListener {
|
|||
switch (message.type) {
|
||||
case messages.CONSOLE_QUERY_COMPLETIONS:
|
||||
return this.onConsoleQueryCompletions(message.text);
|
||||
case messages.CONSOLE_ENTER_COMMAND:
|
||||
return this.onConsoleEnterCommand(message.text);
|
||||
case messages.SETTINGS_QUERY:
|
||||
return this.onSettingsQuery();
|
||||
case messages.SETTINGS_RELOAD:
|
||||
|
@ -48,10 +50,15 @@ export default class ContentMessageListener {
|
|||
}
|
||||
|
||||
async onConsoleQueryCompletions(line) {
|
||||
let completions = await this.completionsController.getCompletions(line);
|
||||
let completions = await this.commandController.getCompletions(line);
|
||||
return Promise.resolve(completions.serialize());
|
||||
}
|
||||
|
||||
onConsoleEnterCommand(text) {
|
||||
return this.commandController.exec(text);
|
||||
}
|
||||
|
||||
|
||||
onSettingsQuery() {
|
||||
return this.settingController.getSetting();
|
||||
}
|
||||
|
|
0
src/background/presenters/bookmark.js
Normal file
0
src/background/presenters/bookmark.js
Normal file
16
src/background/presenters/console.js
Normal file
16
src/background/presenters/console.js
Normal file
|
@ -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 });
|
||||
}
|
||||
|
||||
create(url, { openerTabId, active }) {
|
||||
return browser.tabs.create({ url, openerTabId, active });
|
||||
create(url, opts) {
|
||||
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 }) {
|
||||
|
|
5
src/background/presenters/window.js
Normal file
5
src/background/presenters/window.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default class WindowPresenter {
|
||||
create(url) {
|
||||
return browser.windows.create({ url });
|
||||
}
|
||||
}
|
13
src/background/repositories/bookmark.js
Normal file
13
src/background/repositories/bookmark.js
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
114
src/background/usecases/command.js
Normal file
114
src/background/usecases/command.js
Normal file
|
@ -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) {
|
||||
return this.queryTabs(name, true, keywords);
|
||||
return this.queryTabs(name, false, keywords);
|
||||
}
|
||||
|
||||
queryBdelete(name, keywords) {
|
||||
|
|
Reference in a new issue