aboutsummaryrefslogtreecommitdiff
path: root/src/background/usecases
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2018-07-28 20:05:06 +0900
committerGitHub <noreply@github.com>2018-07-28 20:05:06 +0900
commited2bd7d75ee1e7aa1db7d03c3f908c740ded1983 (patch)
tree6ac3f5ac5126e1a07c958549c782aedd586c6534 /src/background/usecases
parent84a9655bb39e5902b417e124a0eb23d80808a6a7 (diff)
parent4bd2084ba7b23327c26a2d8b24dc4169c14bfa17 (diff)
Merge pull request #440 from ueokande/background-clean-architecture
Background clean architecture
Diffstat (limited to 'src/background/usecases')
-rw-r--r--src/background/usecases/addon-enabled.js29
-rw-r--r--src/background/usecases/command.js108
-rw-r--r--src/background/usecases/completions.js150
-rw-r--r--src/background/usecases/filters.js72
-rw-r--r--src/background/usecases/find.js15
-rw-r--r--src/background/usecases/link.js27
-rw-r--r--src/background/usecases/operation.js192
-rw-r--r--src/background/usecases/parsers.js55
-rw-r--r--src/background/usecases/setting.js31
-rw-r--r--src/background/usecases/version.js41
10 files changed, 720 insertions, 0 deletions
diff --git a/src/background/usecases/addon-enabled.js b/src/background/usecases/addon-enabled.js
new file mode 100644
index 0000000..d83192f
--- /dev/null
+++ b/src/background/usecases/addon-enabled.js
@@ -0,0 +1,29 @@
+import IndicatorPresenter from '../presenters/indicator';
+import TabPresenter from '../presenters/tab';
+import ContentMessageClient from '../infrastructures/content-message-client';
+
+export default class AddonEnabledInteractor {
+ constructor() {
+ this.indicatorPresentor = new IndicatorPresenter();
+
+ this.indicatorPresentor.onClick(tab => this.onIndicatorClick(tab.id));
+
+ this.tabPresenter = new TabPresenter();
+ this.tabPresenter.onSelected(info => this.onTabSelected(info.tabId));
+
+ this.contentMessageClient = new ContentMessageClient();
+ }
+
+ indicate(enabled) {
+ return this.indicatorPresentor.indicate(enabled);
+ }
+
+ onIndicatorClick(tabId) {
+ return this.contentMessageClient.toggleAddonEnabled(tabId);
+ }
+
+ async onTabSelected(tabId) {
+ let enabled = await this.contentMessageClient.getAddonEnabled(tabId);
+ return this.indicatorPresentor.indicate(enabled);
+ }
+}
diff --git a/src/background/usecases/command.js b/src/background/usecases/command.js
new file mode 100644
index 0000000..3dbf768
--- /dev/null
+++ b/src/background/usecases/command.js
@@ -0,0 +1,108 @@
+import * as parsers from './parsers';
+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';
+import ContentMessageClient from '../infrastructures/content-message-client';
+import * as properties from 'shared/settings/properties';
+
+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();
+
+ this.contentMessageClient = new ContentMessageClient();
+ }
+
+ 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.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);
+ }
+
+ async set(keywords) {
+ if (keywords.length === 0) {
+ return;
+ }
+ let [name, value] = parsers.parseSetOption(keywords, properties.types);
+ await this.settingRepository.setProperty(name, value);
+
+ return this.contentMessageClient.broadcastSettingsChanged();
+ }
+
+ async urlOrSearch(keywords) {
+ let settings = await this.settingRepository.get();
+ return parsers.normalizeUrl(keywords, settings.search);
+ }
+}
diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js
new file mode 100644
index 0000000..2bf5b3b
--- /dev/null
+++ b/src/background/usecases/completions.js
@@ -0,0 +1,150 @@
+import CompletionItem from '../domains/completion-item';
+import CompletionGroup from '../domains/completion-group';
+import Completions from '../domains/completions';
+import CommandDocs from '../domains/command-docs';
+import CompletionRepository from '../repositories/completions';
+import * as filters from './filters';
+import SettingRepository from '../repositories/setting';
+import * as properties from '../../shared/settings/properties';
+
+const COMPLETION_ITEM_LIMIT = 10;
+
+export default class CompletionsInteractor {
+ constructor() {
+ this.completionRepository = new CompletionRepository();
+ this.settingRepository = new SettingRepository();
+ }
+
+ queryConsoleCommand(prefix) {
+ let keys = Object.keys(CommandDocs);
+ let items = keys
+ .filter(name => name.startsWith(prefix))
+ .map(name => ({
+ caption: name,
+ content: name,
+ url: CommandDocs[name],
+ }));
+
+ if (items.length === 0) {
+ return Promise.resolve(Completions.empty());
+ }
+ return Promise.resolve(
+ new Completions([new CompletionGroup('Console Command', items)])
+ );
+ }
+
+ async queryOpen(name, keywords) {
+ let groups = [];
+ let engines = await this.querySearchEngineItems(name, keywords);
+ if (engines.length > 0) {
+ groups.push(new CompletionGroup('Search Engines', engines));
+ }
+ let histories = await this.queryHistoryItems(name, keywords);
+ if (histories.length > 0) {
+ groups.push(new CompletionGroup('History', histories));
+ }
+ let bookmarks = await this.queryBookmarkItems(name, keywords);
+ if (bookmarks.length > 0) {
+ groups.push(new CompletionGroup('Bookmarks', bookmarks));
+ }
+ return new Completions(groups);
+ }
+
+ queryBuffer(name, keywords) {
+ return this.queryTabs(name, false, keywords);
+ }
+
+ queryBdelete(name, keywords) {
+ return this.queryTabs(name, true, keywords);
+ }
+
+ queryBdeleteForce(name, keywords) {
+ return this.queryTabs(name, false, keywords);
+ }
+
+ querySet(name, keywords) {
+ let items = Object.keys(properties.docs).map((key) => {
+ if (properties.types[key] === 'boolean') {
+ return [
+ new CompletionItem({
+ caption: key,
+ content: name + ' ' + key,
+ url: 'Enable ' + properties.docs[key],
+ }),
+ new CompletionItem({
+ caption: 'no' + key,
+ content: name + ' no' + key,
+ url: 'Disable ' + properties.docs[key],
+ }),
+ ];
+ }
+ return [
+ new CompletionItem({
+ caption: key,
+ content: name + ' ' + key,
+ url: 'Set ' + properties.docs[key],
+ })
+ ];
+ });
+ items = items.reduce((acc, val) => acc.concat(val), []);
+ items = items.filter((item) => {
+ return item.caption.startsWith(keywords);
+ });
+ if (items.length === 0) {
+ return Promise.resolve(Completions.empty());
+ }
+ return Promise.resolve(
+ new Completions([new CompletionGroup('Properties', items)])
+ );
+ }
+
+ async queryTabs(name, excludePinned, args) {
+ let tabs = await this.completionRepository.queryTabs(args, excludePinned);
+ let items = tabs.map(tab => new CompletionItem({
+ caption: tab.title,
+ content: name + ' ' + tab.title,
+ url: tab.url,
+ icon: tab.favIconUrl
+ }));
+ if (items.length === 0) {
+ return Promise.resolve(Completions.empty());
+ }
+ return new Completions([new CompletionGroup('Buffers', items)]);
+ }
+
+ async querySearchEngineItems(name, keywords) {
+ let settings = await this.settingRepository.get();
+ let engines = Object.keys(settings.search.engines)
+ .filter(key => key.startsWith(keywords));
+ return engines.map(key => new CompletionItem({
+ caption: key,
+ content: name + ' ' + key,
+ }));
+ }
+
+ async queryHistoryItems(name, keywords) {
+ let histories = await this.completionRepository.queryHistories(keywords);
+ histories = [histories]
+ .map(filters.filterBlankTitle)
+ .map(filters.filterHttp)
+ .map(filters.filterByTailingSlash)
+ .map(pages => filters.filterByPathname(pages, COMPLETION_ITEM_LIMIT))
+ .map(pages => filters.filterByOrigin(pages, COMPLETION_ITEM_LIMIT))[0]
+ .sort((x, y) => x.visitCount < y.visitCount)
+ .slice(0, COMPLETION_ITEM_LIMIT);
+ return histories.map(page => new CompletionItem({
+ caption: page.title,
+ content: name + ' ' + page.url,
+ url: page.url
+ }));
+ }
+
+ async queryBookmarkItems(name, keywords) {
+ let bookmarks = await this.completionRepository.queryBookmarks(keywords);
+ return bookmarks.map(page => new CompletionItem({
+ caption: page.title,
+ content: name + ' ' + page.url,
+ url: page.url
+ }));
+ }
+}
diff --git a/src/background/usecases/filters.js b/src/background/usecases/filters.js
new file mode 100644
index 0000000..d057dca
--- /dev/null
+++ b/src/background/usecases/filters.js
@@ -0,0 +1,72 @@
+const filterHttp = (items) => {
+ let httpsHosts = items.map(x => new URL(x.url))
+ .filter(x => x.protocol === 'https:')
+ .map(x => x.host);
+ httpsHosts = new Set(httpsHosts);
+
+ return items.filter((item) => {
+ let url = new URL(item.url);
+ return url.protocol === 'https:' || !httpsHosts.has(url.host);
+ });
+};
+
+const filterBlankTitle = (items) => {
+ return items.filter(item => item.title && item.title !== '');
+};
+
+const filterByTailingSlash = (items) => {
+ let urls = items.map(item => new URL(item.url));
+ let simplePaths = urls
+ .filter(url => url.hash === '' && url.search === '')
+ .map(url => url.origin + url.pathname);
+ simplePaths = new Set(simplePaths);
+
+ return items.filter((item) => {
+ let url = new URL(item.url);
+ if (url.hash !== '' || url.search !== '' ||
+ url.pathname.slice(-1) !== '/') {
+ return true;
+ }
+ return !simplePaths.has(url.origin + url.pathname.slice(0, -1));
+ });
+};
+
+const filterByPathname = (items, min) => {
+ let hash = {};
+ for (let item of items) {
+ let url = new URL(item.url);
+ let pathname = url.origin + url.pathname;
+ if (!hash[pathname]) {
+ hash[pathname] = item;
+ } else if (hash[pathname].url.length > item.url.length) {
+ hash[pathname] = item;
+ }
+ }
+ let filtered = Object.values(hash);
+ if (filtered.length < min) {
+ return items;
+ }
+ return filtered;
+};
+
+const filterByOrigin = (items, min) => {
+ let hash = {};
+ for (let item of items) {
+ let origin = new URL(item.url).origin;
+ if (!hash[origin]) {
+ hash[origin] = item;
+ } else if (hash[origin].url.length > item.url.length) {
+ hash[origin] = item;
+ }
+ }
+ let filtered = Object.values(hash);
+ if (filtered.length < min) {
+ return items;
+ }
+ return filtered;
+};
+
+export {
+ filterHttp, filterBlankTitle, filterByTailingSlash,
+ filterByPathname, filterByOrigin
+};
diff --git a/src/background/usecases/find.js b/src/background/usecases/find.js
new file mode 100644
index 0000000..eae480d
--- /dev/null
+++ b/src/background/usecases/find.js
@@ -0,0 +1,15 @@
+import FindRepository from '../repositories/find';
+
+export default class FindInteractor {
+ constructor() {
+ this.findRepository = new FindRepository();
+ }
+
+ getKeyword() {
+ return this.findRepository.getKeyword();
+ }
+
+ setKeyword(keyword) {
+ return this.findRepository.setKeyword(keyword);
+ }
+}
diff --git a/src/background/usecases/link.js b/src/background/usecases/link.js
new file mode 100644
index 0000000..f9e474a
--- /dev/null
+++ b/src/background/usecases/link.js
@@ -0,0 +1,27 @@
+import SettingRepository from '../repositories/setting';
+import TabPresenter from '../presenters/tab';
+
+export default class LinkInteractor {
+ constructor() {
+ this.settingRepository = new SettingRepository();
+ this.tabPresenter = new TabPresenter();
+ }
+
+ openToTab(url, tabId) {
+ this.tabPresenter.open(url, tabId);
+
+ }
+
+ async openNewTab(url, openerId, background) {
+ let settings = await this.settingRepository.get();
+ let { adjacenttab } = settings.properties;
+ if (adjacenttab) {
+ return this.tabPresenter.create(url, {
+ openerTabId: openerId, active: !background
+ });
+ }
+ return this.tabPresenter.create(url, {
+ openerTabId: openerId, active: !background
+ });
+ }
+}
diff --git a/src/background/usecases/operation.js b/src/background/usecases/operation.js
new file mode 100644
index 0000000..86f39ca
--- /dev/null
+++ b/src/background/usecases/operation.js
@@ -0,0 +1,192 @@
+import MemoryStorage from '../infrastructures/memory-storage';
+import TabPresenter from '../presenters/tab';
+import ConsolePresenter from '../presenters/console';
+
+const CURRENT_SELECTED_KEY = 'tabs.current.selected';
+const LAST_SELECTED_KEY = 'tabs.last.selected';
+
+const ZOOM_SETTINGS = [
+ 0.33, 0.50, 0.66, 0.75, 0.80, 0.90, 1.00,
+ 1.10, 1.25, 1.50, 1.75, 2.00, 2.50, 3.00
+];
+
+export default class OperationInteractor {
+ constructor() {
+ this.tabPresenter = new TabPresenter();
+ this.tabPresenter.onSelected(info => this.onTabSelected(info.tabId));
+
+ this.consolePresenter = new ConsolePresenter();
+
+ this.cache = new MemoryStorage();
+ }
+
+ async close(force) {
+ let tab = await this.tabPresenter.getCurrent();
+ if (!force && tab.pinned) {
+ return;
+ }
+ return this.tabPresenter.remove([tab.id]);
+ }
+
+ reopen() {
+ return this.tabPresenter.reopen();
+ }
+
+ async selectPrev(count) {
+ let tabs = await this.tabPresenter.getAll();
+ if (tabs.length < 2) {
+ return;
+ }
+ let tab = tabs.find(t => t.active);
+ if (!tab) {
+ return;
+ }
+ let select = (tab.index - count + tabs.length) % tabs.length;
+ return this.tabPresenter.select(tabs[select].id);
+ }
+
+ async selectNext(count) {
+ let tabs = await this.tabPresenter.getAll();
+ if (tabs.length < 2) {
+ return;
+ }
+ let tab = tabs.find(t => t.active);
+ if (!tab) {
+ return;
+ }
+ let select = (tab.index + count) % tabs.length;
+ return this.tabPresenter.select(tabs[select].id);
+ }
+
+ async selectFirst() {
+ let tabs = await this.tabPresenter.getAll();
+ return this.tabPresenter.select(tabs[0].id);
+ }
+
+ async selectLast() {
+ let tabs = await this.tabPresenter.getAll();
+ return this.tabPresenter.select(tabs[tabs.length - 1].id);
+ }
+
+ async selectPrevSelected() {
+ let tabId = await this.cache.get(LAST_SELECTED_KEY);
+ if (tabId === null || typeof tabId === 'undefined') {
+ return;
+ }
+ this.tabPresenter.select(tabId);
+ }
+
+ async reload(cache) {
+ let tab = await this.tabPresenter.getCurrent();
+ return this.tabPresenter.reload(tab.id, cache);
+ }
+
+ async setPinned(pinned) {
+ let tab = await this.tabPresenter.getCurrent();
+ return this.tabPresenter.setPinned(tab.id, pinned);
+ }
+
+ async togglePinned() {
+ let tab = await this.tabPresenter.getCurrent();
+ return this.tabPresenter.setPinned(tab.id, !tab.pinned);
+ }
+
+ async duplicate() {
+ let tab = await this.tabPresenter.getCurrent();
+ return this.tabPresenter.duplicate(tab.id);
+ }
+
+ async openPageSource() {
+ let tab = await this.tabPresenter.getCurrent();
+ let url = 'view-source:' + tab.url;
+ return this.tabPresenter.create(url);
+ }
+
+ async zoomIn(tabId) {
+ let tab = await this.tabPresenter.getCurrent();
+ let current = await this.tabPresenter.getZoom(tab.id);
+ let factor = ZOOM_SETTINGS.find(f => f > current);
+ if (factor) {
+ return this.tabPresenter.setZoom(tabId, factor);
+ }
+ }
+
+ async zoomOut(tabId) {
+ let tab = await this.tabPresenter.getCurrent();
+ let current = await this.tabPresenter.getZoom(tab.id);
+ let factor = [].concat(ZOOM_SETTINGS).reverse().find(f => f < current);
+ if (factor) {
+ return this.tabPresenter.setZoom(tabId, factor);
+ }
+ }
+
+ zoomNutoral(tabId) {
+ return this.tabPresenter.setZoom(tabId, 1);
+ }
+
+ async showCommand() {
+ let tab = await this.tabPresenter.getCurrent();
+ this.consolePresenter.showCommand(tab.id, '');
+ }
+
+ async showOpenCommand(alter) {
+ let tab = await this.tabPresenter.getCurrent();
+ let command = 'open ';
+ if (alter) {
+ command += tab.url;
+ }
+ return this.consolePresenter.showCommand(tab.id, command);
+ }
+
+ async showTabopenCommand(alter) {
+ let tab = await this.tabPresenter.getCurrent();
+ let command = 'tabopen ';
+ if (alter) {
+ command += tab.url;
+ }
+ return this.consolePresenter.showCommand(tab.id, command);
+ }
+
+ async showWinopenCommand(alter) {
+ let tab = await this.tabPresenter.getCurrent();
+ let command = 'winopen ';
+ if (alter) {
+ command += tab.url;
+ }
+ return this.consolePresenter.showCommand(tab.id, command);
+ }
+
+ async showBufferCommand() {
+ let tab = await this.tabPresenter.getCurrent();
+ let command = 'buffer ';
+ return this.consolePresenter.showCommand(tab.id, command);
+ }
+
+ async showAddbookmarkCommand(alter) {
+ let tab = await this.tabPresenter.getCurrent();
+ let command = 'addbookmark ';
+ if (alter) {
+ command += tab.title;
+ }
+ return this.consolePresenter.showCommand(tab.id, command);
+ }
+
+ async findStart() {
+ let tab = await this.tabPresenter.getCurrent();
+ this.consolePresenter.showFind(tab.id);
+ }
+
+ async hideConsole() {
+ let tab = await this.tabPresenter.getCurrent();
+ this.consolePresenter.hide(tab.id);
+ }
+
+ onTabSelected(tabId) {
+ let lastId = this.cache.get(CURRENT_SELECTED_KEY);
+ if (lastId) {
+ this.cache.set(LAST_SELECTED_KEY, lastId);
+ }
+ this.cache.set(CURRENT_SELECTED_KEY, tabId);
+ }
+}
+
diff --git a/src/background/usecases/parsers.js b/src/background/usecases/parsers.js
new file mode 100644
index 0000000..cda26c3
--- /dev/null
+++ b/src/background/usecases/parsers.js
@@ -0,0 +1,55 @@
+const trimStart = (str) => {
+ // NOTE String.trimStart is available on Firefox 61
+ return str.replace(/^\s+/, '');
+};
+
+const normalizeUrl = (keywords, searchSettings) => {
+ try {
+ return new URL(keywords).href;
+ } catch (e) {
+ if (keywords.includes('.') && !keywords.includes(' ')) {
+ return 'http://' + keywords;
+ }
+ let template = searchSettings.engines[searchSettings.default];
+ let query = keywords;
+
+ let first = trimStart(keywords).split(' ')[0];
+ if (Object.keys(searchSettings.engines).includes(first)) {
+ template = searchSettings.engines[first];
+ query = trimStart(trimStart(keywords).slice(first.length));
+ }
+ return template.replace('{}', encodeURIComponent(query));
+ }
+};
+
+const mustNumber = (v) => {
+ let num = Number(v);
+ if (isNaN(num)) {
+ throw new Error('Not number: ' + v);
+ }
+ return num;
+};
+
+const parseSetOption = (word, types) => {
+ let [key, value] = word.split('=');
+ if (value === undefined) {
+ value = !key.startsWith('no');
+ key = value ? key : key.slice(2);
+ }
+ let type = types[key];
+ if (!type) {
+ throw new Error('Unknown property: ' + key);
+ }
+ if (type === 'boolean' && typeof value !== 'boolean' ||
+ type !== 'boolean' && typeof value === 'boolean') {
+ throw new Error('Invalid argument: ' + word);
+ }
+
+ switch (type) {
+ case 'string': return [key, value];
+ case 'number': return [key, mustNumber(value)];
+ case 'boolean': return [key, value];
+ }
+};
+
+export { normalizeUrl, parseSetOption };
diff --git a/src/background/usecases/setting.js b/src/background/usecases/setting.js
new file mode 100644
index 0000000..656fc3f
--- /dev/null
+++ b/src/background/usecases/setting.js
@@ -0,0 +1,31 @@
+import Setting from '../domains/setting';
+import PersistentSettingRepository from '../repositories/persistent-setting';
+import SettingRepository from '../repositories/setting';
+
+export default class SettingInteractor {
+ constructor() {
+ this.persistentSettingRepository = new PersistentSettingRepository();
+ this.settingRepository = new SettingRepository();
+ }
+
+ save(settings) {
+ this.persistentSettingRepository.save(settings);
+ }
+
+ get() {
+ return this.settingRepository.get();
+ }
+
+ async reload() {
+ let settings = await this.persistentSettingRepository.load();
+ if (!settings) {
+ settings = Setting.defaultSettings();
+ }
+
+ let value = settings.value();
+
+ this.settingRepository.update(value);
+
+ return value;
+ }
+}
diff --git a/src/background/usecases/version.js b/src/background/usecases/version.js
new file mode 100644
index 0000000..a71f90d
--- /dev/null
+++ b/src/background/usecases/version.js
@@ -0,0 +1,41 @@
+import manifest from '../../../manifest.json';
+import VersionRepository from '../repositories/version';
+import TabPresenter from '../presenters/tab';
+import Notifier from '../infrastructures/notifier';
+
+export default class VersionInteractor {
+ constructor() {
+ this.versionRepository = new VersionRepository();
+ this.tabPresenter = new TabPresenter();
+ this.notifier = new Notifier();
+ }
+
+ async notifyIfUpdated() {
+ if (!await this.checkUpdated()) {
+ return;
+ }
+
+ let title = 'Vim Vixen ' + manifest.version + ' has been installed';
+ let message = 'Click here to see release notes';
+ this.notifier.notify(title, message, () => {
+ let url = this.releaseNoteUrl(manifest.version);
+ this.tabPresenter.create(url);
+ });
+ this.versionRepository.update(manifest.version);
+ }
+
+ async checkUpdated() {
+ let prev = await this.versionRepository.get();
+ if (!prev) {
+ return true;
+ }
+ return manifest.version !== prev;
+ }
+
+ releaseNoteUrl(version) {
+ if (version) {
+ return 'https://github.com/ueokande/vim-vixen/releases/tag/' + version;
+ }
+ return 'https://github.com/ueokande/vim-vixen/releases/';
+ }
+}