From bf7c125fb214b52d67527bdd292a4b5bb81b1d32 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Fri, 20 Jul 2018 23:36:03 +0900 Subject: My First Clean Architecture --- src/background/usecases/completions.js | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/background/usecases/completions.js (limited to 'src/background/usecases') diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js new file mode 100644 index 0000000..fc1ff52 --- /dev/null +++ b/src/background/usecases/completions.js @@ -0,0 +1,40 @@ +import CompletionItem from '../domains/completion-item'; +import CompletionGroup from '../domains/completion-group'; +import Completions from '../domains/completions'; +import CompletionRepository from '../repositories/completions'; +import CommandDocs from 'background/shared/commands/docs'; + +export default class CompletionsInteractor { + constructor() { + this.completionRepository = new CompletionRepository(); + } + + 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 queryBdeleteCommand(name, force, args) { + let tabs = await this.completionRepository.queryTabs(args); + let items = tabs.map(tab => new CompletionItem({ + caption: tab.title, + content: name + ' ' + tab.title, + url: tab.url, + icon: tab.favIconUrl + })); + return [new CompletionGroup('Buffers', items)]; + } +} -- cgit v1.2.3 From 890d93fe773ea5cde41d19125d4bad626e54059e Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 21 Jul 2018 10:35:27 +0900 Subject: Buffer completion on Clean Architecture --- src/background/controllers/completions.js | 19 +++++++++--------- src/background/repositories/completions.js | 11 ++++++++++ src/background/usecases/completions.js | 32 ++++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 src/background/repositories/completions.js (limited to 'src/background/usecases') diff --git a/src/background/controllers/completions.js b/src/background/controllers/completions.js index 613940f..c22ba83 100644 --- a/src/background/controllers/completions.js +++ b/src/background/controllers/completions.js @@ -13,6 +13,7 @@ export default class ContentMessageController { if (words.length === 1) { return this.completionsInteractor.queryConsoleCommand(name); } + let keywords = trimmed.slice(name.length).trimStart(); switch (words[0]) { case 'o': case 'open': @@ -20,22 +21,22 @@ export default class ContentMessageController { case 'tabopen': case 'w': case 'winopen': - break; + return this.completionsInteractor.queryOpen(name); case 'b': case 'buffer': - break; - case 'bd!': - case 'bdel!': - case 'bdelete!': - case 'bdeletes!': - break; + return this.completionsInteractor.queryBuffer(name, keywords); case 'bd': case 'bdel': case 'bdelete': case 'bdeletes': - break; + return this.completionsInteractor.queryBdelete(name, keywords); + case 'bd!': + case 'bdel!': + case 'bdelete!': + case 'bdeletes!': + return this.completionsInteractor.queryBdeleteForce(name, keywords); case 'set': - break; + return this.completionsInteractor.querySet(name, keywords); } return Promise.resolve(Completions.empty()); } diff --git a/src/background/repositories/completions.js b/src/background/repositories/completions.js new file mode 100644 index 0000000..30ede05 --- /dev/null +++ b/src/background/repositories/completions.js @@ -0,0 +1,11 @@ +export default class CompletionsRepository { + async queryTabs(keywords, excludePinned) { + let tabs = await browser.tabs.query({ currentWindow: true }); + return tabs.filter((t) => { + return t.url.toLowerCase().includes(keywords.toLowerCase()) || + t.title && t.title.toLowerCase().includes(keywords.toLowerCase()); + }).filter((t) => { + return !(excludePinned && t.pinned); + }); + } +} diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js index fc1ff52..21049d2 100644 --- a/src/background/usecases/completions.js +++ b/src/background/usecases/completions.js @@ -22,19 +22,39 @@ export default class CompletionsInteractor { if (items.length === 0) { return Promise.resolve(Completions.empty()); } - return Promise.resolve(new Completions( - [new CompletionGroup('Console Command', items)] - )); + return Promise.resolve( + new Completions([new CompletionGroup('Console Command', items)]) + ); } - async queryBdeleteCommand(name, force, args) { - let tabs = await this.completionRepository.queryTabs(args); + queryOpen() { + return Promise.resolve(Completions.empty()); + } + + queryBuffer(name, keywords) { + return this.queryTabs(name, true, keywords); + } + + queryBdelete(name, keywords) { + return this.queryTabs(name, true, keywords); + } + + queryBdeleteForce(name, keywords) { + return this.queryTabs(name, false, keywords); + } + + querySet() { + return Promise.resolve(Completions.empty()); + } + + 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 })); - return [new CompletionGroup('Buffers', items)]; + return new Completions([new CompletionGroup('Buffers', items)]); } } -- cgit v1.2.3 From fc5e6b2b410d6850278dd982990ce42552a3b1b2 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 21 Jul 2018 11:17:51 +0900 Subject: Complete open histories and bookmarks --- src/background/controllers/completions.js | 2 +- src/background/repositories/completions.js | 97 ++++++++++++++++++++++++++++++ src/background/usecases/completions.js | 24 +++++++- 3 files changed, 120 insertions(+), 3 deletions(-) (limited to 'src/background/usecases') diff --git a/src/background/controllers/completions.js b/src/background/controllers/completions.js index c22ba83..f8eade9 100644 --- a/src/background/controllers/completions.js +++ b/src/background/controllers/completions.js @@ -21,7 +21,7 @@ export default class ContentMessageController { case 'tabopen': case 'w': case 'winopen': - return this.completionsInteractor.queryOpen(name); + return this.completionsInteractor.queryOpen(name, keywords); case 'b': case 'buffer': return this.completionsInteractor.queryBuffer(name, keywords); diff --git a/src/background/repositories/completions.js b/src/background/repositories/completions.js index 30ede05..c4e956e 100644 --- a/src/background/repositories/completions.js +++ b/src/background/repositories/completions.js @@ -1,4 +1,101 @@ export default class CompletionsRepository { + async queryBookmarks(keywords) { + let items = await browser.bookmarks.search({ query: keywords }); + return items.filter((item) => { + let url = undefined; + try { + url = new URL(item.url); + } catch (e) { + return false; + } + return item.type === 'bookmark' && url.protocol !== 'place:'; + }); + } + + // eslint-disable-next-line complexity, max-lines-per-function + async queryHistories(keywords) { + const filterHttp = (items) => { + const httpsHosts = items + .filter(item => item[1].protocol === 'https:') + .map(item => item[1].host); + const httpsHostSet = new Set(httpsHosts); + return items.filter( + item => !(item[1].protocol === 'http:' && + httpsHostSet.has(item[1].host)) + ); + }; + + const filterEmptyTitle = (items) => { + return items.filter(item => item[0].title && item[0].title !== ''); + }; + + const filterClosedPath = (items) => { + const allSimplePaths = items + .filter(item => item[1].hash === '' && item[1].search === '') + .map(item => item[1].origin + item[1].pathname); + const allSimplePathSet = new Set(allSimplePaths); + return items.filter( + item => !(item[1].hash === '' && item[1].search === '' && + (/\/$/).test(item[1].pathname) && + allSimplePathSet.has( + (item[1].origin + item[1].pathname).replace(/\/$/, '') + ) + ) + ); + }; + + const reduceByPathname = (items, min) => { + let hash = {}; + for (let item of items) { + let pathname = item[1].origin + item[1].pathname; + if (!hash[pathname]) { + hash[pathname] = item; + } else if (hash[pathname][1].href.length > item[1].href.length) { + hash[pathname] = item; + } + } + let filtered = Object.values(hash); + if (filtered.length < min) { + return items; + } + return filtered; + }; + + const reduceByOrigin = (items, min) => { + let hash = {}; + for (let item of items) { + let origin = item[1].origin; + if (!hash[origin]) { + hash[origin] = item; + } else if (hash[origin][1].href.length > item[1].href.length) { + hash[origin] = item; + } + } + let filtered = Object.values(hash); + if (filtered.length < min) { + return items; + } + return filtered; + }; + + + let historyItems = await browser.history.search({ + text: keywords, + startTime: 0, + }); + return [historyItems.map(item => [item, new URL(item.url)])] + .map(filterEmptyTitle) + .map(filterHttp) + .map(filterClosedPath) + .map(items => reduceByPathname(items, 10)) + .map(items => reduceByOrigin(items, 10)) + .map(items => items + .sort((x, y) => x[0].visitCount < y[0].visitCount) + .slice(0, 10) + .map(item => item[0]) + )[0]; + } + async queryTabs(keywords, excludePinned) { let tabs = await browser.tabs.query({ currentWindow: true }); return tabs.filter((t) => { diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js index 21049d2..0bc34d1 100644 --- a/src/background/usecases/completions.js +++ b/src/background/usecases/completions.js @@ -27,8 +27,28 @@ export default class CompletionsInteractor { ); } - queryOpen() { - return Promise.resolve(Completions.empty()); + async queryOpen(name, keywords) { + let groups = []; + let histories = await this.completionRepository.queryHistories(keywords); + if (histories.length > 0) { + let items = histories.map(page => new CompletionItem({ + caption: page.title, + content: name + ' ' + page.url, + url: page.url + })); + groups.push(new CompletionGroup('History', items)); + } + + let bookmarks = await this.completionRepository.queryBookmarks(keywords); + if (bookmarks.length > 0) { + let items = bookmarks.map(page => new CompletionItem({ + caption: page.title, + content: name + ' ' + page.url, + url: page.url + })); + groups.push(new CompletionGroup('Bookmarks', items)); + } + return new Completions(groups); } queryBuffer(name, keywords) { -- cgit v1.2.3 From 0652131de885df3ebb7c9c0e762fd1604486a892 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 21 Jul 2018 15:59:30 +0900 Subject: Clean and tests for filters on open command --- src/background/repositories/completions.js | 81 +-------------------- src/background/usecases/completions.js | 12 +++ src/background/usecases/filters.js | 72 ++++++++++++++++++ test/background/usecases/filters.test.js | 113 +++++++++++++++++++++++++++++ 4 files changed, 199 insertions(+), 79 deletions(-) create mode 100644 src/background/usecases/filters.js create mode 100644 test/background/usecases/filters.test.js (limited to 'src/background/usecases') diff --git a/src/background/repositories/completions.js b/src/background/repositories/completions.js index c4e956e..1318d36 100644 --- a/src/background/repositories/completions.js +++ b/src/background/repositories/completions.js @@ -12,88 +12,11 @@ export default class CompletionsRepository { }); } - // eslint-disable-next-line complexity, max-lines-per-function - async queryHistories(keywords) { - const filterHttp = (items) => { - const httpsHosts = items - .filter(item => item[1].protocol === 'https:') - .map(item => item[1].host); - const httpsHostSet = new Set(httpsHosts); - return items.filter( - item => !(item[1].protocol === 'http:' && - httpsHostSet.has(item[1].host)) - ); - }; - - const filterEmptyTitle = (items) => { - return items.filter(item => item[0].title && item[0].title !== ''); - }; - - const filterClosedPath = (items) => { - const allSimplePaths = items - .filter(item => item[1].hash === '' && item[1].search === '') - .map(item => item[1].origin + item[1].pathname); - const allSimplePathSet = new Set(allSimplePaths); - return items.filter( - item => !(item[1].hash === '' && item[1].search === '' && - (/\/$/).test(item[1].pathname) && - allSimplePathSet.has( - (item[1].origin + item[1].pathname).replace(/\/$/, '') - ) - ) - ); - }; - - const reduceByPathname = (items, min) => { - let hash = {}; - for (let item of items) { - let pathname = item[1].origin + item[1].pathname; - if (!hash[pathname]) { - hash[pathname] = item; - } else if (hash[pathname][1].href.length > item[1].href.length) { - hash[pathname] = item; - } - } - let filtered = Object.values(hash); - if (filtered.length < min) { - return items; - } - return filtered; - }; - - const reduceByOrigin = (items, min) => { - let hash = {}; - for (let item of items) { - let origin = item[1].origin; - if (!hash[origin]) { - hash[origin] = item; - } else if (hash[origin][1].href.length > item[1].href.length) { - hash[origin] = item; - } - } - let filtered = Object.values(hash); - if (filtered.length < min) { - return items; - } - return filtered; - }; - - - let historyItems = await browser.history.search({ + queryHistories(keywords) { + return browser.history.search({ text: keywords, startTime: 0, }); - return [historyItems.map(item => [item, new URL(item.url)])] - .map(filterEmptyTitle) - .map(filterHttp) - .map(filterClosedPath) - .map(items => reduceByPathname(items, 10)) - .map(items => reduceByOrigin(items, 10)) - .map(items => items - .sort((x, y) => x[0].visitCount < y[0].visitCount) - .slice(0, 10) - .map(item => item[0]) - )[0]; } async queryTabs(keywords, excludePinned) { diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js index 0bc34d1..1c0fe38 100644 --- a/src/background/usecases/completions.js +++ b/src/background/usecases/completions.js @@ -3,6 +3,9 @@ import CompletionGroup from '../domains/completion-group'; import Completions from '../domains/completions'; import CompletionRepository from '../repositories/completions'; import CommandDocs from 'background/shared/commands/docs'; +import * as filters from './filters'; + +const COMPLETION_ITEM_LIMIT = 10; export default class CompletionsInteractor { constructor() { @@ -28,9 +31,18 @@ export default class CompletionsInteractor { } async queryOpen(name, keywords) { + // TODO get search engines from settings let groups = []; let histories = await this.completionRepository.queryHistories(keywords); if (histories.length > 0) { + 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); let items = histories.map(page => new CompletionItem({ caption: page.title, content: name + ' ' + 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/test/background/usecases/filters.test.js b/test/background/usecases/filters.test.js new file mode 100644 index 0000000..bdfb0be --- /dev/null +++ b/test/background/usecases/filters.test.js @@ -0,0 +1,113 @@ +import * as filters from 'background/usecases/filters'; + +describe("background/usecases/filters", () => { + describe('filterHttp', () => { + it('filters http URLs duplicates to https hosts', () => { + let pages = [ + { url: 'http://i-beam.org/foo' }, + { url: 'https://i-beam.org/bar' }, + { url: 'http://i-beam.net/hoge' }, + { url: 'http://i-beam.net/fuga' }, + ]; + let filtered = filters.filterHttp(pages); + + let urls = filtered.map(x => x.url); + expect(urls).to.deep.equal([ + 'https://i-beam.org/bar', 'http://i-beam.net/hoge', 'http://i-beam.net/fuga' + ]); + }) + }); + + describe('filterBlankTitle', () => { + it('filters blank titles', () => { + let pages = [ + { title: 'hello' }, + { title: '' }, + {}, + ]; + let filtered = filters.filterBlankTitle(pages); + + expect(filtered).to.deep.equal([{ title: 'hello' }]); + }); + }) + + describe('filterByTailingSlash', () => { + it('filters duplicated pathname on tailing slash', () => { + let pages = [ + { url: 'http://i-beam.org/content' }, + { url: 'http://i-beam.org/content/' }, + { url: 'http://i-beam.org/search' }, + { url: 'http://i-beam.org/search?q=apple_banana_cherry' }, + ]; + let filtered = filters.filterByTailingSlash(pages); + + let urls = filtered.map(x => x.url); + expect(urls).to.deep.equal([ + 'http://i-beam.org/content', + 'http://i-beam.org/search', + 'http://i-beam.org/search?q=apple_banana_cherry', + ]); + }); + }) + + describe('filterByPathname', () => { + it('remains items less than minimam length', () => { + let pages = [ + { url: 'http://i-beam.org/search?q=apple' }, + { url: 'http://i-beam.org/search?q=apple_banana' }, + { url: 'http://i-beam.org/search?q=apple_banana_cherry' }, + { url: 'http://i-beam.org/request?q=apple' }, + { url: 'http://i-beam.org/request?q=apple_banana' }, + { url: 'http://i-beam.org/request?q=apple_banana_cherry' }, + ]; + let filtered = filters.filterByPathname(pages, 10); + expect(filtered).to.have.lengthOf(6); + }); + + it('filters by length of pathname', () => { + let pages = [ + { url: 'http://i-beam.org/search?q=apple' }, + { url: 'http://i-beam.org/search?q=apple_banana' }, + { url: 'http://i-beam.org/search?q=apple_banana_cherry' }, + { url: 'http://i-beam.net/search?q=apple' }, + { url: 'http://i-beam.net/search?q=apple_banana' }, + { url: 'http://i-beam.net/search?q=apple_banana_cherry' }, + ]; + let filtered = filters.filterByPathname(pages, 0); + expect(filtered).to.deep.equal([ + { url: 'http://i-beam.org/search?q=apple' }, + { url: 'http://i-beam.net/search?q=apple' }, + ]); + }); + }) + + describe('filterByOrigin', () => { + it('remains items less than minimam length', () => { + let pages = [ + { url: 'http://i-beam.org/search?q=apple' }, + { url: 'http://i-beam.org/search?q=apple_banana' }, + { url: 'http://i-beam.org/search?q=apple_banana_cherry' }, + { url: 'http://i-beam.org/request?q=apple' }, + { url: 'http://i-beam.org/request?q=apple_banana' }, + { url: 'http://i-beam.org/request?q=apple_banana_cherry' }, + ]; + let filtered = filters.filterByOrigin(pages, 10); + expect(filtered).to.have.lengthOf(6); + }); + + it('filters by length of pathname', () => { + let pages = [ + { url: 'http://i-beam.org/search?q=apple' }, + { url: 'http://i-beam.org/search?q=apple_banana' }, + { url: 'http://i-beam.org/search?q=apple_banana_cherry' }, + { url: 'http://i-beam.org/request?q=apple' }, + { url: 'http://i-beam.org/request?q=apple_banana' }, + { url: 'http://i-beam.org/request?q=apple_banana_cherry' }, + ]; + let filtered = filters.filterByOrigin(pages, 0); + expect(filtered).to.deep.equal([ + { url: 'http://i-beam.org/search?q=apple' }, + ]); + }); + }) +}); -- cgit v1.2.3 From 89c28d67fd7b961833b557da904bd17aa28660c5 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 22 Jul 2018 00:01:24 +0900 Subject: Settings as clean architecture --- src/background/actions/setting.js | 20 --------- src/background/components/background.js | 6 --- src/background/controllers/setting.js | 18 ++++++++ src/background/domains/setting.js | 51 ++++++++++++++++++++++ src/background/index.js | 6 +-- .../infrastructures/content-message-client.js | 12 +++++ .../infrastructures/content-message-listener.js | 21 +++++++-- src/background/infrastructures/memory-storage.js | 11 +++++ src/background/repositories/persistent-setting.js | 16 +++++++ src/background/repositories/setting.js | 17 ++++++++ src/background/usecases/setting.js | 31 +++++++++++++ 11 files changed, 176 insertions(+), 33 deletions(-) delete mode 100644 src/background/actions/setting.js create mode 100644 src/background/controllers/setting.js create mode 100644 src/background/domains/setting.js create mode 100644 src/background/infrastructures/content-message-client.js create mode 100644 src/background/infrastructures/memory-storage.js create mode 100644 src/background/repositories/persistent-setting.js create mode 100644 src/background/repositories/setting.js create mode 100644 src/background/usecases/setting.js (limited to 'src/background/usecases') diff --git a/src/background/actions/setting.js b/src/background/actions/setting.js deleted file mode 100644 index 7eeb5de..0000000 --- a/src/background/actions/setting.js +++ /dev/null @@ -1,20 +0,0 @@ -import actions from '../actions'; -import * as settingsStorage from 'shared/settings/storage'; - -const load = async() => { - let value = await settingsStorage.loadValue(); - return { - type: actions.SETTING_SET_SETTINGS, - value, - }; -}; - -const setProperty = (name, value) => { - return { - type: actions.SETTING_SET_PROPERTY, - name, - value, - }; -}; - -export { load, setProperty }; diff --git a/src/background/components/background.js b/src/background/components/background.js index d933d7b..9ba733d 100644 --- a/src/background/components/background.js +++ b/src/background/components/background.js @@ -1,6 +1,5 @@ import messages from 'shared/messages'; import * as commandActions from 'background/actions/command'; -import * as settingActions from 'background/actions/setting'; import * as findActions from 'background/actions/find'; import * as tabActions from 'background/actions/tab'; @@ -38,11 +37,6 @@ export default class BackgroundComponent { commandActions.exec(sender.tab, message.text, settings.value), ); return this.broadcastSettingsChanged(); - case messages.SETTINGS_QUERY: - return Promise.resolve(this.store.getState().setting.value); - case messages.SETTINGS_RELOAD: - this.store.dispatch(settingActions.load()); - return this.broadcastSettingsChanged(); case messages.FIND_GET_KEYWORD: return Promise.resolve(find.keyword); case messages.FIND_SET_KEYWORD: diff --git a/src/background/controllers/setting.js b/src/background/controllers/setting.js new file mode 100644 index 0000000..9e6019e --- /dev/null +++ b/src/background/controllers/setting.js @@ -0,0 +1,18 @@ +import SettingInteractor from '../usecases/setting'; +import ContentMessageClient from '../infrastructures/content-message-client'; + +export default class SettingController { + constructor() { + this.settingInteractor = new SettingInteractor(); + this.contentMessageClient = new ContentMessageClient(); + } + + getSetting() { + return this.settingInteractor.get(); + } + + async reload() { + await this.settingInteractor.reload(); + this.contentMessageClient.broadcastSettingsChanged(); + } +} diff --git a/src/background/domains/setting.js b/src/background/domains/setting.js new file mode 100644 index 0000000..106ec0f --- /dev/null +++ b/src/background/domains/setting.js @@ -0,0 +1,51 @@ +import DefaultSettings from '../../shared/settings/default'; +import * as settingsValues from '../../shared/settings/values'; + +export default class Setting { + constructor({ source, json, form }) { + this.obj = { + source, json, form + }; + } + + get source() { + return this.obj.source; + } + + get json() { + return this.obj.json; + } + + get form() { + return this.obj.form; + } + + value() { + let value = JSON.parse(DefaultSettings.json); + if (this.obj.source === 'json') { + value = settingsValues.valueFromJson(this.obj.json); + } else if (this.obj.source === 'form') { + value = settingsValues.valueFromForm(this.obj.form); + } + if (!value.properties) { + value.properties = {}; + } + return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value }; + } + + serialize() { + return this.obj; + } + + static deserialize(obj) { + return new Setting({ source: obj.source, json: obj.json, form: obj.form }); + } + + static defaultSettings() { + return new Setting({ + source: DefaultSettings.source, + json: DefaultSettings.json, + form: {}, + }); + } +} diff --git a/src/background/index.js b/src/background/index.js index 619b076..69dbe06 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,4 +1,3 @@ -import * as settingActions from 'background/actions/setting'; import BackgroundComponent from 'background/components/background'; import OperationComponent from 'background/components/operation'; import TabComponent from 'background/components/tab'; @@ -9,6 +8,7 @@ import promise from 'redux-promise'; import * as versions from './shared/versions'; import ContentMessageListener from './infrastructures/content-message-listener'; +import SettingController from './controllers/setting'; const store = createStore( reducers, @@ -31,8 +31,8 @@ const tabComponent = new TabComponent(store); const indicatorComponent = new IndicatorComponent(store); /* eslint-enable no-unused-vars */ -store.dispatch(settingActions.load()); - checkAndNotifyUpdated(); +new SettingController().reload(); + new ContentMessageListener().run(); diff --git a/src/background/infrastructures/content-message-client.js b/src/background/infrastructures/content-message-client.js new file mode 100644 index 0000000..b3b37b4 --- /dev/null +++ b/src/background/infrastructures/content-message-client.js @@ -0,0 +1,12 @@ +import messages from '../../shared/messages'; + +export default class ContentMessageClient { + async broadcastSettingsChanged() { + let tabs = await browser.tabs.query({}); + for (let tab of tabs) { + browser.tabs.sendMessage(tab.id, { + type: messages.SETTINGS_CHANGED, + }); + } + } +} diff --git a/src/background/infrastructures/content-message-listener.js b/src/background/infrastructures/content-message-listener.js index a0ed66c..6236f1c 100644 --- a/src/background/infrastructures/content-message-listener.js +++ b/src/background/infrastructures/content-message-listener.js @@ -1,8 +1,10 @@ import messages from '../../shared/messages'; import CompletionsController from '../controllers/completions'; +import SettingController from '../controllers/setting'; export default class ContentMessageListener { constructor() { + this.settingController = new SettingController(); this.completionsController = new CompletionsController(); } @@ -22,13 +24,24 @@ export default class ContentMessageListener { onMessage(message) { switch (message.type) { case messages.CONSOLE_QUERY_COMPLETIONS: - return this.onConsoleQueryCompletions(message); + return this.onConsoleQueryCompletions(message.text); + case messages.SETTINGS_QUERY: + return this.onSettingsQuery(); + case messages.SETTINGS_RELOAD: + return this.onSettingsReload(); } } - async onConsoleQueryCompletions(message) { - let completions = - await this.completionsController.getCompletions(message.text); + async onConsoleQueryCompletions(line) { + let completions = await this.completionsController.getCompletions(line); return Promise.resolve(completions.serialize()); } + + onSettingsQuery() { + return this.settingController.getSetting(); + } + + onSettingsReload() { + return this.settingController.reload(); + } } diff --git a/src/background/infrastructures/memory-storage.js b/src/background/infrastructures/memory-storage.js new file mode 100644 index 0000000..0a05928 --- /dev/null +++ b/src/background/infrastructures/memory-storage.js @@ -0,0 +1,11 @@ +const db = {}; + +export default class MemoryStorage { + set(name, value) { + db[name] = value; + } + + get(name) { + return db[name]; + } +} diff --git a/src/background/repositories/persistent-setting.js b/src/background/repositories/persistent-setting.js new file mode 100644 index 0000000..247ea6f --- /dev/null +++ b/src/background/repositories/persistent-setting.js @@ -0,0 +1,16 @@ +import Setting from '../domains/setting'; + +export default class SettingRepository { + save(settings) { + return browser.storage.local.set({ settings: settings.serialize() }); + } + + async load() { + let { settings } = await browser.storage.local.get('settings'); + if (!settings) { + return null; + } + return Setting.deserialize(settings); + } +} + diff --git a/src/background/repositories/setting.js b/src/background/repositories/setting.js new file mode 100644 index 0000000..d9c481d --- /dev/null +++ b/src/background/repositories/setting.js @@ -0,0 +1,17 @@ +import MemoryStorage from '../infrastructures/memory-storage'; + +const CACHED_SETTING_KEY = 'setting'; + +export default class SettingRepository { + constructor() { + this.cache = new MemoryStorage(); + } + + get() { + return Promise.resolve(this.cache.get(CACHED_SETTING_KEY)); + } + + update(value) { + return this.cache.set(CACHED_SETTING_KEY, value); + } +} 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; + } +} -- cgit v1.2.3 From a1e5e97200bd96ba918744dfa2758f977ca823c6 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 22 Jul 2018 00:01:24 +0900 Subject: Find as clean architecture --- src/background/components/background.js | 7 ------- src/background/controllers/find.js | 15 +++++++++++++++ .../infrastructures/content-message-listener.js | 14 ++++++++++++++ src/background/repositories/find.js | 18 ++++++++++++++++++ src/background/usecases/find.js | 15 +++++++++++++++ 5 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 src/background/controllers/find.js create mode 100644 src/background/repositories/find.js create mode 100644 src/background/usecases/find.js (limited to 'src/background/usecases') diff --git a/src/background/components/background.js b/src/background/components/background.js index 9ba733d..3667f14 100644 --- a/src/background/components/background.js +++ b/src/background/components/background.js @@ -1,6 +1,5 @@ import messages from 'shared/messages'; import * as commandActions from 'background/actions/command'; -import * as findActions from 'background/actions/find'; import * as tabActions from 'background/actions/tab'; export default class BackgroundComponent { @@ -21,7 +20,6 @@ export default class BackgroundComponent { onMessage(message, sender) { let settings = this.store.getState().setting; - let find = this.store.getState().find; switch (message.type) { case messages.OPEN_URL: @@ -37,11 +35,6 @@ export default class BackgroundComponent { commandActions.exec(sender.tab, message.text, settings.value), ); return this.broadcastSettingsChanged(); - case messages.FIND_GET_KEYWORD: - return Promise.resolve(find.keyword); - case messages.FIND_SET_KEYWORD: - this.store.dispatch(findActions.setKeyword(message.keyword)); - return Promise.resolve({}); } } diff --git a/src/background/controllers/find.js b/src/background/controllers/find.js new file mode 100644 index 0000000..7096014 --- /dev/null +++ b/src/background/controllers/find.js @@ -0,0 +1,15 @@ +import FindInteractor from '../usecases/find'; + +export default class FindController { + constructor() { + this.findInteractor = new FindInteractor(); + } + + getKeyword() { + return this.findInteractor.getKeyword(); + } + + setKeyword(keyword) { + return this.findInteractor.setKeyword(keyword); + } +} diff --git a/src/background/infrastructures/content-message-listener.js b/src/background/infrastructures/content-message-listener.js index 6236f1c..00dba51 100644 --- a/src/background/infrastructures/content-message-listener.js +++ b/src/background/infrastructures/content-message-listener.js @@ -1,11 +1,13 @@ import messages from '../../shared/messages'; import CompletionsController from '../controllers/completions'; import SettingController from '../controllers/setting'; +import FindController from '../controllers/find'; export default class ContentMessageListener { constructor() { this.settingController = new SettingController(); this.completionsController = new CompletionsController(); + this.findController = new FindController(); } run() { @@ -29,6 +31,10 @@ export default class ContentMessageListener { return this.onSettingsQuery(); case messages.SETTINGS_RELOAD: return this.onSettingsReload(); + case messages.FIND_GET_KEYWORD: + return this.onFindGetKeyword(); + case messages.FIND_SET_KEYWORD: + return this.onFindSetKeyword(message.keyword); } } @@ -44,4 +50,12 @@ export default class ContentMessageListener { onSettingsReload() { return this.settingController.reload(); } + + onFindGetKeyword() { + return this.findController.getKeyword(); + } + + onFindSetKeyword(keyword) { + return this.findController.setKeyword(keyword); + } } diff --git a/src/background/repositories/find.js b/src/background/repositories/find.js new file mode 100644 index 0000000..9482e78 --- /dev/null +++ b/src/background/repositories/find.js @@ -0,0 +1,18 @@ +import MemoryStorage from '../infrastructures/memory-storage'; + +const FIND_KEYWORD_KEY = 'find-keyword'; + +export default class FindRepository { + constructor() { + this.cache = new MemoryStorage(); + } + + getKeyword() { + return Promise.resolve(this.cache.get(FIND_KEYWORD_KEY)); + } + + setKeyword(keyword) { + return this.cache.set(FIND_KEYWORD_KEY, keyword); + } +} + 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); + } +} -- cgit v1.2.3 From 87ed1f43a96a67c4901c267aee96784de1d45889 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 22 Jul 2018 09:30:01 +0900 Subject: Version as Clean Architecture --- src/background/controllers/version.js | 11 +++++++ src/background/index.js | 14 ++------- src/background/infrastructures/notifier.js | 23 ++++++++++++++ src/background/repositories/tab.js | 5 +++ src/background/repositories/version.js | 10 ++++++ src/background/shared/versions/index.js | 38 ----------------------- src/background/shared/versions/release-notes.js | 8 ----- src/background/shared/versions/storage.js | 10 ------ src/background/usecases/version.js | 41 +++++++++++++++++++++++++ test/background/repositories/version.js | 34 ++++++++++++++++++++ test/background/shared/versions/index.test.js | 40 ------------------------ test/background/shared/versions/storage.test.js | 28 ----------------- 12 files changed, 126 insertions(+), 136 deletions(-) create mode 100644 src/background/controllers/version.js create mode 100644 src/background/infrastructures/notifier.js create mode 100644 src/background/repositories/tab.js create mode 100644 src/background/repositories/version.js delete mode 100644 src/background/shared/versions/index.js delete mode 100644 src/background/shared/versions/release-notes.js delete mode 100644 src/background/shared/versions/storage.js create mode 100644 src/background/usecases/version.js create mode 100644 test/background/repositories/version.js delete mode 100644 test/background/shared/versions/index.test.js delete mode 100644 test/background/shared/versions/storage.test.js (limited to 'src/background/usecases') diff --git a/src/background/controllers/version.js b/src/background/controllers/version.js new file mode 100644 index 0000000..04d99fe --- /dev/null +++ b/src/background/controllers/version.js @@ -0,0 +1,11 @@ +import VersionInteractor from '../usecases/version'; + +export default class VersionController { + constructor() { + this.versionInteractor = new VersionInteractor(); + } + + notifyIfUpdated() { + this.versionInteractor.notifyIfUpdated(); + } +} diff --git a/src/background/index.js b/src/background/index.js index 69dbe06..e753c48 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -5,25 +5,16 @@ import IndicatorComponent from 'background/components/indicator'; import reducers from 'background/reducers'; import { createStore, applyMiddleware } from 'redux'; import promise from 'redux-promise'; -import * as versions from './shared/versions'; import ContentMessageListener from './infrastructures/content-message-listener'; import SettingController from './controllers/setting'; +import VersionRepository from './controllers/version'; const store = createStore( reducers, applyMiddleware(promise), ); -const checkAndNotifyUpdated = async() => { - let updated = await versions.checkUpdated(); - if (!updated) { - return; - } - await versions.notify(); - await versions.commit(); -}; - /* eslint-disable no-unused-vars */ const backgroundComponent = new BackgroundComponent(store); const operationComponent = new OperationComponent(store); @@ -31,8 +22,7 @@ const tabComponent = new TabComponent(store); const indicatorComponent = new IndicatorComponent(store); /* eslint-enable no-unused-vars */ -checkAndNotifyUpdated(); - new SettingController().reload(); +new VersionRepository().notifyIfUpdated(); new ContentMessageListener().run(); diff --git a/src/background/infrastructures/notifier.js b/src/background/infrastructures/notifier.js new file mode 100644 index 0000000..1eccc47 --- /dev/null +++ b/src/background/infrastructures/notifier.js @@ -0,0 +1,23 @@ +const NOTIFICATION_ID = 'vimvixen-update'; + +export default class Notifier { + notify(title, message, onclick) { + const listener = (id) => { + if (id !== NOTIFICATION_ID) { + return; + } + + onclick(); + + browser.notifications.onClicked.removeListener(listener); + }; + browser.notifications.onClicked.addListener(listener); + + return browser.notifications.create(NOTIFICATION_ID, { + 'type': 'basic', + 'iconUrl': browser.extension.getURL('resources/icon_48x48.png'), + title, + message, + }); + } +} diff --git a/src/background/repositories/tab.js b/src/background/repositories/tab.js new file mode 100644 index 0000000..16989e1 --- /dev/null +++ b/src/background/repositories/tab.js @@ -0,0 +1,5 @@ +export default class TabRepository { + create(url) { + browser.tabs.create({ url, }); + } +} diff --git a/src/background/repositories/version.js b/src/background/repositories/version.js new file mode 100644 index 0000000..4c71d05 --- /dev/null +++ b/src/background/repositories/version.js @@ -0,0 +1,10 @@ +export default class VersionRepository { + async get() { + let { version } = await browser.storage.local.get('version'); + return version; + } + + update(version) { + return browser.storage.local.set({ version }); + } +} diff --git a/src/background/shared/versions/index.js b/src/background/shared/versions/index.js deleted file mode 100644 index aa09c92..0000000 --- a/src/background/shared/versions/index.js +++ /dev/null @@ -1,38 +0,0 @@ -import * as storage from './storage'; -import * as releaseNotes from './release-notes'; -import manifest from '../../../../manifest.json'; - -const NOTIFICATION_ID = 'vimvixen-update'; - -const notificationClickListener = (id) => { - if (id !== NOTIFICATION_ID) { - return; - } - - browser.tabs.create({ url: releaseNotes.url(manifest.version) }); - browser.notifications.onClicked.removeListener(notificationClickListener); -}; - -const checkUpdated = async() => { - let prev = await storage.load(); - if (!prev) { - return true; - } - return manifest.version !== prev; -}; - -const notify = () => { - browser.notifications.onClicked.addListener(notificationClickListener); - return browser.notifications.create(NOTIFICATION_ID, { - 'type': 'basic', - 'iconUrl': browser.extension.getURL('resources/icon_48x48.png'), - 'title': 'Vim Vixen ' + manifest.version + ' has been installed', - 'message': 'Click here to see release notes', - }); -}; - -const commit = () => { - storage.save(manifest.version); -}; - -export { checkUpdated, notify, commit }; diff --git a/src/background/shared/versions/release-notes.js b/src/background/shared/versions/release-notes.js deleted file mode 100644 index 6ef2335..0000000 --- a/src/background/shared/versions/release-notes.js +++ /dev/null @@ -1,8 +0,0 @@ -const url = (version) => { - if (version) { - return 'https://github.com/ueokande/vim-vixen/releases/tag/' + version; - } - return 'https://github.com/ueokande/vim-vixen/releases/'; -}; - -export { url }; diff --git a/src/background/shared/versions/storage.js b/src/background/shared/versions/storage.js deleted file mode 100644 index 7883258..0000000 --- a/src/background/shared/versions/storage.js +++ /dev/null @@ -1,10 +0,0 @@ -const load = async() => { - let { version } = await browser.storage.local.get('version'); - return version; -}; - -const save = (version) => { - return browser.storage.local.set({ version }); -}; - -export { load, save }; diff --git a/src/background/usecases/version.js b/src/background/usecases/version.js new file mode 100644 index 0000000..a681bdf --- /dev/null +++ b/src/background/usecases/version.js @@ -0,0 +1,41 @@ +import manifest from '../../../manifest.json'; +import VersionRepository from '../repositories/version'; +import TabRepository from '../repositories/tab'; +import Notifier from '../infrastructures/notifier'; + +export default class VersionInteractor { + constructor() { + this.versionRepository = new VersionRepository(); + this.tabRepository = new TabRepository(); + 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.tabRepository.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/'; + } +} diff --git a/test/background/repositories/version.js b/test/background/repositories/version.js new file mode 100644 index 0000000..8510ae6 --- /dev/null +++ b/test/background/repositories/version.js @@ -0,0 +1,34 @@ +import VersionRepository from 'background/repositories/version'; + +describe("background/repositories/version", () => { + let versionRepository; + + beforeEach(() => { + versionRepository = new VersionRepository; + }); + + describe('#get', () => { + beforeEach(() => { + return browser.storage.local.remove('version'); + }); + + it('loads saved version', async() => { + await browser.storage.local.set({ version: '1.2.3' }); + let version = await this.versionRepository.get(); + expect(version).to.equal('1.2.3'); + }); + + it('returns undefined if no versions in storage', async() => { + let version = await storage.load(); + expect(version).to.be.a('undefined'); + }); + }); + + describe('#update', () => { + it('saves version string', async() => { + await versionRepository.update('2.3.4'); + let { version } = await browser.storage.local.get('version'); + expect(version).to.equal('2.3.4'); + }); + }); +}); diff --git a/test/background/shared/versions/index.test.js b/test/background/shared/versions/index.test.js deleted file mode 100644 index d65dd9a..0000000 --- a/test/background/shared/versions/index.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import * as versions from 'background/shared/versions'; -import manifest from '../../../../manifest.json'; - -describe("shared/versions/storage", () => { - describe('#checkUpdated', () => { - beforeEach(() => { - return browser.storage.local.remove('version'); - }); - - it('return true if no previous versions', async() => { - let updated = await versions.checkUpdated(); - expect(updated).to.be.true; - }); - - it('return true if updated', async() => { - await browser.storage.local.set({ version: '0.001' }); - let updated = await versions.checkUpdated(); - expect(updated).to.be.true; - }); - - it('return false if not updated', async() => { - await browser.storage.local.set({ version: manifest.version }); - let updated = await versions.checkUpdated(); - expect(updated).to.be.false; - }); - }); - - describe('#commit', () => { - beforeEach(() => { - return browser.storage.local.remove('version'); - }); - - it('saves current version from manifest.json', async() => { - await versions.commit(); - let { version } = await browser.storage.local.get('version'); - expect(version).to.be.a('string'); - expect(version).to.equal(manifest.version); - }); - }); -}); diff --git a/test/background/shared/versions/storage.test.js b/test/background/shared/versions/storage.test.js deleted file mode 100644 index f452516..0000000 --- a/test/background/shared/versions/storage.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as storage from 'background/shared/versions/storage'; - -describe("shared/versions/storage", () => { - describe('#load', () => { - beforeEach(() => { - return browser.storage.local.remove('version'); - }); - - it('loads saved version', async() => { - await browser.storage.local.set({ version: '1.2.3' }); - let version = await storage.load(); - expect(version).to.equal('1.2.3'); - }); - - it('returns undefined if no versions in storage', async() => { - let version = await storage.load(); - expect(version).to.be.a('undefined'); - }); - }); - - describe('#save', () => { - it('saves version string', async() => { - await storage.save('2.3.4'); - let { version } = await browser.storage.local.get('version'); - expect(version).to.equal('2.3.4'); - }); - }); -}); -- cgit v1.2.3 From a2345f55d64c041f7da079cf36eb4039c43013df Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 22 Jul 2018 11:13:21 +0900 Subject: Search engine completions --- src/background/usecases/completions.js | 70 ++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 24 deletions(-) (limited to 'src/background/usecases') diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js index 1c0fe38..18174bd 100644 --- a/src/background/usecases/completions.js +++ b/src/background/usecases/completions.js @@ -4,12 +4,14 @@ import Completions from '../domains/completions'; import CompletionRepository from '../repositories/completions'; import CommandDocs from 'background/shared/commands/docs'; import * as filters from './filters'; +import SettingRepository from '../repositories/setting'; const COMPLETION_ITEM_LIMIT = 10; export default class CompletionsInteractor { constructor() { this.completionRepository = new CompletionRepository(); + this.settingRepository = new SettingRepository(); } queryConsoleCommand(prefix) { @@ -31,34 +33,18 @@ export default class CompletionsInteractor { } async queryOpen(name, keywords) { - // TODO get search engines from settings let groups = []; - let histories = await this.completionRepository.queryHistories(keywords); + 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) { - 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); - let items = histories.map(page => new CompletionItem({ - caption: page.title, - content: name + ' ' + page.url, - url: page.url - })); - groups.push(new CompletionGroup('History', items)); + groups.push(new CompletionGroup('History', histories)); } - - let bookmarks = await this.completionRepository.queryBookmarks(keywords); + let bookmarks = await this.queryBookmarkItems(name, keywords); if (bookmarks.length > 0) { - let items = bookmarks.map(page => new CompletionItem({ - caption: page.title, - content: name + ' ' + page.url, - url: page.url - })); - groups.push(new CompletionGroup('Bookmarks', items)); + groups.push(new CompletionGroup('Bookmarks', bookmarks)); } return new Completions(groups); } @@ -89,4 +75,40 @@ export default class CompletionsInteractor { })); 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 + })); + } } -- cgit v1.2.3 From b130fd52681a4e3f381d3d09a48589fc42996a8b Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 22 Jul 2018 11:33:52 +0900 Subject: TabPresenter --- src/background/presenters/tab.js | 5 +++++ src/background/repositories/tab.js | 5 ----- src/background/usecases/version.js | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 src/background/presenters/tab.js delete mode 100644 src/background/repositories/tab.js (limited to 'src/background/usecases') diff --git a/src/background/presenters/tab.js b/src/background/presenters/tab.js new file mode 100644 index 0000000..8f60535 --- /dev/null +++ b/src/background/presenters/tab.js @@ -0,0 +1,5 @@ +export default class TabPresenter { + create(url) { + browser.tabs.create({ url, }); + } +} diff --git a/src/background/repositories/tab.js b/src/background/repositories/tab.js deleted file mode 100644 index 16989e1..0000000 --- a/src/background/repositories/tab.js +++ /dev/null @@ -1,5 +0,0 @@ -export default class TabRepository { - create(url) { - browser.tabs.create({ url, }); - } -} diff --git a/src/background/usecases/version.js b/src/background/usecases/version.js index a681bdf..a71f90d 100644 --- a/src/background/usecases/version.js +++ b/src/background/usecases/version.js @@ -1,12 +1,12 @@ import manifest from '../../../manifest.json'; import VersionRepository from '../repositories/version'; -import TabRepository from '../repositories/tab'; +import TabPresenter from '../presenters/tab'; import Notifier from '../infrastructures/notifier'; export default class VersionInteractor { constructor() { this.versionRepository = new VersionRepository(); - this.tabRepository = new TabRepository(); + this.tabPresenter = new TabPresenter(); this.notifier = new Notifier(); } @@ -19,7 +19,7 @@ export default class VersionInteractor { let message = 'Click here to see release notes'; this.notifier.notify(title, message, () => { let url = this.releaseNoteUrl(manifest.version); - this.tabRepository.create(url); + this.tabPresenter.create(url); }); this.versionRepository.update(manifest.version); } -- cgit v1.2.3 From 42d902982a1d2edbca8ca2edb5fb25e642794e2a Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 22 Jul 2018 15:39:23 +0900 Subject: Indicator as Clean Architecture --- src/background/controllers/addon-enabled.js | 11 ++++++++ src/background/index.js | 2 -- .../infrastructures/content-message-client.js | 13 ++++++++++ .../infrastructures/content-message-listener.js | 8 ++++++ src/background/presenters/indicator.js | 12 +++++++++ src/background/presenters/tab.js | 4 +++ src/background/usecases/addon-enabled.js | 29 ++++++++++++++++++++++ 7 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/background/controllers/addon-enabled.js create mode 100644 src/background/presenters/indicator.js create mode 100644 src/background/usecases/addon-enabled.js (limited to 'src/background/usecases') diff --git a/src/background/controllers/addon-enabled.js b/src/background/controllers/addon-enabled.js new file mode 100644 index 0000000..0f5d801 --- /dev/null +++ b/src/background/controllers/addon-enabled.js @@ -0,0 +1,11 @@ +import AddonEnabledInteractor from '../usecases/addon-enabled'; + +export default class AddonEnabledController { + constructor() { + this.addonEnabledInteractor = new AddonEnabledInteractor(); + } + + indicate(enabled) { + this.addonEnabledInteractor.indicate(enabled); + } +} diff --git a/src/background/index.js b/src/background/index.js index e753c48..70d514f 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,7 +1,6 @@ import BackgroundComponent from 'background/components/background'; import OperationComponent from 'background/components/operation'; import TabComponent from 'background/components/tab'; -import IndicatorComponent from 'background/components/indicator'; import reducers from 'background/reducers'; import { createStore, applyMiddleware } from 'redux'; import promise from 'redux-promise'; @@ -19,7 +18,6 @@ const store = createStore( const backgroundComponent = new BackgroundComponent(store); const operationComponent = new OperationComponent(store); const tabComponent = new TabComponent(store); -const indicatorComponent = new IndicatorComponent(store); /* eslint-enable no-unused-vars */ new SettingController().reload(); diff --git a/src/background/infrastructures/content-message-client.js b/src/background/infrastructures/content-message-client.js index b3b37b4..d659560 100644 --- a/src/background/infrastructures/content-message-client.js +++ b/src/background/infrastructures/content-message-client.js @@ -9,4 +9,17 @@ export default class ContentMessageClient { }); } } + + async getAddonEnabled(tabId) { + let { enabled } = await browser.tabs.sendMessage(tabId, { + type: messages.ADDON_ENABLED_QUERY, + }); + return enabled; + } + + toggleAddonEnabled(tabId) { + return browser.tabs.sendMessage(tabId, { + type: messages.ADDON_TOGGLE_ENABLED, + }); + } } diff --git a/src/background/infrastructures/content-message-listener.js b/src/background/infrastructures/content-message-listener.js index 00dba51..8a361c1 100644 --- a/src/background/infrastructures/content-message-listener.js +++ b/src/background/infrastructures/content-message-listener.js @@ -2,12 +2,14 @@ import messages from '../../shared/messages'; import CompletionsController from '../controllers/completions'; import SettingController from '../controllers/setting'; import FindController from '../controllers/find'; +import AddonEnabledController from '../controllers/addon-enabled'; export default class ContentMessageListener { constructor() { this.settingController = new SettingController(); this.completionsController = new CompletionsController(); this.findController = new FindController(); + this.addonEnabledController = new AddonEnabledController(); } run() { @@ -35,6 +37,8 @@ export default class ContentMessageListener { return this.onFindGetKeyword(); case messages.FIND_SET_KEYWORD: return this.onFindSetKeyword(message.keyword); + case messages.ADDON_ENABLED_RESPONSE: + return this.onAddonEnabledResponse(message.enabled); } } @@ -58,4 +62,8 @@ export default class ContentMessageListener { onFindSetKeyword(keyword) { return this.findController.setKeyword(keyword); } + + onAddonEnabledResponse(enabled) { + return this.addonEnabledController.indicate(enabled); + } } diff --git a/src/background/presenters/indicator.js b/src/background/presenters/indicator.js new file mode 100644 index 0000000..5737519 --- /dev/null +++ b/src/background/presenters/indicator.js @@ -0,0 +1,12 @@ +export default class IndicatorPresenter { + indicate(enabled) { + let path = enabled + ? 'resources/enabled_32x32.png' + : 'resources/disabled_32x32.png'; + return browser.browserAction.setIcon({ path }); + } + + onClick(listener) { + browser.browserAction.onClicked.addListener(listener); + } +} diff --git a/src/background/presenters/tab.js b/src/background/presenters/tab.js index 8f60535..6120f6e 100644 --- a/src/background/presenters/tab.js +++ b/src/background/presenters/tab.js @@ -2,4 +2,8 @@ export default class TabPresenter { create(url) { browser.tabs.create({ url, }); } + + onSelected(listener) { + browser.tabs.onActivated.addListener(listener); + } } diff --git a/src/background/usecases/addon-enabled.js b/src/background/usecases/addon-enabled.js new file mode 100644 index 0000000..37eb1c2 --- /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) { + 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); + } +} -- cgit v1.2.3 From 0846587baf8ff04d2183985a61f14ccdea7263d3 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 22 Jul 2018 17:09:14 +0900 Subject: Open link as Clean Architecture --- src/background/components/background.js | 9 -------- src/background/controllers/link.js | 15 ++++++++++++ .../infrastructures/content-message-listener.js | 14 ++++++++++- src/background/presenters/tab.js | 20 ++++++++++++++-- src/background/usecases/link.js | 27 ++++++++++++++++++++++ 5 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 src/background/controllers/link.js create mode 100644 src/background/usecases/link.js (limited to 'src/background/usecases') diff --git a/src/background/components/background.js b/src/background/components/background.js index 3667f14..86e96fa 100644 --- a/src/background/components/background.js +++ b/src/background/components/background.js @@ -1,6 +1,5 @@ import messages from 'shared/messages'; import * as commandActions from 'background/actions/command'; -import * as tabActions from 'background/actions/tab'; export default class BackgroundComponent { constructor(store) { @@ -22,14 +21,6 @@ export default class BackgroundComponent { let settings = this.store.getState().setting; switch (message.type) { - case messages.OPEN_URL: - if (message.newTab) { - let action = tabActions.openNewTab( - message.url, sender.tab.id, message.background, - settings.value.properties.adjacenttab); - return this.store.dispatch(action); - } - return this.store.dispatch(tabActions.openToTab(message.url, sender.tab)); case messages.CONSOLE_ENTER_COMMAND: this.store.dispatch( commandActions.exec(sender.tab, message.text, settings.value), diff --git a/src/background/controllers/link.js b/src/background/controllers/link.js new file mode 100644 index 0000000..7ebbb34 --- /dev/null +++ b/src/background/controllers/link.js @@ -0,0 +1,15 @@ +import LinkInteractor from '../usecases/link'; + +export default class LinkController { + constructor() { + this.linkInteractor = new LinkInteractor(); + } + + openToTab(url, tabId) { + this.linkInteractor.openToTab(url, tabId); + } + + openNewTab(url, openerId, background) { + this.linkInteractor.openNewTab(url, openerId, background); + } +} diff --git a/src/background/infrastructures/content-message-listener.js b/src/background/infrastructures/content-message-listener.js index 8a361c1..f16804f 100644 --- a/src/background/infrastructures/content-message-listener.js +++ b/src/background/infrastructures/content-message-listener.js @@ -3,6 +3,7 @@ import CompletionsController from '../controllers/completions'; import SettingController from '../controllers/setting'; import FindController from '../controllers/find'; import AddonEnabledController from '../controllers/addon-enabled'; +import LinkController from '../controllers/link'; export default class ContentMessageListener { constructor() { @@ -10,6 +11,7 @@ export default class ContentMessageListener { this.completionsController = new CompletionsController(); this.findController = new FindController(); this.addonEnabledController = new AddonEnabledController(); + this.linkController = new LinkController(); } run() { @@ -25,7 +27,7 @@ export default class ContentMessageListener { }); } - onMessage(message) { + onMessage(message, sender) { switch (message.type) { case messages.CONSOLE_QUERY_COMPLETIONS: return this.onConsoleQueryCompletions(message.text); @@ -39,6 +41,9 @@ export default class ContentMessageListener { return this.onFindSetKeyword(message.keyword); case messages.ADDON_ENABLED_RESPONSE: return this.onAddonEnabledResponse(message.enabled); + case messages.OPEN_URL: + return this.onOpenUrl( + message.newTab, message.url, sender.tab.id, message.background); } } @@ -66,4 +71,11 @@ export default class ContentMessageListener { onAddonEnabledResponse(enabled) { return this.addonEnabledController.indicate(enabled); } + + onOpenUrl(newTab, url, openerId, background) { + if (newTab) { + return this.linkController.openNewTab(url, openerId, background); + } + return this.linkController.openToTab(url, openerId); + } } diff --git a/src/background/presenters/tab.js b/src/background/presenters/tab.js index 6120f6e..66a207f 100644 --- a/src/background/presenters/tab.js +++ b/src/background/presenters/tab.js @@ -1,6 +1,22 @@ export default class TabPresenter { - create(url) { - browser.tabs.create({ url, }); + open(url, tabId) { + return browser.tabs.update(tabId, { url }); + } + + create(url, { openerTabId, active }) { + return browser.tabs.create({ url, openerTabId, active }); + } + + async createAdjacent(url, { openerTabId, active }) { + let tabs = await browser.tabs.query({ + active: true, currentWindow: true + }); + return browser.tabs.create({ + url, + openerTabId, + active, + index: tabs[0].index + 1 + }); } onSelected(listener) { 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 + }); + } +} -- cgit v1.2.3 From c4afd7237b7720acbf642fc4c6eb529420295dcd Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Mon, 23 Jul 2018 21:26:47 +0900 Subject: [wip] implement command usecases --- src/background/controllers/command.js | 89 ++++++++++++++++ src/background/controllers/completions.js | 43 -------- .../infrastructures/content-message-listener.js | 13 ++- src/background/presenters/bookmark.js | 0 src/background/presenters/console.js | 16 +++ src/background/presenters/tab.js | 45 +++++++- src/background/presenters/window.js | 5 + src/background/repositories/bookmark.js | 13 +++ src/background/usecases/command.js | 114 +++++++++++++++++++++ src/background/usecases/completions.js | 2 +- 10 files changed, 291 insertions(+), 49 deletions(-) create mode 100644 src/background/controllers/command.js delete mode 100644 src/background/controllers/completions.js create mode 100644 src/background/presenters/bookmark.js create mode 100644 src/background/presenters/console.js create mode 100644 src/background/presenters/window.js create mode 100644 src/background/repositories/bookmark.js create mode 100644 src/background/usecases/command.js (limited to 'src/background/usecases') diff --git a/src/background/controllers/command.js b/src/background/controllers/command.js new file mode 100644 index 0000000..41057e0 --- /dev/null +++ b/src/background/controllers/command.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); + } + } +} diff --git a/src/background/controllers/completions.js b/src/background/controllers/completions.js deleted file mode 100644 index f8eade9..0000000 --- a/src/background/controllers/completions.js +++ /dev/null @@ -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()); - } -} diff --git a/src/background/infrastructures/content-message-listener.js b/src/background/infrastructures/content-message-listener.js index f16804f..2e84fcc 100644 --- a/src/background/infrastructures/content-message-listener.js +++ b/src/background/infrastructures/content-message-listener.js @@ -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(); } diff --git a/src/background/presenters/bookmark.js b/src/background/presenters/bookmark.js new file mode 100644 index 0000000..e69de29 diff --git a/src/background/presenters/console.js b/src/background/presenters/console.js new file mode 100644 index 0000000..f7d3777 --- /dev/null +++ b/src/background/presenters/console.js @@ -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, + }); + } +} diff --git a/src/background/presenters/tab.js b/src/background/presenters/tab.js index 66a207f..be6955a 100644 --- a/src/background/presenters/tab.js +++ b/src/background/presenters/tab.js @@ -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 }) { diff --git a/src/background/presenters/window.js b/src/background/presenters/window.js new file mode 100644 index 0000000..a82c4a2 --- /dev/null +++ b/src/background/presenters/window.js @@ -0,0 +1,5 @@ +export default class WindowPresenter { + create(url) { + return browser.windows.create({ url }); + } +} diff --git a/src/background/repositories/bookmark.js b/src/background/repositories/bookmark.js new file mode 100644 index 0000000..99f7ec4 --- /dev/null +++ b/src/background/repositories/bookmark.js @@ -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; + } +} diff --git a/src/background/usecases/command.js b/src/background/usecases/command.js new file mode 100644 index 0000000..1d4744e --- /dev/null +++ b/src/background/usecases/command.js @@ -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)); + } + } +} diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js index 18174bd..ee519e1 100644 --- a/src/background/usecases/completions.js +++ b/src/background/usecases/completions.js @@ -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) { -- cgit v1.2.3 From 86c4841964db7988a32b8c2a5edd0a0998eb34e2 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 26 Jul 2018 21:41:00 +0900 Subject: Implement set-command --- src/background/components/background.js | 5 --- src/background/repositories/setting.js | 6 +++ src/background/usecases/command.js | 36 +++++++--------- src/background/usecases/parsers.js | 50 +++++++++++++++++++++ test/background/usecases/parsers.test.js | 74 ++++++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 26 deletions(-) create mode 100644 src/background/usecases/parsers.js create mode 100644 test/background/usecases/parsers.test.js (limited to 'src/background/usecases') diff --git a/src/background/components/background.js b/src/background/components/background.js index 86e96fa..23131d2 100644 --- a/src/background/components/background.js +++ b/src/background/components/background.js @@ -21,11 +21,6 @@ export default class BackgroundComponent { let settings = this.store.getState().setting; switch (message.type) { - case messages.CONSOLE_ENTER_COMMAND: - this.store.dispatch( - commandActions.exec(sender.tab, message.text, settings.value), - ); - return this.broadcastSettingsChanged(); } } diff --git a/src/background/repositories/setting.js b/src/background/repositories/setting.js index d9c481d..6d48525 100644 --- a/src/background/repositories/setting.js +++ b/src/background/repositories/setting.js @@ -14,4 +14,10 @@ export default class SettingRepository { update(value) { return this.cache.set(CACHED_SETTING_KEY, value); } + + async setProperty(name, value) { + let current = await this.get(); + current.properties[name] = value; + return this.update(current); + } } diff --git a/src/background/usecases/command.js b/src/background/usecases/command.js index 1d4744e..7887509 100644 --- a/src/background/usecases/command.js +++ b/src/background/usecases/command.js @@ -1,8 +1,11 @@ +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() { @@ -11,6 +14,8 @@ export default class CommandIndicator { this.settingRepository = new SettingRepository(); this.bookmarkRepository = new BookmarkRepository(); this.consolePresenter = new ConsolePresenter(); + + this.contentMessageClient = new ContentMessageClient(); } async open(keywords) { @@ -86,29 +91,18 @@ export default class CommandIndicator { return this.consolePresenter.showInfo(tab.id, message); } - set(keywords) { - // TODO implement set command + 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) { - 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)); - } + let settings = await this.settingRepository.get(); + return parsers.normalizeUrl(keywords, settings.search); } } diff --git a/src/background/usecases/parsers.js b/src/background/usecases/parsers.js new file mode 100644 index 0000000..650ccd0 --- /dev/null +++ b/src/background/usecases/parsers.js @@ -0,0 +1,50 @@ +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 = keywords.trimStart().split(' ')[0]; + if (Object.keys(searchSettings.engines).includes(first)) { + template = searchSettings.engines[first]; + query = keywords.trimStart().slice(first.length).trimStart(); + } + 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/test/background/usecases/parsers.test.js b/test/background/usecases/parsers.test.js new file mode 100644 index 0000000..fa0e757 --- /dev/null +++ b/test/background/usecases/parsers.test.js @@ -0,0 +1,74 @@ +import * as parsers from 'background/usecases/parsers'; + +describe("background/usecases/parsers", () => { + describe("#parsers.parseSetOption", () => { + it('parse set string', () => { + let [key, value] = parsers.parseSetOption('encoding=utf-8', { encoding: 'string' }); + expect(key).to.equal('encoding'); + expect(value).to.equal('utf-8'); + }); + + it('parse set empty string', () => { + let [key, value] = parsers.parseSetOption('encoding=', { encoding: 'string' }); + expect(key).to.equal('encoding'); + expect(value).to.equal(''); + }); + + it('parse set string', () => { + let [key, value] = parsers.parseSetOption('history=50', { history: 'number' }); + expect(key).to.equal('history'); + expect(value).to.equal(50); + }); + + it('parse set boolean', () => { + let [key, value] = parsers.parseSetOption('paste', { paste: 'boolean' }); + expect(key).to.equal('paste'); + expect(value).to.be.true; + + [key, value] = parsers.parseSetOption('nopaste', { paste: 'boolean' }); + expect(key).to.equal('paste'); + expect(value).to.be.false; + }); + + it('throws error on unknown property', () => { + expect(() => parsers.parseSetOption('charset=utf-8', {})).to.throw(Error, 'Unknown'); + expect(() => parsers.parseSetOption('smoothscroll', {})).to.throw(Error, 'Unknown'); + expect(() => parsers.parseSetOption('nosmoothscroll', {})).to.throw(Error, 'Unknown'); + }) + + it('throws error on invalid property', () => { + expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'number' })).to.throw(Error, 'Not number'); + expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'boolean' })).to.throw(Error, 'Invalid'); + expect(() => parsers.parseSetOption('charset=', { charset: 'boolean' })).to.throw(Error, 'Invalid'); + expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'string' })).to.throw(Error, 'Invalid'); + expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'number' })).to.throw(Error, 'Invalid'); + }) + }); + + describe('#normalizeUrl', () => { + const config = { + default: 'google', + engines: { + google: 'https://google.com/search?q={}', + yahoo: 'https://yahoo.com/search?q={}', + } + }; + + it('convertes search url', () => { + expect(parsers.normalizeUrl('google apple', config)) + .to.equal('https://google.com/search?q=apple'); + expect(parsers.normalizeUrl('yahoo apple', config)) + .to.equal('https://yahoo.com/search?q=apple'); + expect(parsers.normalizeUrl('google apple banana', config)) + .to.equal('https://google.com/search?q=apple%20banana'); + expect(parsers.normalizeUrl('yahoo C++CLI', config)) + .to.equal('https://yahoo.com/search?q=C%2B%2BCLI'); + }); + + it('user default search engine', () => { + expect(parsers.normalizeUrl('apple banana', config)) + .to.equal('https://google.com/search?q=apple%20banana'); + }); + }); +}); + -- cgit v1.2.3 From ab29706348ec9091d1d75c0418f24dfab849a2e5 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 26 Jul 2018 21:57:45 +0900 Subject: Complete set properties --- src/background/usecases/completions.js | 37 ++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) (limited to 'src/background/usecases') diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js index ee519e1..626f9e2 100644 --- a/src/background/usecases/completions.js +++ b/src/background/usecases/completions.js @@ -5,6 +5,7 @@ import CompletionRepository from '../repositories/completions'; import CommandDocs from 'background/shared/commands/docs'; import * as filters from './filters'; import SettingRepository from '../repositories/setting'; +import * as properties from '../../shared/settings/properties'; const COMPLETION_ITEM_LIMIT = 10; @@ -61,8 +62,40 @@ export default class CompletionsInteractor { return this.queryTabs(name, false, keywords); } - querySet() { - return Promise.resolve(Completions.empty()); + 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) { -- cgit v1.2.3 From 66c23423f931bb66c59cd29cf9279a5de5d56535 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 28 Jul 2018 10:42:32 +0900 Subject: Background operation as Clean Architecture --- src/background/components/operation.js | 3 - src/background/controllers/operation.js | 65 +++++++ .../infrastructures/content-message-listener.js | 8 + src/background/presenters/bookmark.js | 0 src/background/presenters/console.js | 20 +++ src/background/presenters/tab.js | 35 ++++ src/background/usecases/operation.js | 190 +++++++++++++++++++++ 7 files changed, 318 insertions(+), 3 deletions(-) create mode 100644 src/background/controllers/operation.js delete mode 100644 src/background/presenters/bookmark.js create mode 100644 src/background/usecases/operation.js (limited to 'src/background/usecases') diff --git a/src/background/components/operation.js b/src/background/components/operation.js index ce93270..57b2497 100644 --- a/src/background/components/operation.js +++ b/src/background/components/operation.js @@ -22,9 +22,6 @@ export default class BackgroundComponent { onMessage(message, sender) { switch (message.type) { - case messages.BACKGROUND_OPERATION: - return this.store.dispatch( - this.exec(message.operation, sender.tab)); } } diff --git a/src/background/controllers/operation.js b/src/background/controllers/operation.js new file mode 100644 index 0000000..1339006 --- /dev/null +++ b/src/background/controllers/operation.js @@ -0,0 +1,65 @@ +import operations from '../../shared/operations'; +import OperationInteractor from '../usecases/operation'; + +export default class OperationController { + constructor() { + this.operationInteractor = new OperationInteractor(); + } + + // eslint-disable-next-line complexity, max-lines-per-function + exec(operation) { + switch (operation.type) { + case operations.TAB_CLOSE: + return this.operationInteractor.close(false); + case operations.TAB_CLOSE_FORCE: + return this.operationInteractor.close(true); + case operations.TAB_REOPEN: + return this.operationInteractor.reopen(); + case operations.TAB_PREV: + return this.operationInteractor.selectPrev(1); + case operations.TAB_NEXT: + return this.operationInteractor.selectNext(1); + case operations.TAB_FIRST: + return this.operationInteractor.selectFirst(); + case operations.TAB_LAST: + return this.operationInteractor.selectLast(); + case operations.TAB_PREV_SEL: + return this.operationInteractor.selectPrevSelected(); + case operations.TAB_RELOAD: + return this.operationInteractor.reload(operation.cache); + case operations.TAB_PIN: + return this.operationInteractor.setPinned(true); + case operations.TAB_UNPIN: + return this.operationInteractor.setPinned(false); + case operations.TAB_TOGGLE_PINNED: + return this.operationInteractor.togglePinned(); + case operations.TAB_DUPLICATE: + return this.operationInteractor.duplicate(); + case operations.PAGE_SOURCE: + return this.operationInteractor.openPageSource(); + case operations.ZOOM_IN: + return this.operationInteractor.zoomIn(); + case operations.ZOOM_OUT: + return this.operationInteractor.zoomOut(); + case operations.ZOOM_NEUTRAL: + return this.operationInteractor.zoomNutoral(); + case operations.COMMAND_SHOW: + return this.operationInteractor.showCommand(); + case operations.COMMAND_SHOW_OPEN: + return this.operationInteractor.showOpenCommand(operation.alter); + case operations.COMMAND_SHOW_TABOPEN: + return this.operationInteractor.showTabopenCommand(operation.alter); + case operations.COMMAND_SHOW_WINOPEN: + return this.operationInteractor.showWinopenCommand(operation.alter); + case operations.COMMAND_SHOW_BUFFER: + return this.operationInteractor.showBufferCommand(); + case operations.COMMAND_SHOW_ADDBOOKMARK: + return this.operationInteractor.showAddbookmarkCommand(operation.alter); + case operations.FIND_START: + return this.operationInteractor.findStart(); + case operations.CANCEL: + return this.operationInteractor.hideConsole(); + } + } +} + diff --git a/src/background/infrastructures/content-message-listener.js b/src/background/infrastructures/content-message-listener.js index 2e84fcc..277d108 100644 --- a/src/background/infrastructures/content-message-listener.js +++ b/src/background/infrastructures/content-message-listener.js @@ -4,6 +4,7 @@ import SettingController from '../controllers/setting'; import FindController from '../controllers/find'; import AddonEnabledController from '../controllers/addon-enabled'; import LinkController from '../controllers/link'; +import OperationController from '../controllers/operation'; export default class ContentMessageListener { constructor() { @@ -12,6 +13,7 @@ export default class ContentMessageListener { this.findController = new FindController(); this.addonEnabledController = new AddonEnabledController(); this.linkController = new LinkController(); + this.backgroundOperationController = new OperationController(); } run() { @@ -46,6 +48,8 @@ export default class ContentMessageListener { case messages.OPEN_URL: return this.onOpenUrl( message.newTab, message.url, sender.tab.id, message.background); + case messages.BACKGROUND_OPERATION: + return this.onBackgroundOperation(message.operation); } } @@ -85,4 +89,8 @@ export default class ContentMessageListener { } return this.linkController.openToTab(url, openerId); } + + onBackgroundOperation(operation) { + return this.backgroundOperationController.exec(operation); + } } diff --git a/src/background/presenters/bookmark.js b/src/background/presenters/bookmark.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/background/presenters/console.js b/src/background/presenters/console.js index f7d3777..8259238 100644 --- a/src/background/presenters/console.js +++ b/src/background/presenters/console.js @@ -1,16 +1,36 @@ import messages from '../../shared/messages'; export default class ConsolePresenter { + showCommand(tabId, command) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_SHOW_COMMAND, + command, + }); + } + + showFind(tabId) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_SHOW_FIND + }); + } + 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, }); } + + hide(tabId) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_HIDE, + }); + } } diff --git a/src/background/presenters/tab.js b/src/background/presenters/tab.js index be6955a..2a06a5a 100644 --- a/src/background/presenters/tab.js +++ b/src/background/presenters/tab.js @@ -48,6 +48,41 @@ export default class TabPresenter { return browser.tabs.remove(ids); } + async reopen() { + let window = await browser.windows.getCurrent(); + let sessions = await browser.sessions.getRecentlyClosed(); + let session = sessions.find((s) => { + return s.tab && s.tab.windowId === window.id; + }); + if (!session) { + return; + } + if (session.tab) { + return browser.sessions.restore(session.tab.sessionId); + } + return browser.sessions.restore(session.window.sessionId); + } + + reload(tabId, cache) { + return browser.tabs.reload(tabId, { bypassCache: cache }); + } + + setPinned(tabId, pinned) { + return browser.tabs.update(tabId, { pinned }); + } + + duplicate(id) { + return browser.tabs.duplicate(id); + } + + getZoom(tabId) { + return browser.tabs.getZoom(tabId); + } + + setZoom(tabId, factor) { + return browser.tabs.setZoom(tabId, factor); + } + async createAdjacent(url, { openerTabId, active }) { let tabs = await browser.tabs.query({ active: true, currentWindow: true diff --git a/src/background/usecases/operation.js b/src/background/usecases/operation.js new file mode 100644 index 0000000..f19c632 --- /dev/null +++ b/src/background/usecases/operation.js @@ -0,0 +1,190 @@ +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); + this.cache.set(LAST_SELECTED_KEY, lastId); + this.cache.set(CURRENT_SELECTED_KEY, tabId); + } +} + -- cgit v1.2.3 From ccdb0a2428bbdc56e8288442f844a4bb3b8f9a11 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 28 Jul 2018 10:51:52 +0900 Subject: Clean old codes --- src/background/actions/command.js | 147 -------------------- src/background/actions/console.js | 41 ------ src/background/actions/find.js | 10 -- src/background/actions/index.js | 11 -- src/background/actions/tab.js | 34 ----- src/background/components/background.js | 35 ----- src/background/components/indicator.js | 43 ------ src/background/components/operation.js | 124 ----------------- src/background/components/tab.js | 16 --- src/background/domains/command-docs.js | 12 ++ src/background/index.js | 18 --- src/background/reducers/find.js | 15 -- src/background/reducers/index.js | 8 -- src/background/reducers/setting.js | 22 --- src/background/reducers/tab.js | 19 --- src/background/shared/bookmarks.js | 9 -- src/background/shared/commands/docs.js | 11 -- src/background/shared/commands/parsers.js | 59 -------- src/background/shared/completions/bookmarks.js | 14 -- src/background/shared/completions/histories.js | 82 ----------- src/background/shared/completions/index.js | 173 ------------------------ src/background/shared/completions/tabs.js | 8 -- src/background/shared/indicators.js | 13 -- src/background/shared/tabs.js | 158 ---------------------- src/background/shared/zooms.js | 32 ----- src/background/usecases/completions.js | 2 +- test/background/actions/find.test.js | 12 -- test/background/actions/tab.test.js | 13 -- test/background/reducers/find.test.js | 18 --- test/background/reducers/setting.test.js | 35 ----- test/background/reducers/tab.test.js | 22 --- test/background/shared/commands/parsers.test.js | 84 ------------ test/background/usecases/parsers.test.js | 5 +- 33 files changed, 15 insertions(+), 1290 deletions(-) delete mode 100644 src/background/actions/command.js delete mode 100644 src/background/actions/console.js delete mode 100644 src/background/actions/find.js delete mode 100644 src/background/actions/index.js delete mode 100644 src/background/actions/tab.js delete mode 100644 src/background/components/background.js delete mode 100644 src/background/components/indicator.js delete mode 100644 src/background/components/operation.js delete mode 100644 src/background/components/tab.js create mode 100644 src/background/domains/command-docs.js delete mode 100644 src/background/reducers/find.js delete mode 100644 src/background/reducers/index.js delete mode 100644 src/background/reducers/setting.js delete mode 100644 src/background/reducers/tab.js delete mode 100644 src/background/shared/bookmarks.js delete mode 100644 src/background/shared/commands/docs.js delete mode 100644 src/background/shared/commands/parsers.js delete mode 100644 src/background/shared/completions/bookmarks.js delete mode 100644 src/background/shared/completions/histories.js delete mode 100644 src/background/shared/completions/index.js delete mode 100644 src/background/shared/completions/tabs.js delete mode 100644 src/background/shared/indicators.js delete mode 100644 src/background/shared/tabs.js delete mode 100644 src/background/shared/zooms.js delete mode 100644 test/background/actions/find.test.js delete mode 100644 test/background/actions/tab.test.js delete mode 100644 test/background/reducers/find.test.js delete mode 100644 test/background/reducers/setting.test.js delete mode 100644 test/background/reducers/tab.test.js delete mode 100644 test/background/shared/commands/parsers.test.js (limited to 'src/background/usecases') diff --git a/src/background/actions/command.js b/src/background/actions/command.js deleted file mode 100644 index feb16d8..0000000 --- a/src/background/actions/command.js +++ /dev/null @@ -1,147 +0,0 @@ -import actions from '../actions'; -import * as consoleActions from './console'; -import * as tabs from '../shared/tabs'; -import * as bookmarks from '../shared/bookmarks'; -import * as parsers from 'background/shared/commands/parsers'; -import * as properties from 'shared/settings/properties'; - -const openCommand = async(url) => { - let got = await browser.tabs.query({ - active: true, currentWindow: true - }); - if (got.length > 0) { - return browser.tabs.update(got[0].id, { url: url }); - } -}; - -const tabopenCommand = (url) => { - return browser.tabs.create({ url: url }); -}; - -const tabcloseCommand = async() => { - let got = await browser.tabs.query({ - active: true, currentWindow: true - }); - return browser.tabs.remove(got.map(tab => tab.id)); -}; - -const tabcloseAllCommand = () => { - return browser.tabs.query({ - currentWindow: true - }).then((tabList) => { - return browser.tabs.remove(tabList.map(tab => tab.id)); - }); -}; - -const winopenCommand = (url) => { - return browser.windows.create({ url }); -}; - -const bufferCommand = async(keywords) => { - if (keywords.length === 0) { - return; - } - let keywordsStr = keywords.join(' '); - let got = await browser.tabs.query({ - active: true, currentWindow: true - }); - if (got.length === 0) { - return; - } - if (isNaN(keywordsStr)) { - return tabs.selectByKeyword(got[0], keywordsStr); - } - let index = parseInt(keywordsStr, 10) - 1; - return tabs.selectAt(index); -}; - -const addbookmarkCommand = async(tab, args) => { - if (!args[0]) { - return { type: '' }; - } - let item = await bookmarks.create(args.join(' '), tab.url); - if (!item) { - return consoleActions.error(tab, 'Could not create a bookmark'); - } - return consoleActions.info(tab, 'Saved current page: ' + item.url); -}; - -const setCommand = (args) => { - if (!args[0]) { - return { type: '' }; - } - - let [name, value] = parsers.parseSetOption(args[0], properties.types); - return { - type: actions.SETTING_SET_PROPERTY, - name, - value - }; -}; - -// eslint-disable-next-line complexity, max-lines-per-function -const doExec = async(tab, line, settings) => { - let [name, args] = parsers.parseCommandLine(line); - - switch (name) { - case 'o': - case 'open': - await openCommand(parsers.normalizeUrl(args, settings.search)); - break; - case 't': - case 'tabopen': - await tabopenCommand(parsers.normalizeUrl(args, settings.search)); - break; - case 'w': - case 'winopen': - await winopenCommand(parsers.normalizeUrl(args, settings.search)); - break; - case 'b': - case 'buffer': - await bufferCommand(args); - break; - case 'bd': - case 'bdel': - case 'bdelete': - await tabs.closeTabByKeywords(args.join(' ')); - break; - case 'bd!': - case 'bdel!': - case 'bdelete!': - await tabs.closeTabByKeywordsForce(args.join(' ')); - break; - case 'bdeletes': - await tabs.closeTabsByKeywords(args.join(' ')); - break; - case 'bdeletes!': - await tabs.closeTabsByKeywordsForce(args.join(' ')); - break; - case 'addbookmark': - return addbookmarkCommand(tab, args); - case 'set': - return setCommand(args); - case 'q': - case 'quit': - await tabcloseCommand(); - break; - case 'qa': - case 'quitall': - await tabcloseAllCommand(); - break; - default: - return consoleActions.error(tab, name + ' command is not defined'); - } - return { type: '' }; -}; - -// eslint-disable-next-line complexity -const exec = async(tab, line, settings) => { - try { - let action = await doExec(tab, line, settings); - return action; - } catch (e) { - return consoleActions.error(tab, e.toString()); - } -}; - -export { exec }; diff --git a/src/background/actions/console.js b/src/background/actions/console.js deleted file mode 100644 index d385b2d..0000000 --- a/src/background/actions/console.js +++ /dev/null @@ -1,41 +0,0 @@ -import messages from 'shared/messages'; - -const error = async(tab, text) => { - await browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_SHOW_ERROR, - text, - }); - return { type: '' }; -}; - -const info = async(tab, text) => { - await browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_SHOW_INFO, - text, - }); - return { type: '' }; -}; - -const showCommand = async(tab, command) => { - await browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_SHOW_COMMAND, - command, - }); - return { type: '' }; -}; - -const showFind = async(tab) => { - await browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_SHOW_FIND - }); - return { type: '' }; -}; - -const hide = async(tab) => { - await browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_HIDE, - }); - return { type: '' }; -}; - -export { error, info, showCommand, showFind, hide }; diff --git a/src/background/actions/find.js b/src/background/actions/find.js deleted file mode 100644 index 8da5572..0000000 --- a/src/background/actions/find.js +++ /dev/null @@ -1,10 +0,0 @@ -import actions from './index'; - -const setKeyword = (keyword) => { - return { - type: actions.FIND_SET_KEYWORD, - keyword, - }; -}; - -export { setKeyword }; diff --git a/src/background/actions/index.js b/src/background/actions/index.js deleted file mode 100644 index 3833389..0000000 --- a/src/background/actions/index.js +++ /dev/null @@ -1,11 +0,0 @@ -export default { - // Settings - SETTING_SET_SETTINGS: 'setting.set.settings', - SETTING_SET_PROPERTY: 'setting.set.property', - - // Find - FIND_SET_KEYWORD: 'find.set.keyword', - - // Tab - TAB_SELECTED: 'tab.selected', -}; diff --git a/src/background/actions/tab.js b/src/background/actions/tab.js deleted file mode 100644 index 0f32a90..0000000 --- a/src/background/actions/tab.js +++ /dev/null @@ -1,34 +0,0 @@ -import actions from './index'; - -const openNewTab = async( - url, openerTabId, background = false, adjacent = false -) => { - if (!adjacent) { - await browser.tabs.create({ url, active: !background }); - return { type: '' }; - } - let tabs = await browser.tabs.query({ - active: true, currentWindow: true - }); - await browser.tabs.create({ - url, - openerTabId, - active: !background, - index: tabs[0].index + 1 - }); - return { type: '' }; -}; - -const openToTab = async(url, tab) => { - await browser.tabs.update(tab.id, { url: url }); - return { type: '' }; -}; - -const selected = (tabId) => { - return { - type: actions.TAB_SELECTED, - tabId, - }; -}; - -export { openNewTab, openToTab, selected }; diff --git a/src/background/components/background.js b/src/background/components/background.js deleted file mode 100644 index 23131d2..0000000 --- a/src/background/components/background.js +++ /dev/null @@ -1,35 +0,0 @@ -import messages from 'shared/messages'; -import * as commandActions from 'background/actions/command'; - -export default class BackgroundComponent { - constructor(store) { - this.store = store; - - browser.runtime.onMessage.addListener((message, sender) => { - try { - return this.onMessage(message, sender); - } catch (e) { - return browser.tabs.sendMessage(sender.tab.id, { - type: messages.CONSOLE_SHOW_ERROR, - text: e.message, - }); - } - }); - } - - onMessage(message, sender) { - let settings = this.store.getState().setting; - - switch (message.type) { - } - } - - async broadcastSettingsChanged() { - let tabs = await browser.tabs.query({}); - for (let tab of tabs) { - browser.tabs.sendMessage(tab.id, { - type: messages.SETTINGS_CHANGED, - }); - } - } -} diff --git a/src/background/components/indicator.js b/src/background/components/indicator.js deleted file mode 100644 index 1ded329..0000000 --- a/src/background/components/indicator.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as indicators from '../shared/indicators'; -import messages from 'shared/messages'; - -export default class IndicatorComponent { - constructor(store) { - this.store = store; - - messages.onMessage(this.onMessage.bind(this)); - - browser.browserAction.onClicked.addListener(this.onClicked); - browser.tabs.onActivated.addListener(async(info) => { - await browser.tabs.query({ currentWindow: true }); - return this.onTabActivated(info); - }); - } - - async onTabActivated(info) { - let { enabled } = await browser.tabs.sendMessage(info.tabId, { - type: messages.ADDON_ENABLED_QUERY, - }); - return this.updateIndicator(enabled); - } - - onClicked(tab) { - browser.tabs.sendMessage(tab.id, { - type: messages.ADDON_TOGGLE_ENABLED, - }); - } - - onMessage(message) { - switch (message.type) { - case messages.ADDON_ENABLED_RESPONSE: - return this.updateIndicator(message.enabled); - } - } - - updateIndicator(enabled) { - if (enabled) { - return indicators.enable(); - } - return indicators.disable(); - } -} diff --git a/src/background/components/operation.js b/src/background/components/operation.js deleted file mode 100644 index 57b2497..0000000 --- a/src/background/components/operation.js +++ /dev/null @@ -1,124 +0,0 @@ -import messages from 'shared/messages'; -import operations from 'shared/operations'; -import * as tabs from '../shared//tabs'; -import * as zooms from '../shared/zooms'; -import * as consoleActions from '../actions/console'; - -export default class BackgroundComponent { - constructor(store) { - this.store = store; - - browser.runtime.onMessage.addListener((message, sender) => { - try { - return this.onMessage(message, sender); - } catch (e) { - return browser.tabs.sendMessage(sender.tab.id, { - type: messages.CONSOLE_SHOW_ERROR, - text: e.message, - }); - } - }); - } - - onMessage(message, sender) { - switch (message.type) { - } - } - - // eslint-disable-next-line complexity, max-lines-per-function - async exec(operation, tab) { - let tabState = this.store.getState().tab; - - switch (operation.type) { - case operations.TAB_CLOSE: - await tabs.closeTab(tab.id); - break; - case operations.TAB_CLOSE_FORCE: - await tabs.closeTabForce(tab.id); - break; - case operations.TAB_REOPEN: - await tabs.reopenTab(); - break; - case operations.TAB_PREV: - await tabs.selectPrevTab(tab.index, operation.count); - break; - case operations.TAB_NEXT: - await tabs.selectNextTab(tab.index, operation.count); - break; - case operations.TAB_FIRST: - await tabs.selectFirstTab(); - break; - case operations.TAB_LAST: - await tabs.selectLastTab(); - break; - case operations.TAB_PREV_SEL: - if (tabState.previousSelected > 0) { - await tabs.selectTab(tabState.previousSelected); - } - break; - case operations.TAB_RELOAD: - await tabs.reload(tab, operation.cache); - break; - case operations.TAB_PIN: - await tabs.updateTabPinned(tab, true); - break; - case operations.TAB_UNPIN: - await tabs.updateTabPinned(tab, false); - break; - case operations.TAB_TOGGLE_PINNED: - await tabs.toggleTabPinned(tab); - break; - case operations.TAB_DUPLICATE: - await tabs.duplicate(tab.id); - break; - case operations.ZOOM_IN: - await zooms.zoomIn(); - break; - case operations.ZOOM_OUT: - await zooms.zoomOut(); - break; - case operations.ZOOM_NEUTRAL: - await zooms.neutral(); - break; - case operations.COMMAND_SHOW: - return consoleActions.showCommand(tab, ''); - case operations.COMMAND_SHOW_OPEN: - if (operation.alter) { - // alter url - return consoleActions.showCommand(tab, 'open ' + tab.url); - } - return consoleActions.showCommand(tab, 'open '); - case operations.COMMAND_SHOW_TABOPEN: - if (operation.alter) { - // alter url - return consoleActions.showCommand(tab, 'tabopen ' + tab.url); - } - return consoleActions.showCommand(tab, 'tabopen '); - case operations.COMMAND_SHOW_WINOPEN: - if (operation.alter) { - // alter url - return consoleActions.showCommand(tab, 'winopen ' + tab.url); - } - return consoleActions.showCommand(tab, 'winopen '); - case operations.COMMAND_SHOW_BUFFER: - return consoleActions.showCommand(tab, 'buffer '); - case operations.COMMAND_SHOW_ADDBOOKMARK: - if (operation.alter) { - return consoleActions.showCommand(tab, 'addbookmark ' + tab.title); - } - return consoleActions.showCommand(tab, 'addbookmark '); - case operations.FIND_START: - return consoleActions.showFind(tab); - case operations.CANCEL: - return consoleActions.hide(tab); - case operations.PAGE_SOURCE: - await browser.tabs.create({ - url: 'view-source:' + tab.url, - index: tab.index + 1, - openerTabId: tab.id, - }); - break; - } - return { type: '' }; - } -} diff --git a/src/background/components/tab.js b/src/background/components/tab.js deleted file mode 100644 index 6af3fd7..0000000 --- a/src/background/components/tab.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as tabActions from '../actions/tab'; - -export default class TabComponent { - constructor(store) { - this.store = store; - - browser.tabs.onActivated.addListener(async(info) => { - await browser.tabs.query({ currentWindow: true }); - return this.onTabActivated(info); - }); - } - - onTabActivated(info) { - return this.store.dispatch(tabActions.selected(info.tabId)); - } -} diff --git a/src/background/domains/command-docs.js b/src/background/domains/command-docs.js new file mode 100644 index 0000000..0b8ede7 --- /dev/null +++ b/src/background/domains/command-docs.js @@ -0,0 +1,12 @@ +export default { + set: 'Set a value of the property', + open: 'Open a URL or search by keywords in current tab', + tabopen: 'Open a URL or search by keywords in new tab', + winopen: 'Open a URL or search by keywords in new window', + buffer: 'Sekect tabs by matched keywords', + bdelete: 'Close a certain tab matched by keywords', + bdeletes: 'Close all tabs matched by keywords', + quit: 'Close the current tab', + quitall: 'Close all tabs', +}; + diff --git a/src/background/index.js b/src/background/index.js index 70d514f..3492398 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,25 +1,7 @@ -import BackgroundComponent from 'background/components/background'; -import OperationComponent from 'background/components/operation'; -import TabComponent from 'background/components/tab'; -import reducers from 'background/reducers'; -import { createStore, applyMiddleware } from 'redux'; -import promise from 'redux-promise'; - import ContentMessageListener from './infrastructures/content-message-listener'; import SettingController from './controllers/setting'; import VersionRepository from './controllers/version'; -const store = createStore( - reducers, - applyMiddleware(promise), -); - -/* eslint-disable no-unused-vars */ -const backgroundComponent = new BackgroundComponent(store); -const operationComponent = new OperationComponent(store); -const tabComponent = new TabComponent(store); -/* eslint-enable no-unused-vars */ - new SettingController().reload(); new VersionRepository().notifyIfUpdated(); diff --git a/src/background/reducers/find.js b/src/background/reducers/find.js deleted file mode 100644 index bbc6b36..0000000 --- a/src/background/reducers/find.js +++ /dev/null @@ -1,15 +0,0 @@ -import actions from 'content/actions'; - -const defaultState = { - keyword: null, -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.FIND_SET_KEYWORD: - return { ...state, - keyword: action.keyword, }; - default: - return state; - } -} diff --git a/src/background/reducers/index.js b/src/background/reducers/index.js deleted file mode 100644 index 465f927..0000000 --- a/src/background/reducers/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import { combineReducers } from 'redux'; -import setting from './setting'; -import find from './find'; -import tab from './tab'; - -export default combineReducers({ - setting, find, tab, -}); diff --git a/src/background/reducers/setting.js b/src/background/reducers/setting.js deleted file mode 100644 index 8dbc1b4..0000000 --- a/src/background/reducers/setting.js +++ /dev/null @@ -1,22 +0,0 @@ -import actions from 'background/actions'; - -const defaultState = { - value: {}, -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.SETTING_SET_SETTINGS: - return { - value: action.value, - }; - case actions.SETTING_SET_PROPERTY: - return { - value: { ...state.value, - properties: { ...state.value.properties, [action.name]: action.value }} - }; - default: - return state; - } -} - diff --git a/src/background/reducers/tab.js b/src/background/reducers/tab.js deleted file mode 100644 index e0cdf32..0000000 --- a/src/background/reducers/tab.js +++ /dev/null @@ -1,19 +0,0 @@ -import actions from 'background/actions'; - -const defaultState = { - previousSelected: -1, - currentSelected: -1, -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.TAB_SELECTED: - return { - previousSelected: state.currentSelected, - currentSelected: action.tabId, - }; - default: - return state; - } -} - diff --git a/src/background/shared/bookmarks.js b/src/background/shared/bookmarks.js deleted file mode 100644 index 5e7927b..0000000 --- a/src/background/shared/bookmarks.js +++ /dev/null @@ -1,9 +0,0 @@ -const create = (title, url) => { - return browser.bookmarks.create({ - type: 'bookmark', - title, - url, - }); -}; - -export { create }; diff --git a/src/background/shared/commands/docs.js b/src/background/shared/commands/docs.js deleted file mode 100644 index c73eb71..0000000 --- a/src/background/shared/commands/docs.js +++ /dev/null @@ -1,11 +0,0 @@ -export default { - set: 'Set a value of the property', - open: 'Open a URL or search by keywords in current tab', - tabopen: 'Open a URL or search by keywords in new tab', - winopen: 'Open a URL or search by keywords in new window', - buffer: 'Sekect tabs by matched keywords', - bdelete: 'Close a certain tab matched by keywords', - bdeletes: 'Close all tabs matched by keywords', - quit: 'Close the current tab', - quitall: 'Close all tabs', -}; diff --git a/src/background/shared/commands/parsers.js b/src/background/shared/commands/parsers.js deleted file mode 100644 index fb37d2a..0000000 --- a/src/background/shared/commands/parsers.js +++ /dev/null @@ -1,59 +0,0 @@ -const normalizeUrl = (args, searchConfig) => { - let concat = args.join(' '); - try { - return new URL(concat).href; - } catch (e) { - if (concat.includes('.') && !concat.includes(' ')) { - return 'http://' + concat; - } - let query = concat; - let template = searchConfig.engines[ - searchConfig.default - ]; - for (let key in searchConfig.engines) { - if (args[0] === key) { - query = args.slice(1).join(' '); - template = searchConfig.engines[key]; - } - } - 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]; - } -}; - -const parseCommandLine = (line) => { - let words = line.trim().split(/ +/); - let name = words.shift(); - return [name, words]; -}; - -export { normalizeUrl, parseCommandLine, parseSetOption }; diff --git a/src/background/shared/completions/bookmarks.js b/src/background/shared/completions/bookmarks.js deleted file mode 100644 index bd753af..0000000 --- a/src/background/shared/completions/bookmarks.js +++ /dev/null @@ -1,14 +0,0 @@ -const getCompletions = async(keywords) => { - let items = await browser.bookmarks.search({ query: keywords }); - return items.filter((item) => { - let url = undefined; - try { - url = new URL(item.url); - } catch (e) { - return false; - } - return item.type === 'bookmark' && url.protocol !== 'place:'; - }).slice(0, 10); -}; - -export { getCompletions }; diff --git a/src/background/shared/completions/histories.js b/src/background/shared/completions/histories.js deleted file mode 100644 index 2d35401..0000000 --- a/src/background/shared/completions/histories.js +++ /dev/null @@ -1,82 +0,0 @@ -const filterHttp = (items) => { - const httpsHosts = items - .filter(item => item[1].protocol === 'https:') - .map(item => item[1].host); - const httpsHostSet = new Set(httpsHosts); - return items.filter( - item => !(item[1].protocol === 'http:' && httpsHostSet.has(item[1].host)) - ); -}; - -const filterEmptyTitle = (items) => { - return items.filter(item => item[0].title && item[0].title !== ''); -}; - -const filterClosedPath = (items) => { - const allSimplePaths = items - .filter(item => item[1].hash === '' && item[1].search === '') - .map(item => item[1].origin + item[1].pathname); - const allSimplePathSet = new Set(allSimplePaths); - return items.filter( - item => !(item[1].hash === '' && item[1].search === '' && - (/\/$/).test(item[1].pathname) && - allSimplePathSet.has( - (item[1].origin + item[1].pathname).replace(/\/$/, '') - ) - ) - ); -}; - -const reduceByPathname = (items, min) => { - let hash = {}; - for (let item of items) { - let pathname = item[1].origin + item[1].pathname; - if (!hash[pathname]) { - hash[pathname] = item; - } else if (hash[pathname][1].href.length > item[1].href.length) { - hash[pathname] = item; - } - } - let filtered = Object.values(hash); - if (filtered.length < min) { - return items; - } - return filtered; -}; - -const reduceByOrigin = (items, min) => { - let hash = {}; - for (let item of items) { - let origin = item[1].origin; - if (!hash[origin]) { - hash[origin] = item; - } else if (hash[origin][1].href.length > item[1].href.length) { - hash[origin] = item; - } - } - let filtered = Object.values(hash); - if (filtered.length < min) { - return items; - } - return filtered; -}; - -const getCompletions = async(keyword) => { - let historyItems = await browser.history.search({ - text: keyword, - startTime: 0, - }); - return [historyItems.map(item => [item, new URL(item.url)])] - .map(filterEmptyTitle) - .map(filterHttp) - .map(filterClosedPath) - .map(items => reduceByPathname(items, 10)) - .map(items => reduceByOrigin(items, 10)) - .map(items => items - .sort((x, y) => x[0].visitCount < y[0].visitCount) - .slice(0, 10) - .map(item => item[0]) - )[0]; -}; - -export { getCompletions }; diff --git a/src/background/shared/completions/index.js b/src/background/shared/completions/index.js deleted file mode 100644 index 157604e..0000000 --- a/src/background/shared/completions/index.js +++ /dev/null @@ -1,173 +0,0 @@ -import commandDocs from 'background/shared/commands/docs'; -import * as tabs from './tabs'; -import * as histories from './histories'; -import * as bookmarks from './bookmarks'; -import * as properties from 'shared/settings/properties'; - -const completeCommands = (typing) => { - let keys = Object.keys(commandDocs); - return keys - .filter(name => name.startsWith(typing)) - .map(name => ({ - caption: name, - content: name, - url: commandDocs[name], - })); -}; - -const getSearchCompletions = (command, keywords, searchConfig) => { - let engineNames = Object.keys(searchConfig.engines); - let engineItems = engineNames.filter(name => name.startsWith(keywords)) - .map(name => ({ - caption: name, - content: command + ' ' + name - })); - return Promise.resolve(engineItems); -}; - -const getHistoryCompletions = async(command, keywords) => { - let items = await histories.getCompletions(keywords); - return items.map((page) => { - return { - caption: page.title, - content: command + ' ' + page.url, - url: page.url - }; - }); -}; - -const getBookmarksCompletions = async(command, keywords) => { - let items = await bookmarks.getCompletions(keywords); - return items.map(item => ({ - caption: item.title, - content: command + ' ' + item.url, - url: item.url, - })); -}; - -const getOpenCompletions = async(command, keywords, searchConfig) => { - let engineItems = await getSearchCompletions(command, keywords, searchConfig); - let historyItems = await getHistoryCompletions(command, keywords); - let bookmarkItems = await getBookmarksCompletions(command, keywords); - let completions = []; - if (engineItems.length > 0) { - completions.push({ - name: 'Search Engines', - items: engineItems - }); - } - if (historyItems.length > 0) { - completions.push({ - name: 'History', - items: historyItems - }); - } - if (bookmarkItems.length > 0) { - completions.push({ - name: 'Bookmarks', - items: bookmarkItems - }); - } - return completions; -}; - -const getBufferCompletions = async(command, keywords, excludePinned) => { - let items = await tabs.getCompletions(keywords, excludePinned); - items = items.map(tab => ({ - caption: tab.title, - content: command + ' ' + tab.title, - url: tab.url, - icon: tab.favIconUrl - })); - return [ - { - name: 'Buffers', - items: items - } - ]; -}; - -const getSetCompletions = (command, keywords) => { - let keys = Object.keys(properties.docs).filter( - name => name.startsWith(keywords) - ); - let items = keys.map((key) => { - if (properties.types[key] === 'boolean') { - return [ - { - caption: key, - content: command + ' ' + key, - url: 'Enable ' + properties.docs[key], - }, { - caption: 'no' + key, - content: command + ' no' + key, - url: 'Disable ' + properties.docs[key], - } - ]; - } - return [ - { - caption: key, - content: command + ' ' + key, - url: 'Set ' + properties.docs[key], - } - ]; - }); - items = items.reduce((acc, val) => acc.concat(val), []); - if (items.length === 0) { - return Promise.resolve([]); - } - return Promise.resolve([ - { - name: 'Properties', - items, - } - ]); -}; - -const complete = (line, settings) => { - let trimmed = line.trimStart(); - let words = trimmed.split(/ +/); - let name = words[0]; - if (words.length === 1) { - let items = completeCommands(name); - if (items.length === 0) { - return Promise.resolve([]); - } - return Promise.resolve([ - { - name: 'Console Command', - items: completeCommands(name), - } - ]); - } - let keywords = trimmed.slice(name.length).trimStart(); - - switch (words[0]) { - case 'o': - case 'open': - case 't': - case 'tabopen': - case 'w': - case 'winopen': - return getOpenCompletions(name, keywords, settings.search); - case 'b': - case 'buffer': - return getBufferCompletions(name, keywords, false); - case 'bd!': - case 'bdel!': - case 'bdelete!': - case 'bdeletes!': - return getBufferCompletions(name, keywords, false); - case 'bd': - case 'bdel': - case 'bdelete': - case 'bdeletes': - return getBufferCompletions(name, keywords, true); - case 'set': - return getSetCompletions(name, keywords); - } - return Promise.resolve([]); -}; - -export { complete }; diff --git a/src/background/shared/completions/tabs.js b/src/background/shared/completions/tabs.js deleted file mode 100644 index bdb2741..0000000 --- a/src/background/shared/completions/tabs.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as tabs from '../tabs'; - -const getCompletions = (keyword, excludePinned) => { - return tabs.queryByKeyword(keyword, excludePinned); -}; - - -export { getCompletions }; diff --git a/src/background/shared/indicators.js b/src/background/shared/indicators.js deleted file mode 100644 index 74002c4..0000000 --- a/src/background/shared/indicators.js +++ /dev/null @@ -1,13 +0,0 @@ -const enable = () => { - return browser.browserAction.setIcon({ - path: 'resources/enabled_32x32.png', - }); -}; - -const disable = () => { - return browser.browserAction.setIcon({ - path: 'resources/disabled_32x32.png', - }); -}; - -export { enable, disable }; diff --git a/src/background/shared/tabs.js b/src/background/shared/tabs.js deleted file mode 100644 index 26e2e44..0000000 --- a/src/background/shared/tabs.js +++ /dev/null @@ -1,158 +0,0 @@ -import * as tabCompletions from './completions/tabs'; - -const closeTab = async(id) => { - let tab = await browser.tabs.get(id); - if (!tab.pinned) { - return browser.tabs.remove(id); - } -}; - -const closeTabForce = (id) => { - return browser.tabs.remove(id); -}; - -const queryByKeyword = async(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); - }); -}; - -const closeTabByKeywords = async(keyword) => { - let tabs = await queryByKeyword(keyword, true); - if (tabs.length === 0) { - throw new Error('No matching buffer for ' + keyword); - } else if (tabs.length > 1) { - throw new Error('More than one match for ' + keyword); - } - return browser.tabs.remove(tabs[0].id); -}; - -const closeTabByKeywordsForce = async(keyword) => { - let tabs = await queryByKeyword(keyword, false); - if (tabs.length === 0) { - throw new Error('No matching buffer for ' + keyword); - } else if (tabs.length > 1) { - throw new Error('More than one match for ' + keyword); - } - return browser.tabs.remove(tabs[0].id); -}; - -const closeTabsByKeywords = async(keyword) => { - let tabs = await tabCompletions.getCompletions(keyword); - tabs = tabs.filter(tab => !tab.pinned); - return browser.tabs.remove(tabs.map(tab => tab.id)); -}; - -const closeTabsByKeywordsForce = async(keyword) => { - let tabs = await tabCompletions.getCompletions(keyword); - return browser.tabs.remove(tabs.map(tab => tab.id)); -}; - -const reopenTab = async() => { - let window = await browser.windows.getCurrent(); - let sessions = await browser.sessions.getRecentlyClosed(); - let session = sessions.find((s) => { - return s.tab && s.tab.windowId === window.id; - }); - if (!session) { - return; - } - if (session.tab) { - return browser.sessions.restore(session.tab.sessionId); - } - return browser.sessions.restore(session.window.sessionId); -}; - -const selectAt = async(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 }); -}; - -const selectByKeyword = async(current, keyword) => { - let tabs = await queryByKeyword(keyword); - if (tabs.length === 0) { - throw new RangeError('No matching buffer for ' + keyword); - } - for (let tab of tabs) { - if (tab.index > current.index) { - return browser.tabs.update(tab.id, { active: true }); - } - } - return browser.tabs.update(tabs[0].id, { active: true }); -}; - -const selectPrevTab = async(current, count) => { - let tabs = await browser.tabs.query({ currentWindow: true }); - if (tabs.length < 2) { - return; - } - let select = (current - count + tabs.length) % tabs.length; - let id = tabs[select].id; - return browser.tabs.update(id, { active: true }); -}; - -const selectNextTab = async(current, count) => { - let tabs = await browser.tabs.query({ currentWindow: true }); - if (tabs.length < 2) { - return; - } - let select = (current + count) % tabs.length; - let id = tabs[select].id; - return browser.tabs.update(id, { active: true }); -}; - -const selectFirstTab = async() => { - let tabs = await browser.tabs.query({ currentWindow: true }); - let id = tabs[0].id; - return browser.tabs.update(id, { active: true }); -}; - -const selectLastTab = async() => { - let tabs = await browser.tabs.query({ currentWindow: true }); - let id = tabs[tabs.length - 1].id; - return browser.tabs.update(id, { active: true }); -}; - -const selectTab = (id) => { - return browser.tabs.update(id, { active: true }); -}; - -const reload = (current, cache) => { - return browser.tabs.reload( - current.id, - { bypassCache: cache } - ); -}; - -const updateTabPinned = (current, pinned) => { - return browser.tabs.update(current.id, { pinned }); -}; - -const toggleTabPinned = (current) => { - return updateTabPinned(current, !current.pinned); -}; - -const duplicate = (id) => { - return browser.tabs.duplicate(id); -}; - -export { - closeTab, closeTabForce, - queryByKeyword, closeTabByKeywords, closeTabByKeywordsForce, - closeTabsByKeywords, closeTabsByKeywordsForce, - reopenTab, selectAt, selectByKeyword, - selectPrevTab, selectNextTab, selectFirstTab, - selectLastTab, selectTab, reload, updateTabPinned, - toggleTabPinned, duplicate -}; diff --git a/src/background/shared/zooms.js b/src/background/shared/zooms.js deleted file mode 100644 index 17b28fa..0000000 --- a/src/background/shared/zooms.js +++ /dev/null @@ -1,32 +0,0 @@ -// For chromium -// const ZOOM_SETTINGS = [ -// 0.25, 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, 4.00, 5.00 -// ]; - -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 -]; - -const zoomIn = async(tabId = undefined) => { - let current = await browser.tabs.getZoom(tabId); - let factor = ZOOM_SETTINGS.find(f => f > current); - if (factor) { - return browser.tabs.setZoom(tabId, factor); - } -}; - -const zoomOut = async(tabId = undefined) => { - let current = await browser.tabs.getZoom(tabId); - let factor = [].concat(ZOOM_SETTINGS).reverse().find(f => f < current); - if (factor) { - return browser.tabs.setZoom(tabId, factor); - } -}; - -const neutral = (tabId = undefined) => { - return browser.tabs.setZoom(tabId, 1); -}; - -export { zoomIn, zoomOut, neutral }; diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js index 626f9e2..92a54c1 100644 --- a/src/background/usecases/completions.js +++ b/src/background/usecases/completions.js @@ -1,8 +1,8 @@ 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 CommandDocs from 'background/shared/commands/docs'; import * as filters from './filters'; import SettingRepository from '../repositories/setting'; import * as properties from '../../shared/settings/properties'; diff --git a/test/background/actions/find.test.js b/test/background/actions/find.test.js deleted file mode 100644 index 6b0b846..0000000 --- a/test/background/actions/find.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import actions from 'background/actions'; -import * as findActions from 'background/actions/find'; - -describe("find actions", () => { - describe("setKeyword", () => { - it('create FIND_SET_KEYWORD action', () => { - let action = findActions.setKeyword('banana'); - expect(action.type).to.equal(actions.FIND_SET_KEYWORD); - expect(action.keyword).to.equal('banana'); - }); - }); -}); diff --git a/test/background/actions/tab.test.js b/test/background/actions/tab.test.js deleted file mode 100644 index ab57374..0000000 --- a/test/background/actions/tab.test.js +++ /dev/null @@ -1,13 +0,0 @@ -import actions from 'background/actions'; -import * as tabActions from 'background/actions/tab'; - -describe("tab actions", () => { - describe("selected", () => { - it('create TAB_SELECTED action', () => { - let action = tabActions.selected(123); - expect(action.type).to.equal(actions.TAB_SELECTED); - expect(action.tabId).to.equal(123); - }); - }); -}); - diff --git a/test/background/reducers/find.test.js b/test/background/reducers/find.test.js deleted file mode 100644 index c366223..0000000 --- a/test/background/reducers/find.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import actions from 'background/actions'; -import findReducer from 'background/reducers/find'; - -describe("find reducer", () => { - it('return the initial state', () => { - let state = findReducer(undefined, {}); - expect(state).to.have.deep.property('keyword', null); - }); - - it('return next state for FIND_SET_KEYWORD', () => { - let action = { - type: actions.FIND_SET_KEYWORD, - keyword: 'cherry', - }; - let state = findReducer(undefined, action); - expect(state).to.have.deep.property('keyword', 'cherry') - }); -}); diff --git a/test/background/reducers/setting.test.js b/test/background/reducers/setting.test.js deleted file mode 100644 index 24d02ea..0000000 --- a/test/background/reducers/setting.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import actions from 'background/actions'; -import settingReducer from 'background/reducers/setting'; - -describe("setting reducer", () => { - it('return the initial state', () => { - let state = settingReducer(undefined, {}); - expect(state).to.have.deep.property('value', {}); - }); - - it('return next state for SETTING_SET_SETTINGS', () => { - let action = { - type: actions.SETTING_SET_SETTINGS, - value: { key: 123 }, - }; - let state = settingReducer(undefined, action); - expect(state).to.have.deep.property('value', { key: 123 }); - }); - - it('return next state for SETTING_SET_PROPERTY', () => { - let state = { - value: { - properties: { smoothscroll: true } - } - } - let action = { - type: actions.SETTING_SET_PROPERTY, - name: 'encoding', - value: 'utf-8', - }; - state = settingReducer(state, action); - - expect(state.value.properties).to.have.property('smoothscroll', true); - expect(state.value.properties).to.have.property('encoding', 'utf-8'); - }); -}); diff --git a/test/background/reducers/tab.test.js b/test/background/reducers/tab.test.js deleted file mode 100644 index 09fa8a7..0000000 --- a/test/background/reducers/tab.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import actions from 'background/actions'; -import tabReducer from 'background/reducers/tab'; - -describe("tab reducer", () => { - it('return the initial state', () => { - let state = tabReducer(undefined, {}); - expect(state.previousSelected).to.equal(-1); - expect(state.currentSelected).to.equal(-1); - }); - - it('return next state for TAB_SELECTED', () => { - let state = undefined; - - state = tabReducer(state, { type: actions.TAB_SELECTED, tabId: 123 }); - expect(state.previousSelected).to.equal(-1); - expect(state.currentSelected).to.equal(123); - - state = tabReducer(state, { type: actions.TAB_SELECTED, tabId: 456 }); - expect(state.previousSelected).to.equal(123); - expect(state.currentSelected).to.equal(456); - }); -}); diff --git a/test/background/shared/commands/parsers.test.js b/test/background/shared/commands/parsers.test.js deleted file mode 100644 index 4a0c9f0..0000000 --- a/test/background/shared/commands/parsers.test.js +++ /dev/null @@ -1,84 +0,0 @@ -import * as parsers from 'background/shared/commands/parsers'; - -describe("shared/commands/parsers", () => { - describe("#parsers.parseSetOption", () => { - it('parse set string', () => { - let [key, value] = parsers.parseSetOption('encoding=utf-8', { encoding: 'string' }); - expect(key).to.equal('encoding'); - expect(value).to.equal('utf-8'); - }); - - it('parse set empty string', () => { - let [key, value] = parsers.parseSetOption('encoding=', { encoding: 'string' }); - expect(key).to.equal('encoding'); - expect(value).to.equal(''); - }); - - it('parse set string', () => { - let [key, value] = parsers.parseSetOption('history=50', { history: 'number' }); - expect(key).to.equal('history'); - expect(value).to.equal(50); - }); - - it('parse set boolean', () => { - let [key, value] = parsers.parseSetOption('paste', { paste: 'boolean' }); - expect(key).to.equal('paste'); - expect(value).to.be.true; - - [key, value] = parsers.parseSetOption('nopaste', { paste: 'boolean' }); - expect(key).to.equal('paste'); - expect(value).to.be.false; - }); - - it('throws error on unknown property', () => { - expect(() => parsers.parseSetOption('charset=utf-8', {})).to.throw(Error, 'Unknown'); - expect(() => parsers.parseSetOption('smoothscroll', {})).to.throw(Error, 'Unknown'); - expect(() => parsers.parseSetOption('nosmoothscroll', {})).to.throw(Error, 'Unknown'); - }) - - it('throws error on invalid property', () => { - expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'number' })).to.throw(Error, 'Not number'); - expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'boolean' })).to.throw(Error, 'Invalid'); - expect(() => parsers.parseSetOption('charset=', { charset: 'boolean' })).to.throw(Error, 'Invalid'); - expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'string' })).to.throw(Error, 'Invalid'); - expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'number' })).to.throw(Error, 'Invalid'); - }) - }); - - describe('#normalizeUrl', () => { - const config = { - default: 'google', - engines: { - google: 'https://google.com/search?q={}', - yahoo: 'https://yahoo.com/search?q={}', - } - }; - - it('convertes search url', () => { - expect(parsers.normalizeUrl(['google', 'apple'], config)) - .to.equal('https://google.com/search?q=apple'); - expect(parsers.normalizeUrl(['yahoo', 'apple'], config)) - .to.equal('https://yahoo.com/search?q=apple'); - expect(parsers.normalizeUrl(['google', 'apple', 'banana'], config)) - .to.equal('https://google.com/search?q=apple%20banana'); - expect(parsers.normalizeUrl(['yahoo', 'C++CLI'], config)) - .to.equal('https://yahoo.com/search?q=C%2B%2BCLI'); - }); - - it('user default search engine', () => { - expect(parsers.normalizeUrl(['apple', 'banana'], config)) - .to.equal('https://google.com/search?q=apple%20banana'); - }); - }); - - describe('#parseCommandLine', () => { - it('parse command line as name and args', () => { - expect(parsers.parseCommandLine('open google apple')).to.deep.equal(['open', ['google', 'apple']]); - expect(parsers.parseCommandLine(' open google apple ')).to.deep.equal(['open', ['google', 'apple']]); - expect(parsers.parseCommandLine('')).to.deep.equal(['', []]); - expect(parsers.parseCommandLine(' ')).to.deep.equal(['', []]); - expect(parsers.parseCommandLine('exit')).to.deep.equal(['exit', []]); - expect(parsers.parseCommandLine(' exit ')).to.deep.equal(['exit', []]); - }); - }); -}); diff --git a/test/background/usecases/parsers.test.js b/test/background/usecases/parsers.test.js index fa0e757..a58c4a3 100644 --- a/test/background/usecases/parsers.test.js +++ b/test/background/usecases/parsers.test.js @@ -1,6 +1,6 @@ import * as parsers from 'background/usecases/parsers'; -describe("background/usecases/parsers", () => { +describe("shared/commands/parsers", () => { describe("#parsers.parseSetOption", () => { it('parse set string', () => { let [key, value] = parsers.parseSetOption('encoding=utf-8', { encoding: 'string' }); @@ -65,10 +65,9 @@ describe("background/usecases/parsers", () => { .to.equal('https://yahoo.com/search?q=C%2B%2BCLI'); }); - it('user default search engine', () => { + it('user default search engine', () => { expect(parsers.normalizeUrl('apple banana', config)) .to.equal('https://google.com/search?q=apple%20banana'); }); }); }); - -- cgit v1.2.3 From f23eeee4f15a9469bbcb183dddf39bdea6070b9f Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 28 Jul 2018 10:58:44 +0900 Subject: Use replace instead of trimStart --- src/background/usecases/parsers.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/background/usecases') diff --git a/src/background/usecases/parsers.js b/src/background/usecases/parsers.js index 650ccd0..cda26c3 100644 --- a/src/background/usecases/parsers.js +++ b/src/background/usecases/parsers.js @@ -1,3 +1,8 @@ +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; @@ -8,10 +13,10 @@ const normalizeUrl = (keywords, searchSettings) => { let template = searchSettings.engines[searchSettings.default]; let query = keywords; - let first = keywords.trimStart().split(' ')[0]; + let first = trimStart(keywords).split(' ')[0]; if (Object.keys(searchSettings.engines).includes(first)) { template = searchSettings.engines[first]; - query = keywords.trimStart().slice(first.length).trimStart(); + query = trimStart(trimStart(keywords).slice(first.length)); } return template.replace('{}', encodeURIComponent(query)); } -- cgit v1.2.3 From 691e9ca8f28a8430e4765ab1a9ae13687289a53b Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 28 Jul 2018 17:06:20 +0900 Subject: Fix last tab is undefined --- src/background/infrastructures/memory-storage.js | 6 +++++- src/background/usecases/operation.js | 4 +++- test/background/infrastructures/memory-storage.test.js | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) (limited to 'src/background/usecases') diff --git a/src/background/infrastructures/memory-storage.js b/src/background/infrastructures/memory-storage.js index 234a9b8..3a7e4f2 100644 --- a/src/background/infrastructures/memory-storage.js +++ b/src/background/infrastructures/memory-storage.js @@ -10,6 +10,10 @@ export default class MemoryStorage { } get(name) { - return JSON.parse(db[name]); + let data = db[name]; + if (!data) { + return undefined; + } + return JSON.parse(data); } } diff --git a/src/background/usecases/operation.js b/src/background/usecases/operation.js index f19c632..86f39ca 100644 --- a/src/background/usecases/operation.js +++ b/src/background/usecases/operation.js @@ -183,7 +183,9 @@ export default class OperationInteractor { onTabSelected(tabId) { let lastId = this.cache.get(CURRENT_SELECTED_KEY); - this.cache.set(LAST_SELECTED_KEY, lastId); + if (lastId) { + this.cache.set(LAST_SELECTED_KEY, lastId); + } this.cache.set(CURRENT_SELECTED_KEY, tabId); } } diff --git a/test/background/infrastructures/memory-storage.test.js b/test/background/infrastructures/memory-storage.test.js index 5d62880..0fea895 100644 --- a/test/background/infrastructures/memory-storage.test.js +++ b/test/background/infrastructures/memory-storage.test.js @@ -15,6 +15,11 @@ describe("background/infrastructures/memory-storage", () => { expect(cache.get('object')).to.deep.equal({ hello: '123' }); }); + it('returns undefined if no keys', () => { + let cache = new MemoryStorage(); + expect(cache.get('no-keys')).to.be.undefined; + }) + it('stored on shared memory', () => { let cache = new MemoryStorage(); cache.set('red', 'apple'); -- cgit v1.2.3 From 87b4f8e99718c22b1d0b3d0772a4d7b4b58b848c Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 28 Jul 2018 18:45:24 +0900 Subject: Empty buf completion --- src/background/usecases/completions.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/background/usecases') diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js index 92a54c1..2bf5b3b 100644 --- a/src/background/usecases/completions.js +++ b/src/background/usecases/completions.js @@ -106,6 +106,9 @@ export default class CompletionsInteractor { url: tab.url, icon: tab.favIconUrl })); + if (items.length === 0) { + return Promise.resolve(Completions.empty()); + } return new Completions([new CompletionGroup('Buffers', items)]); } -- cgit v1.2.3 From 7cd606fd70271d614292a732db3b19afbee6c63b Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 28 Jul 2018 18:52:39 +0900 Subject: Fix quitall command --- src/background/usecases/command.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/background/usecases') diff --git a/src/background/usecases/command.js b/src/background/usecases/command.js index 7887509..3dbf768 100644 --- a/src/background/usecases/command.js +++ b/src/background/usecases/command.js @@ -78,10 +78,10 @@ export default class CommandIndicator { return this.tabPresenter.remove([tab.id]); } - async quitall() { + async quitAll() { let tabs = await this.tabPresenter.getAll(); let ids = tabs.map(tab => tab.id); - this.tabPresenter.tabPresenter.remove(ids); + this.tabPresenter.remove(ids); } async addbookmark(title) { -- cgit v1.2.3 From 6f4e327b6f91aa687b373463d8bc284ffaceba53 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 28 Jul 2018 19:09:49 +0900 Subject: Fix indicate return values --- src/background/controllers/addon-enabled.js | 2 +- src/background/usecases/addon-enabled.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/background/usecases') diff --git a/src/background/controllers/addon-enabled.js b/src/background/controllers/addon-enabled.js index 0f5d801..6a9b776 100644 --- a/src/background/controllers/addon-enabled.js +++ b/src/background/controllers/addon-enabled.js @@ -6,6 +6,6 @@ export default class AddonEnabledController { } indicate(enabled) { - this.addonEnabledInteractor.indicate(enabled); + return this.addonEnabledInteractor.indicate(enabled); } } diff --git a/src/background/usecases/addon-enabled.js b/src/background/usecases/addon-enabled.js index 37eb1c2..d83192f 100644 --- a/src/background/usecases/addon-enabled.js +++ b/src/background/usecases/addon-enabled.js @@ -15,7 +15,7 @@ export default class AddonEnabledInteractor { } indicate(enabled) { - this.indicatorPresentor.indicate(enabled); + return this.indicatorPresentor.indicate(enabled); } onIndicatorClick(tabId) { -- cgit v1.2.3