[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 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
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 });
|
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 }) {
|
||||||
|
|
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) {
|
queryBuffer(name, keywords) {
|
||||||
return this.queryTabs(name, true, keywords);
|
return this.queryTabs(name, false, keywords);
|
||||||
}
|
}
|
||||||
|
|
||||||
queryBdelete(name, keywords) {
|
queryBdelete(name, keywords) {
|
||||||
|
|
Reference in a new issue