From b2a37b8fc3e273dd71e1e3558c58be8002aa3789 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 26 Mar 2020 22:17:00 +0900 Subject: Query completions on open command by a completion source --- src/console/clients/CompletionClient.ts | 54 +++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/console/clients/CompletionClient.ts (limited to 'src/console/clients') diff --git a/src/console/clients/CompletionClient.ts b/src/console/clients/CompletionClient.ts new file mode 100644 index 0000000..d5f9b01 --- /dev/null +++ b/src/console/clients/CompletionClient.ts @@ -0,0 +1,54 @@ +import * as messages from "../../shared/messages"; +import { + ConsoleGetCompletionTypesResponse, + ConsoleRequestBookmarksResponse, + ConsoleRequestHistoryResponse, ConsoleRequestSearchEnginesResponse +} from "../../shared/messages"; +import CompletionType from "../../shared/CompletionType"; + +export type SearchEngines = { + title: string +} + +export type BookmarkItem = { + title: string + url: string +} + +export type HistoryItem = { + title: string + url: string +} + +export default class CompletionClient { + async getCompletionTypes(): Promise { + const resp = await browser.runtime.sendMessage({ + type: messages.CONSOLE_GET_COMPLETION_TYPES, + }) as ConsoleGetCompletionTypesResponse; + return resp; + } + + async requestSearchEngines(query: string): Promise { + const resp = await browser.runtime.sendMessage({ + type: messages.CONSOLE_REQUEST_SEARCH_ENGINES_MESSAGE, + query, + }) as ConsoleRequestSearchEnginesResponse; + return resp; + } + + async requestBookmarks(query: string): Promise { + const resp = await browser.runtime.sendMessage({ + type: messages.CONSOLE_REQUEST_BOOKMARKS, + query, + }) as ConsoleRequestBookmarksResponse; + return resp; + } + + async requestHistory(query: string): Promise { + const resp = await browser.runtime.sendMessage({ + type: messages.CONSOLE_REQUEST_HISTORY, + query, + }) as ConsoleRequestHistoryResponse; + return resp; + } +} -- cgit v1.2.3 From ea63c5f78b4c985e9d6dd106afe4f97bfeedbcd0 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 28 Mar 2020 21:35:06 +0900 Subject: Complete tabs by the completion packages --- src/background/completion/CompletionUseCase.ts | 59 ---------- src/background/completion/OpenCompletionUseCase.ts | 59 ++++++++++ src/background/completion/TabCompletionUseCase.ts | 34 ++++++ src/background/completion/TabItem.ts | 11 ++ src/background/completion/TabRepository.ts | 12 ++ .../completion/impl/TabRepositoryImpl.ts | 27 +++++ src/background/controllers/CompletionController.ts | 15 ++- src/background/di.ts | 4 + .../infrastructures/ContentMessageListener.ts | 2 + src/background/presenters/TabPresenter.ts | 65 +++++++--- src/background/usecases/AddonEnabledUseCase.ts | 4 +- src/background/usecases/CommandUseCase.ts | 4 +- src/background/usecases/CompletionsUseCase.ts | 6 +- src/background/usecases/ConsoleUseCase.ts | 4 +- src/background/usecases/FindUseCase.ts | 4 +- src/background/usecases/LinkUseCase.ts | 4 +- src/background/usecases/MarkUseCase.ts | 4 +- src/background/usecases/NavigateUseCase.ts | 4 +- src/background/usecases/TabSelectUseCase.ts | 4 +- src/background/usecases/TabUseCase.ts | 4 +- src/background/usecases/VersionUseCase.ts | 2 +- src/background/usecases/ZoomUseCase.ts | 4 +- src/console/actions/console.ts | 27 ++++- src/console/clients/CompletionClient.ts | 20 +++- src/console/components/Console.tsx | 15 +++ src/shared/TabFlag.ts | 7 ++ src/shared/messages.ts | 17 +++ .../completion/CompletionUseCase.test.ts | 131 --------------------- .../completion/OpenCompletionUseCase.test.ts | 131 +++++++++++++++++++++ .../completion/TabCompletionUseCase.test.ts | 105 +++++++++++++++++ 30 files changed, 552 insertions(+), 237 deletions(-) delete mode 100644 src/background/completion/CompletionUseCase.ts create mode 100644 src/background/completion/OpenCompletionUseCase.ts create mode 100644 src/background/completion/TabCompletionUseCase.ts create mode 100644 src/background/completion/TabItem.ts create mode 100644 src/background/completion/TabRepository.ts create mode 100644 src/background/completion/impl/TabRepositoryImpl.ts create mode 100644 src/shared/TabFlag.ts delete mode 100644 test/background/completion/CompletionUseCase.test.ts create mode 100644 test/background/completion/OpenCompletionUseCase.test.ts create mode 100644 test/background/completion/TabCompletionUseCase.test.ts (limited to 'src/console/clients') diff --git a/src/background/completion/CompletionUseCase.ts b/src/background/completion/CompletionUseCase.ts deleted file mode 100644 index f7531e7..0000000 --- a/src/background/completion/CompletionUseCase.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { inject, injectable } from "tsyringe"; -import CachedSettingRepository from "../repositories/CachedSettingRepository"; -import CompletionType from "../../shared/CompletionType"; -import BookmarkRepository from "./BookmarkRepository"; -import HistoryRepository from "./HistoryRepository"; - -export type BookmarkItem = { - title: string - url: string -} - -export type HistoryItem = { - title: string - url: string -} - -@injectable() -export default class CompletionUseCase { - constructor( - @inject('BookmarkRepository') private bookmarkRepository: BookmarkRepository, - @inject('HistoryRepository') private historyRepository: HistoryRepository, - @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository, - ) { - } - - async getCompletionTypes(): Promise { - const settings = await this.cachedSettingRepository.get(); - const types: CompletionType[] = []; - for (const c of settings.properties.complete) { - switch (c) { - case 's': - types.push(CompletionType.SearchEngines); - break; - case 'h': - types.push(CompletionType.History); - break; - case 'b': - types.push(CompletionType.Bookmarks); - break; - } - // ignore invalid characters in the complete property - } - return types; - } - - async requestSearchEngines(query: string): Promise { - const settings = await this.cachedSettingRepository.get(); - return Object.keys(settings.search.engines) - .filter(key => key.startsWith(query)) - } - - requestBookmarks(query: string): Promise { - return this.bookmarkRepository.queryBookmarks(query); - } - - requestHistory(query: string): Promise { - return this.historyRepository.queryHistories(query); - } -} \ No newline at end of file diff --git a/src/background/completion/OpenCompletionUseCase.ts b/src/background/completion/OpenCompletionUseCase.ts new file mode 100644 index 0000000..1b63e7c --- /dev/null +++ b/src/background/completion/OpenCompletionUseCase.ts @@ -0,0 +1,59 @@ +import { inject, injectable } from "tsyringe"; +import CachedSettingRepository from "../repositories/CachedSettingRepository"; +import CompletionType from "../../shared/CompletionType"; +import BookmarkRepository from "./BookmarkRepository"; +import HistoryRepository from "./HistoryRepository"; + +export type BookmarkItem = { + title: string + url: string +} + +export type HistoryItem = { + title: string + url: string +} + +@injectable() +export default class OpenCompletionUseCase { + constructor( + @inject('BookmarkRepository') private bookmarkRepository: BookmarkRepository, + @inject('HistoryRepository') private historyRepository: HistoryRepository, + @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository, + ) { + } + + async getCompletionTypes(): Promise { + const settings = await this.cachedSettingRepository.get(); + const types: CompletionType[] = []; + for (const c of settings.properties.complete) { + switch (c) { + case 's': + types.push(CompletionType.SearchEngines); + break; + case 'h': + types.push(CompletionType.History); + break; + case 'b': + types.push(CompletionType.Bookmarks); + break; + } + // ignore invalid characters in the complete property + } + return types; + } + + async requestSearchEngines(query: string): Promise { + const settings = await this.cachedSettingRepository.get(); + return Object.keys(settings.search.engines) + .filter(key => key.startsWith(query)) + } + + requestBookmarks(query: string): Promise { + return this.bookmarkRepository.queryBookmarks(query); + } + + requestHistory(query: string): Promise { + return this.historyRepository.queryHistories(query); + } +} \ No newline at end of file diff --git a/src/background/completion/TabCompletionUseCase.ts b/src/background/completion/TabCompletionUseCase.ts new file mode 100644 index 0000000..7e6dce3 --- /dev/null +++ b/src/background/completion/TabCompletionUseCase.ts @@ -0,0 +1,34 @@ +import { inject, injectable } from "tsyringe"; +import TabItem from "./TabItem"; +import TabRepository from "./TabRepository"; +import TabPresenter from "../presenters/TabPresenter"; +import TabFlag from "../../shared/TabFlag"; + +@injectable() +export default class TabCompletionUseCase { + constructor( + @inject('TabRepository') private tabRepository: TabRepository, + @inject('TabPresenter') private tabPresenter: TabPresenter, + ) { + } + + async queryTabs(query: string, excludePinned: boolean): Promise { + const lastTabId = await this.tabPresenter.getLastSelectedId(); + const tabs = await this.tabRepository.queryTabs(query, excludePinned); + return tabs.map(tab => { + let flag = TabFlag.None; + if (tab.active) { + flag = TabFlag.CurrentTab + } else if (tab.id == lastTabId) { + flag = TabFlag.LastTab + } + return { + index: tab.index + 1, + flag: flag, + title: tab.title, + url: tab.url, + faviconUrl : tab.faviconUrl + } + }); + } +} diff --git a/src/background/completion/TabItem.ts b/src/background/completion/TabItem.ts new file mode 100644 index 0000000..630855a --- /dev/null +++ b/src/background/completion/TabItem.ts @@ -0,0 +1,11 @@ +import TabFlag from "../../shared/TabFlag"; + +type TabItem = { + index: number + flag: TabFlag + title: string + url: string + faviconUrl?: string +} + +export default TabItem; \ No newline at end of file diff --git a/src/background/completion/TabRepository.ts b/src/background/completion/TabRepository.ts new file mode 100644 index 0000000..61fac3b --- /dev/null +++ b/src/background/completion/TabRepository.ts @@ -0,0 +1,12 @@ +export type Tab = { + id: number + index: number + active: boolean + title: string + url: string + faviconUrl?: string +} + +export default interface TabRepository { + queryTabs(query: string, excludePinned: boolean): Promise; +} diff --git a/src/background/completion/impl/TabRepositoryImpl.ts b/src/background/completion/impl/TabRepositoryImpl.ts new file mode 100644 index 0000000..6692b27 --- /dev/null +++ b/src/background/completion/impl/TabRepositoryImpl.ts @@ -0,0 +1,27 @@ +import TabRepository, { Tab } from "../TabRepository"; + +const COMPLETION_ITEM_LIMIT = 10; + +export default class TabRepositoryImpl implements TabRepository { + async queryTabs(query: string, excludePinned: boolean): Promise { + const tabs = await browser.tabs.query({ currentWindow: true }); + return tabs + .filter((t) => { + return t.url && t.url.toLowerCase().includes(query.toLowerCase()) || + t.title && t.title.toLowerCase().includes(query.toLowerCase()); + }) + .filter((t) => { + return !(excludePinned && t.pinned); + }) + .filter(item => item.id && item.title && item.url) + .slice(0, COMPLETION_ITEM_LIMIT) + .map(item => ({ + id: item.id!!, + url: item.url!!, + active: item.active, + title: item.title!!, + faviconUrl: item.favIconUrl, + index: item.index, + })) + } +} \ No newline at end of file diff --git a/src/background/controllers/CompletionController.ts b/src/background/controllers/CompletionController.ts index 313f38b..a268d15 100644 --- a/src/background/controllers/CompletionController.ts +++ b/src/background/controllers/CompletionController.ts @@ -1,14 +1,19 @@ import { ConsoleGetCompletionTypesResponse, - ConsoleRequestBookmarksResponse, ConsoleRequestHistoryResponse, ConsoleRequestSearchEnginesResponse + ConsoleRequestBookmarksResponse, + ConsoleRequestHistoryResponse, + ConsoleRequestSearchEnginesResponse, + ConsoleRequesttabsResponse } from "../../shared/messages"; import { injectable } from "tsyringe"; -import CompletionUseCase from "../completion/CompletionUseCase"; +import OpenCompletionUseCase from "../completion/OpenCompletionUseCase"; +import TabCompletionUseCase from "../completion/TabCompletionUseCase"; @injectable() export default class CompletionController { constructor( - private completionUseCase: CompletionUseCase, + private completionUseCase: OpenCompletionUseCase, + private tabCompletionUseCase: TabCompletionUseCase, ) { } @@ -28,4 +33,8 @@ export default class CompletionController { async requestHistory(query: string): Promise { return this.completionUseCase.requestHistory(query); } + + async queryTabs(query: string, excludePinned: boolean): Promise { + return this.tabCompletionUseCase.queryTabs(query, excludePinned); + } } \ No newline at end of file diff --git a/src/background/di.ts b/src/background/di.ts index 0b52e0b..c186262 100644 --- a/src/background/di.ts +++ b/src/background/di.ts @@ -6,6 +6,8 @@ import { CachedSettingRepositoryImpl } from "./repositories/CachedSettingReposit import { container } from 'tsyringe'; import HistoryRepositoryImpl from "./completion/impl/HistoryRepositoryImpl"; import BookmarkRepositoryImpl from "./completion/impl/BookmarkRepositoryImpl"; +import TabRepositoryImpl from "./completion/impl/TabRepositoryImpl"; +import {TabPresenterImpl} from "./presenters/TabPresenter"; container.register('LocalSettingRepository', { useValue: LocalSettingRepository }); container.register('SyncSettingRepository', { useClass: SyncSettingRepository }); @@ -13,3 +15,5 @@ container.register('CachedSettingRepository', { useClass: CachedSettingRepositor container.register('Notifier', { useClass: NotifierImpl }); container.register('HistoryRepository', { useClass: HistoryRepositoryImpl }); container.register('BookmarkRepository', { useClass: BookmarkRepositoryImpl }); +container.register('TabRepository', { useClass: TabRepositoryImpl }); +container.register('TabPresenter', { useClass: TabPresenterImpl }); diff --git a/src/background/infrastructures/ContentMessageListener.ts b/src/background/infrastructures/ContentMessageListener.ts index 62cd49f..13aa763 100644 --- a/src/background/infrastructures/ContentMessageListener.ts +++ b/src/background/infrastructures/ContentMessageListener.ts @@ -73,6 +73,8 @@ export default class ContentMessageListener { return this.completionController.requestBookmarks(message.query); case messages.CONSOLE_REQUEST_HISTORY: return this.completionController.requestHistory(message.query); + case messages.CONSOLE_REQUEST_TABS: + return this.completionController.queryTabs(message.query, message.excludePinned); case messages.CONSOLE_ENTER_COMMAND: return this.onConsoleEnterCommand(message.text); case messages.SETTINGS_QUERY: diff --git a/src/background/presenters/TabPresenter.ts b/src/background/presenters/TabPresenter.ts index 33d8bea..bded5a2 100644 --- a/src/background/presenters/TabPresenter.ts +++ b/src/background/presenters/TabPresenter.ts @@ -1,4 +1,3 @@ -import { injectable } from 'tsyringe'; import MemoryStorage from '../infrastructures/MemoryStorage'; const CURRENT_SELECTED_KEY = 'tabs.current.selected'; @@ -6,8 +5,41 @@ const LAST_SELECTED_KEY = 'tabs.last.selected'; type Tab = browser.tabs.Tab; -@injectable() -export default class TabPresenter { +export default interface TabPresenter { + open(url: string, tabId?: number): Promise; + + create(url: string, opts?: object): Promise; + + getCurrent(): Promise; + + getAll(): Promise; + + getLastSelectedId(): Promise; + + getByKeyword(keyword: string, excludePinned: boolean): Promise; + + select(tabId: number): Promise; + + remove(ids: number[]): Promise; + + reopen(): Promise; + + reload(tabId: number, cache: boolean): Promise; + + setPinned(tabId: number, pinned: boolean): Promise; + + duplicate(id: number): Promise; + + getZoom(tabId: number): Promise; + + setZoom(tabId: number, factor: number): Promise; + + onSelected( + listener: (arg: { tabId: number, windowId: number}) => void, + ): void; +} + +export class TabPresenterImpl implements TabPresenter { open(url: string, tabId?: number): Promise { return browser.tabs.update(tabId, { url }); } @@ -48,15 +80,15 @@ export default class TabPresenter { }); } - select(tabId: number): Promise { - return browser.tabs.update(tabId, { active: true }); + async select(tabId: number): Promise { + await browser.tabs.update(tabId, { active: true }); } - remove(ids: number[]): Promise { - return browser.tabs.remove(ids); + async remove(ids: number[]): Promise { + await browser.tabs.remove(ids); } - async reopen(): Promise { + async reopen(): Promise { const window = await browser.windows.getCurrent(); const sessions = await browser.sessions.getRecentlyClosed(); const session = sessions.find((s) => { @@ -66,19 +98,18 @@ export default class TabPresenter { return; } if (session.tab && session.tab.sessionId) { - return browser.sessions.restore(session.tab.sessionId); - } - if (session.window && session.window.sessionId) { - return browser.sessions.restore(session.window.sessionId); + await browser.sessions.restore(session.tab.sessionId); + } else if (session.window && session.window.sessionId) { + await browser.sessions.restore(session.window.sessionId); } } - reload(tabId: number, cache: boolean): Promise { - return browser.tabs.reload(tabId, { bypassCache: cache }); + async reload(tabId: number, cache: boolean): Promise { + await browser.tabs.reload(tabId, { bypassCache: cache }); } - setPinned(tabId: number, pinned: boolean): Promise { - return browser.tabs.update(tabId, { pinned }); + async setPinned(tabId: number, pinned: boolean): Promise { + await browser.tabs.update(tabId, { pinned }); } duplicate(id: number): Promise { @@ -100,7 +131,7 @@ export default class TabPresenter { } } -const tabPresenter = new TabPresenter(); +const tabPresenter = new TabPresenterImpl(); tabPresenter.onSelected((tab: any) => { const cache = new MemoryStorage(); diff --git a/src/background/usecases/AddonEnabledUseCase.ts b/src/background/usecases/AddonEnabledUseCase.ts index 9abd3dc..51f02e1 100644 --- a/src/background/usecases/AddonEnabledUseCase.ts +++ b/src/background/usecases/AddonEnabledUseCase.ts @@ -1,4 +1,4 @@ -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; import IndicatorPresenter from '../presenters/IndicatorPresenter'; import TabPresenter from '../presenters/TabPresenter'; import ContentMessageClient from '../infrastructures/ContentMessageClient'; @@ -7,7 +7,7 @@ import ContentMessageClient from '../infrastructures/ContentMessageClient'; export default class AddonEnabledUseCase { constructor( private indicatorPresentor: IndicatorPresenter, - private tabPresenter: TabPresenter, + @inject("TabPresenter") private tabPresenter: TabPresenter, private contentMessageClient: ContentMessageClient, ) { this.indicatorPresentor.onClick((tab) => { diff --git a/src/background/usecases/CommandUseCase.ts b/src/background/usecases/CommandUseCase.ts index 7dba664..d2d707e 100644 --- a/src/background/usecases/CommandUseCase.ts +++ b/src/background/usecases/CommandUseCase.ts @@ -14,7 +14,7 @@ import RepeatUseCase from '../usecases/RepeatUseCase'; @injectable() export default class CommandIndicator { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, private windowPresenter: WindowPresenter, private helpPresenter: HelpPresenter, @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository, @@ -80,7 +80,7 @@ export default class CommandIndicator { } const current = await this.tabPresenter.getCurrent(); - const tabs = await this.tabPresenter.getByKeyword(keywords); + const tabs = await this.tabPresenter.getByKeyword(keywords, false); if (tabs.length === 0) { throw new RangeError('No matching buffer for ' + keywords); } diff --git a/src/background/usecases/CompletionsUseCase.ts b/src/background/usecases/CompletionsUseCase.ts index b75b635..439c81a 100644 --- a/src/background/usecases/CompletionsUseCase.ts +++ b/src/background/usecases/CompletionsUseCase.ts @@ -5,17 +5,17 @@ import CompletionsRepository from '../repositories/CompletionsRepository'; import CachedSettingRepository from '../repositories/CachedSettingRepository'; import TabPresenter from '../presenters/TabPresenter'; import Properties from '../../shared/settings/Properties'; -import CompletionUseCase from "../completion/CompletionUseCase"; +import OpenCompletionUseCase from "../completion/OpenCompletionUseCase"; type Tab = browser.tabs.Tab; @injectable() export default class CompletionsUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, private completionsRepository: CompletionsRepository, @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository, - private completionUseCase: CompletionUseCase + private completionUseCase: OpenCompletionUseCase ) { } diff --git a/src/background/usecases/ConsoleUseCase.ts b/src/background/usecases/ConsoleUseCase.ts index 775a1e0..2de5bc1 100644 --- a/src/background/usecases/ConsoleUseCase.ts +++ b/src/background/usecases/ConsoleUseCase.ts @@ -1,4 +1,4 @@ -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; import TabPresenter from '../presenters/TabPresenter'; import ConsoleClient from '../infrastructures/ConsoleClient'; @@ -6,7 +6,7 @@ import ConsoleClient from '../infrastructures/ConsoleClient'; export default class ConsoleUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, private consoleClient: ConsoleClient, ) { } diff --git a/src/background/usecases/FindUseCase.ts b/src/background/usecases/FindUseCase.ts index b8593c6..cb41cd5 100644 --- a/src/background/usecases/FindUseCase.ts +++ b/src/background/usecases/FindUseCase.ts @@ -1,4 +1,4 @@ -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; import FindRepository from '../repositories/FindRepository'; import TabPresenter from '../presenters/TabPresenter'; import ConsoleClient from '../infrastructures/ConsoleClient'; @@ -6,7 +6,7 @@ import ConsoleClient from '../infrastructures/ConsoleClient'; @injectable() export default class FindUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, private findRepository: FindRepository, private consoleClient: ConsoleClient, ) { diff --git a/src/background/usecases/LinkUseCase.ts b/src/background/usecases/LinkUseCase.ts index 9c0eab5..be076c7 100644 --- a/src/background/usecases/LinkUseCase.ts +++ b/src/background/usecases/LinkUseCase.ts @@ -1,10 +1,10 @@ -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; import TabPresenter from '../presenters/TabPresenter'; @injectable() export default class LinkUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, ) { } diff --git a/src/background/usecases/MarkUseCase.ts b/src/background/usecases/MarkUseCase.ts index eeac40f..2c0bc13 100644 --- a/src/background/usecases/MarkUseCase.ts +++ b/src/background/usecases/MarkUseCase.ts @@ -1,4 +1,4 @@ -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; import TabPresenter from '../presenters/TabPresenter'; import MarkRepository from '../repositories/MarkRepository'; import ConsoleClient from '../infrastructures/ConsoleClient'; @@ -7,7 +7,7 @@ import ContentMessageClient from '../infrastructures/ContentMessageClient'; @injectable() export default class MarkUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, private markRepository: MarkRepository, private consoleClient: ConsoleClient, private contentMessageClient: ContentMessageClient, diff --git a/src/background/usecases/NavigateUseCase.ts b/src/background/usecases/NavigateUseCase.ts index 25e7f20..3aa1ed6 100644 --- a/src/background/usecases/NavigateUseCase.ts +++ b/src/background/usecases/NavigateUseCase.ts @@ -1,11 +1,11 @@ -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; import NavigateClient from '../clients/NavigateClient'; import TabPresenter from '../presenters/TabPresenter'; @injectable() export default class NavigateUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, private navigateClient: NavigateClient, ) { } diff --git a/src/background/usecases/TabSelectUseCase.ts b/src/background/usecases/TabSelectUseCase.ts index 62098de..271bb6c 100644 --- a/src/background/usecases/TabSelectUseCase.ts +++ b/src/background/usecases/TabSelectUseCase.ts @@ -1,10 +1,10 @@ -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; import TabPresenter from '../presenters/TabPresenter'; @injectable() export default class TabSelectUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, ) { } diff --git a/src/background/usecases/TabUseCase.ts b/src/background/usecases/TabUseCase.ts index 66f8573..418dde5 100644 --- a/src/background/usecases/TabUseCase.ts +++ b/src/background/usecases/TabUseCase.ts @@ -1,4 +1,4 @@ -import { injectable } from 'tsyringe'; +import {inject, injectable} from 'tsyringe'; import TabPresenter from '../presenters/TabPresenter'; import WindowPresenter from '../presenters/WindowPresenter'; import BrowserSettingRepository from '../repositories/BrowserSettingRepository'; @@ -6,7 +6,7 @@ import BrowserSettingRepository from '../repositories/BrowserSettingRepository'; @injectable() export default class TabUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, private windowPresenter: WindowPresenter, private browserSettingRepository: BrowserSettingRepository, ) { diff --git a/src/background/usecases/VersionUseCase.ts b/src/background/usecases/VersionUseCase.ts index 9ea8af9..21a5e2c 100644 --- a/src/background/usecases/VersionUseCase.ts +++ b/src/background/usecases/VersionUseCase.ts @@ -5,7 +5,7 @@ import Notifier from '../presenters/Notifier'; @injectable() export default class VersionUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, @inject("Notifier") private notifier: Notifier, ) { } diff --git a/src/background/usecases/ZoomUseCase.ts b/src/background/usecases/ZoomUseCase.ts index f598871..ca1368d 100644 --- a/src/background/usecases/ZoomUseCase.ts +++ b/src/background/usecases/ZoomUseCase.ts @@ -1,4 +1,4 @@ -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; import TabPresenter from '../presenters/TabPresenter'; const ZOOM_SETTINGS: number[] = [ @@ -9,7 +9,7 @@ const ZOOM_SETTINGS: number[] = [ @injectable() export default class ZoomUseCase { constructor( - private tabPresenter: TabPresenter, + @inject('TabPresenter') private tabPresenter: TabPresenter, ) { } diff --git a/src/console/actions/console.ts b/src/console/actions/console.ts index f1db941..603a98d 100644 --- a/src/console/actions/console.ts +++ b/src/console/actions/console.ts @@ -1,9 +1,10 @@ import * as messages from '../../shared/messages'; import * as actions from './index'; -import { Command } from "../../shared/Command"; +import {Command} from "../../shared/Command"; import CompletionClient from "../clients/CompletionClient"; import CompletionType from "../../shared/CompletionType"; import Completions from "../Completions"; +import TabFlag from "../../shared/TabFlag"; const completionClient = new CompletionClient(); @@ -155,6 +156,28 @@ const getOpenCompletions = async( }; }; +const getTabCompletions = async ( + original: string, command: Command, query: string, excludePinned: boolean, +): Promise => { + const items = await completionClient.requestTabs(query, excludePinned); + const completions = [{ + name: 'Buffers', + items: items.map(item => ({ + content: command + ' ' + item.url, + caption: `${item.index}: ${item.flag != TabFlag.None ? item.flag : ' ' } ${item.title}`, + url: item.url, + icon: item.faviconUrl, + })), + }]; + return { + type: actions.CONSOLE_SET_COMPLETIONS, + completions, + completionSource: original, + } +}; + + + const getCompletions = async(text: string): Promise => { const completions = await browser.runtime.sendMessage({ type: messages.CONSOLE_QUERY_COMPLETIONS, @@ -181,6 +204,6 @@ const completionPrev = (): actions.CompletionPrevAction => { export { hide, showCommand, showFind, showError, showInfo, hideCommand, setConsoleText, enterCommand, enterFind, - getCompletions, getCommandCompletions, getOpenCompletions, + getCompletions, getCommandCompletions, getOpenCompletions, getTabCompletions, completionNext, completionPrev, }; diff --git a/src/console/clients/CompletionClient.ts b/src/console/clients/CompletionClient.ts index d5f9b01..19b4d99 100644 --- a/src/console/clients/CompletionClient.ts +++ b/src/console/clients/CompletionClient.ts @@ -2,9 +2,10 @@ import * as messages from "../../shared/messages"; import { ConsoleGetCompletionTypesResponse, ConsoleRequestBookmarksResponse, - ConsoleRequestHistoryResponse, ConsoleRequestSearchEnginesResponse + ConsoleRequestHistoryResponse, ConsoleRequestSearchEnginesResponse, ConsoleRequesttabsResponse } from "../../shared/messages"; import CompletionType from "../../shared/CompletionType"; +import TabFlag from "../../shared/TabFlag"; export type SearchEngines = { title: string @@ -20,6 +21,14 @@ export type HistoryItem = { url: string } +export type TabItem = { + index: number + flag: TabFlag + title: string + url: string + faviconUrl?: string +} + export default class CompletionClient { async getCompletionTypes(): Promise { const resp = await browser.runtime.sendMessage({ @@ -51,4 +60,13 @@ export default class CompletionClient { }) as ConsoleRequestHistoryResponse; return resp; } + + async requestTabs(query: string, excludePinned: boolean): Promise { + const resp = await browser.runtime.sendMessage({ + type: messages.CONSOLE_REQUEST_TABS, + query, + excludePinned, + }) as ConsoleRequesttabsResponse; + return resp; + } } diff --git a/src/console/components/Console.tsx b/src/console/components/Console.tsx index 7be073e..c1709cd 100644 --- a/src/console/components/Console.tsx +++ b/src/console/components/Console.tsx @@ -171,6 +171,21 @@ class Console extends React.Component { case Command.WindowOpen: this.props.dispatch(consoleActions.getOpenCompletions(this.props.completionTypes, text, cmd.command, cmd.args)); break; + case Command.Buffer: + this.props.dispatch(consoleActions.getTabCompletions(text, cmd.command, cmd.args, false)); + break; + case Command.BufferDelete: + this.props.dispatch(consoleActions.getTabCompletions(text, cmd.command, cmd.args, true)); + break; + case Command.BufferDeleteForce: + this.props.dispatch(consoleActions.getTabCompletions(text, cmd.command, cmd.args, false)); + break; + case Command.BuffersDelete: + this.props.dispatch(consoleActions.getTabCompletions(text, cmd.command, cmd.args, true)); + break; + case Command.BuffersDeleteForce: + this.props.dispatch(consoleActions.getTabCompletions(text, cmd.command, cmd.args, false)); + break; default: this.props.dispatch(consoleActions.getCompletions(text)); } diff --git a/src/shared/TabFlag.ts b/src/shared/TabFlag.ts new file mode 100644 index 0000000..b10d5c6 --- /dev/null +++ b/src/shared/TabFlag.ts @@ -0,0 +1,7 @@ +enum TabFlag { + CurrentTab = '%', + LastTab = '#', + None = '', +} + +export default TabFlag \ No newline at end of file diff --git a/src/shared/messages.ts b/src/shared/messages.ts index be6a2e1..3f6b7bf 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -1,5 +1,6 @@ import * as operations from './operations'; import CompletionType from "./CompletionType"; +import TabFlag from "./TabFlag"; export const BACKGROUND_OPERATION = 'background.operation'; @@ -16,6 +17,7 @@ export const CONSOLE_GET_COMPLETION_TYPES = 'console.get.completion.types' export const CONSOLE_REQUEST_SEARCH_ENGINES_MESSAGE = 'console.qresut.searchEngines'; export const CONSOLE_REQUEST_BOOKMARKS = 'console.request.bookmarks'; export const CONSOLE_REQUEST_HISTORY = 'console.request.history'; +export const CONSOLE_REQUEST_TABS = 'console.request.tabs'; export const FOLLOW_START = 'follow.start'; export const FOLLOW_REQUEST_COUNT_TARGETS = 'follow.request.count.targets'; @@ -119,6 +121,20 @@ export interface ConsoleRequestHistoryMessage { query: string; } +export interface ConsoleRequestTabsMessage { + type: typeof CONSOLE_REQUEST_TABS; + query: string; + excludePinned: boolean; +} + +export type ConsoleRequesttabsResponse = { + index: number + flag: TabFlag + title: string + url: string + faviconUrl?: string +}[] + export type ConsoleGetCompletionTypesResponse = CompletionType[]; export type ConsoleRequestSearchEnginesResponse = { @@ -279,6 +295,7 @@ export type Message = ConsoleHideMessage | ConsoleRequestBookmarksMessage | ConsoleRequestHistoryMessage | + ConsoleRequestTabsMessage | ConsoleGetCompletionTypesMessage | ConsoleRequestSearchEnginesMessage | FollowStartMessage | diff --git a/test/background/completion/CompletionUseCase.test.ts b/test/background/completion/CompletionUseCase.test.ts deleted file mode 100644 index 0d58e45..0000000 --- a/test/background/completion/CompletionUseCase.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import "reflect-metadata"; -import CompletionType from "../../../src/shared/CompletionType"; -import BookmarkRepository, {BookmarkItem} from "../../../src/background/completion/BookmarkRepository"; -import HistoryRepository, {HistoryItem} from "../../../src/background/completion/HistoryRepository"; -import CompletionUseCase from "../../../src/background/completion/CompletionUseCase"; -import CachedSettingRepository from "../../../src/background/repositories/CachedSettingRepository"; -import Settings, {DefaultSetting} from "../../../src/shared/settings/Settings"; -import { expect } from 'chai'; -import sinon from 'sinon'; -import Properties from "../../../src/shared/settings/Properties"; -import Search from "../../../src/shared/settings/Search"; - -class MockBookmarkRepository implements BookmarkRepository { - queryBookmarks(_query: string): Promise { - throw new Error("not implemented") - } -} - -class MockHistoryRepository implements HistoryRepository { - queryHistories(_keywords: string): Promise { - throw new Error("not implemented") - } -} - -class MockSettingRepository implements CachedSettingRepository { - get(): Promise { - throw new Error("not implemented") - } - - setProperty(_name: string, _value: string | number | boolean): Promise { - throw new Error("not implemented") - } - - update(_value: Settings): Promise { - throw new Error("not implemented") - } -} - -describe('CompletionUseCase', () => { - let bookmarkRepository: MockBookmarkRepository; - let historyRepository: MockHistoryRepository; - let settingRepository: MockSettingRepository; - let sut: CompletionUseCase; - - beforeEach(() => { - bookmarkRepository = new MockBookmarkRepository(); - historyRepository = new MockHistoryRepository(); - settingRepository = new MockSettingRepository(); - sut = new CompletionUseCase(bookmarkRepository, historyRepository, settingRepository) - }); - - describe('#getCompletionTypes', () => { - it("returns completion types from the property", async () => { - sinon.stub(settingRepository, 'get').returns(Promise.resolve(new Settings({ - keymaps: DefaultSetting.keymaps, - search: DefaultSetting.search, - properties: new Properties({ complete: "shb" }), - blacklist: DefaultSetting.blacklist, - }))); - - const items = await sut.getCompletionTypes(); - expect(items).to.deep.equal([ - CompletionType.SearchEngines, - CompletionType.History, - CompletionType.Bookmarks - ]); - }); - }); - - describe('#requestSearchEngines', () => { - it("returns search engines matches by the query", async () => { - sinon.stub(settingRepository, 'get').returns(Promise.resolve(new Settings({ - keymaps: DefaultSetting.keymaps, - search: new Search("google", { - "google": "https://google.com/search?q={}", - "yahoo": "https://search.yahoo.com/search?q={}", - "bing": "https://bing.com/search?q={}", - "google_ja": "https://google.co.jp/search?q={}", - }), - properties: DefaultSetting.properties, - blacklist: DefaultSetting.blacklist, - }))); - - expect(await sut.requestSearchEngines("")).to.deep.equal([ - "google", - "yahoo", - "bing", - "google_ja", - ]); - expect(await sut.requestSearchEngines("go")).to.deep.equal([ - "google", - "google_ja", - ]); - expect(await sut.requestSearchEngines("x")).to.be.empty; - }) - }); - - describe('#requestBookmarks', () => { - it("returns bookmarks from the repository", async() => { - sinon.stub(bookmarkRepository, 'queryBookmarks') - .withArgs("site").returns(Promise.resolve([ - { title: "site1", url: "https://site1.example.com" }, - { title: "site2", url: "https://site2.example.com/" }, - ])) - .withArgs("xyz").returns(Promise.resolve([])); - - expect(await sut.requestBookmarks("site")).to.deep.equal([ - { title: "site1", url: "https://site1.example.com" }, - { title: "site2", url: "https://site2.example.com/" }, - ]); - expect(await sut.requestBookmarks("xyz")).to.be.empty; - }); - }); - - describe('#requestHistory', () => { - it("returns histories from the repository", async() => { - sinon.stub(historyRepository, 'queryHistories') - .withArgs("site").returns(Promise.resolve([ - { title: "site1", url: "https://site1.example.com" }, - { title: "site2", url: "https://site2.example.com/" }, - ])) - .withArgs("xyz").returns(Promise.resolve([])); - - expect(await sut.requestHistory("site")).to.deep.equal([ - { title: "site1", url: "https://site1.example.com" }, - { title: "site2", url: "https://site2.example.com/" }, - ]); - expect(await sut.requestHistory("xyz")).to.be.empty; - }); - }); -}); \ No newline at end of file diff --git a/test/background/completion/OpenCompletionUseCase.test.ts b/test/background/completion/OpenCompletionUseCase.test.ts new file mode 100644 index 0000000..421ce69 --- /dev/null +++ b/test/background/completion/OpenCompletionUseCase.test.ts @@ -0,0 +1,131 @@ +import "reflect-metadata"; +import CompletionType from "../../../src/shared/CompletionType"; +import BookmarkRepository, {BookmarkItem} from "../../../src/background/completion/BookmarkRepository"; +import HistoryRepository, {HistoryItem} from "../../../src/background/completion/HistoryRepository"; +import OpenCompletionUseCase from "../../../src/background/completion/OpenCompletionUseCase"; +import CachedSettingRepository from "../../../src/background/repositories/CachedSettingRepository"; +import Settings, {DefaultSetting} from "../../../src/shared/settings/Settings"; +import { expect } from 'chai'; +import sinon from 'sinon'; +import Properties from "../../../src/shared/settings/Properties"; +import Search from "../../../src/shared/settings/Search"; + +class MockBookmarkRepository implements BookmarkRepository { + queryBookmarks(_query: string): Promise { + throw new Error("not implemented") + } +} + +class MockHistoryRepository implements HistoryRepository { + queryHistories(_keywords: string): Promise { + throw new Error("not implemented") + } +} + +class MockSettingRepository implements CachedSettingRepository { + get(): Promise { + throw new Error("not implemented") + } + + setProperty(_name: string, _value: string | number | boolean): Promise { + throw new Error("not implemented") + } + + update(_value: Settings): Promise { + throw new Error("not implemented") + } +} + +describe('OpenCompletionUseCase', () => { + let bookmarkRepository: MockBookmarkRepository; + let historyRepository: MockHistoryRepository; + let settingRepository: MockSettingRepository; + let sut: OpenCompletionUseCase; + + beforeEach(() => { + bookmarkRepository = new MockBookmarkRepository(); + historyRepository = new MockHistoryRepository(); + settingRepository = new MockSettingRepository(); + sut = new OpenCompletionUseCase(bookmarkRepository, historyRepository, settingRepository) + }); + + describe('#getCompletionTypes', () => { + it("returns completion types from the property", async () => { + sinon.stub(settingRepository, 'get').returns(Promise.resolve(new Settings({ + keymaps: DefaultSetting.keymaps, + search: DefaultSetting.search, + properties: new Properties({ complete: "shb" }), + blacklist: DefaultSetting.blacklist, + }))); + + const items = await sut.getCompletionTypes(); + expect(items).to.deep.equal([ + CompletionType.SearchEngines, + CompletionType.History, + CompletionType.Bookmarks + ]); + }); + }); + + describe('#requestSearchEngines', () => { + it("returns search engines matches by the query", async () => { + sinon.stub(settingRepository, 'get').returns(Promise.resolve(new Settings({ + keymaps: DefaultSetting.keymaps, + search: new Search("google", { + "google": "https://google.com/search?q={}", + "yahoo": "https://search.yahoo.com/search?q={}", + "bing": "https://bing.com/search?q={}", + "google_ja": "https://google.co.jp/search?q={}", + }), + properties: DefaultSetting.properties, + blacklist: DefaultSetting.blacklist, + }))); + + expect(await sut.requestSearchEngines("")).to.deep.equal([ + "google", + "yahoo", + "bing", + "google_ja", + ]); + expect(await sut.requestSearchEngines("go")).to.deep.equal([ + "google", + "google_ja", + ]); + expect(await sut.requestSearchEngines("x")).to.be.empty; + }) + }); + + describe('#requestBookmarks', () => { + it("returns bookmarks from the repository", async() => { + sinon.stub(bookmarkRepository, 'queryBookmarks') + .withArgs("site").returns(Promise.resolve([ + { title: "site1", url: "https://site1.example.com" }, + { title: "site2", url: "https://site2.example.com/" }, + ])) + .withArgs("xyz").returns(Promise.resolve([])); + + expect(await sut.requestBookmarks("site")).to.deep.equal([ + { title: "site1", url: "https://site1.example.com" }, + { title: "site2", url: "https://site2.example.com/" }, + ]); + expect(await sut.requestBookmarks("xyz")).to.be.empty; + }); + }); + + describe('#requestHistory', () => { + it("returns histories from the repository", async() => { + sinon.stub(historyRepository, 'queryHistories') + .withArgs("site").returns(Promise.resolve([ + { title: "site1", url: "https://site1.example.com" }, + { title: "site2", url: "https://site2.example.com/" }, + ])) + .withArgs("xyz").returns(Promise.resolve([])); + + expect(await sut.requestHistory("site")).to.deep.equal([ + { title: "site1", url: "https://site1.example.com" }, + { title: "site2", url: "https://site2.example.com/" }, + ]); + expect(await sut.requestHistory("xyz")).to.be.empty; + }); + }); +}); \ No newline at end of file diff --git a/test/background/completion/TabCompletionUseCase.test.ts b/test/background/completion/TabCompletionUseCase.test.ts new file mode 100644 index 0000000..e2c7c19 --- /dev/null +++ b/test/background/completion/TabCompletionUseCase.test.ts @@ -0,0 +1,105 @@ +import "reflect-metadata" +import TabRepositoryImpl from "../../../src/background/completion/impl/TabRepositoryImpl"; +import {Tab} from "../../../src/background/completion/TabRepository"; +import TabPresenter from "../../../src/background/presenters/TabPresenter"; +import TabCompletionUseCase from "../../../src/background/completion/TabCompletionUseCase"; +import sinon from 'sinon'; +import { expect } from 'chai'; +import TabFlag from "../../../src/shared/TabFlag"; + +class MockTabRepository implements TabRepositoryImpl { + async queryTabs(_query: string, _excludePinned: boolean): Promise { + throw new Error("not implemented") + } +} + +class MockTabPresenter implements TabPresenter { + create(_url: string, _opts?: object): Promise { + throw new Error("not implemented") + } + + duplicate(_id: number): Promise { + throw new Error("not implemented") + } + + getAll(): Promise { + throw new Error("not implemented") + } + + getByKeyword(_keyword: string, _excludePinned: boolean): Promise { + throw new Error("not implemented") + } + + getCurrent(): Promise { + throw new Error("not implemented") + } + + getLastSelectedId(): Promise { + throw new Error("not implemented") + } + + getZoom(_tabId: number): Promise { + throw new Error("not implemented") + } + + onSelected(_listener: (arg: { tabId: number; windowId: number }) => void): void { + throw new Error("not implemented") + } + + open(_url: string, _tabId?: number): Promise { + throw new Error("not implemented") + } + + reload(_tabId: number, _cache: boolean): Promise { + throw new Error("not implemented") + } + + remove(_ids: number[]): Promise { + throw new Error("not implemented") + } + + reopen(): Promise { + throw new Error("not implemented") + } + + select(_tabId: number): Promise { + throw new Error("not implemented") + } + + setPinned(_tabId: number, _pinned: boolean): Promise { + throw new Error("not implemented") + } + + setZoom(_tabId: number, _factor: number): Promise { + throw new Error("not implemented") + } +} + +describe('TabCompletionUseCase', () => { + let tabRepository: MockTabRepository; + let tabPresenter: TabPresenter; + let sut: TabCompletionUseCase; + + beforeEach(() => { + tabRepository = new MockTabRepository(); + tabPresenter = new MockTabPresenter(); + sut = new TabCompletionUseCase(tabRepository, tabPresenter) + }); + + describe('#queryTabs', () => { + it("returns tab items", async () => { + sinon.stub(tabRepository, 'queryTabs').returns(Promise.resolve([ + { id: 10, index: 0, title: 'Google', url: 'https://google.com/', faviconUrl: 'https://google.com/favicon.ico', active: true }, + { id: 11, index: 1, title: 'Yahoo', url: 'https://yahoo.com/', faviconUrl: 'https://yahoo.com/favicon.ico', active: false }, + { id: 12, index: 2, title: 'Bing', url: 'https://bing.com/', active: false }, + ])); + sinon.stub(tabPresenter, 'getLastSelectedId').returns(Promise.resolve(11)); + + expect(await sut.queryTabs("", false)).to.deep.equal([ + { index: 0, title: 'Google', url: 'https://google.com/', faviconUrl: 'https://google.com/favicon.ico', flag: TabFlag.CurrentTab }, + { index: 1, title: 'Yahoo', url: 'https://yahoo.com/', faviconUrl: 'https://yahoo.com/favicon.ico', flag: TabFlag.LastTab }, + { index: 2, title: 'Bing', url: 'https://bing.com/', faviconUrl: undefined, flag: TabFlag.None }, + ]); + }) + }); +}); \ No newline at end of file -- cgit v1.2.3 From c3be3dde555d1f51f696f4bfbe181e7bad6d3563 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 29 Mar 2020 21:47:09 +0900 Subject: Complete properties on set command --- .../completion/PropertyCompletionUseCase.ts | 16 ++++++++ src/background/controllers/CompletionController.ts | 7 ++++ .../infrastructures/ContentMessageListener.ts | 2 + src/console/actions/console.ts | 43 +++++++++++++++++++++- src/console/clients/CompletionClient.ts | 14 ++++++- src/console/components/Console.tsx | 3 ++ src/shared/messages.ts | 13 ++++++- .../completion/PropertyCompletionUseCase.test.ts | 15 ++++++++ 8 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 src/background/completion/PropertyCompletionUseCase.ts create mode 100644 test/background/completion/PropertyCompletionUseCase.test.ts (limited to 'src/console/clients') diff --git a/src/background/completion/PropertyCompletionUseCase.ts b/src/background/completion/PropertyCompletionUseCase.ts new file mode 100644 index 0000000..049cfb8 --- /dev/null +++ b/src/background/completion/PropertyCompletionUseCase.ts @@ -0,0 +1,16 @@ +import { injectable } from "tsyringe"; +import Properties from "../../shared/settings/Properties"; + +type Property = { + name: string; + type: 'string' | 'boolean' | 'number'; +} +@injectable() +export default class PropertyCompletionUseCase { + async getProperties(): Promise { + return Properties.defs().map(def => ({ + name: def.name, + type: def.type, + })); + } +} \ No newline at end of file diff --git a/src/background/controllers/CompletionController.ts b/src/background/controllers/CompletionController.ts index a268d15..fb6137c 100644 --- a/src/background/controllers/CompletionController.ts +++ b/src/background/controllers/CompletionController.ts @@ -1,5 +1,6 @@ import { ConsoleGetCompletionTypesResponse, + ConsoleGetPropertiesResponse, ConsoleRequestBookmarksResponse, ConsoleRequestHistoryResponse, ConsoleRequestSearchEnginesResponse, @@ -8,12 +9,14 @@ import { import { injectable } from "tsyringe"; import OpenCompletionUseCase from "../completion/OpenCompletionUseCase"; import TabCompletionUseCase from "../completion/TabCompletionUseCase"; +import PropertyCompletionUseCase from "../completion/PropertyCompletionUseCase"; @injectable() export default class CompletionController { constructor( private completionUseCase: OpenCompletionUseCase, private tabCompletionUseCase: TabCompletionUseCase, + private propertyCompletionUseCase: PropertyCompletionUseCase, ) { } @@ -37,4 +40,8 @@ export default class CompletionController { async queryTabs(query: string, excludePinned: boolean): Promise { return this.tabCompletionUseCase.queryTabs(query, excludePinned); } + + async getProperties(): Promise { + return this.propertyCompletionUseCase.getProperties(); + } } \ No newline at end of file diff --git a/src/background/infrastructures/ContentMessageListener.ts b/src/background/infrastructures/ContentMessageListener.ts index 13aa763..92481da 100644 --- a/src/background/infrastructures/ContentMessageListener.ts +++ b/src/background/infrastructures/ContentMessageListener.ts @@ -75,6 +75,8 @@ export default class ContentMessageListener { return this.completionController.requestHistory(message.query); case messages.CONSOLE_REQUEST_TABS: return this.completionController.queryTabs(message.query, message.excludePinned); + case messages.CONSOLE_GET_PROPERTIES: + return this.completionController.getProperties(); case messages.CONSOLE_ENTER_COMMAND: return this.onConsoleEnterCommand(message.text); case messages.SETTINGS_QUERY: diff --git a/src/console/actions/console.ts b/src/console/actions/console.ts index 89d9c3c..b17754d 100644 --- a/src/console/actions/console.ts +++ b/src/console/actions/console.ts @@ -22,6 +22,12 @@ const commandDocs = { [Command.Help]: 'Open Vim Vixen help in new tab', }; +const propertyDocs: {[key: string]: string} = { + 'hintchars': 'hint characters on follow mode', + 'smoothscroll': 'smooth scroll', + 'complete': 'which are completed at the open page', +}; + const hide = (): actions.ConsoleAction => { return { type: actions.CONSOLE_HIDE, @@ -191,7 +197,40 @@ const getTabCompletions = async ( } }; - +const getPropertyCompletions = async( + original: string, command: Command, query: string, +): Promise => { + const properties = await completionClient.getProperties(); + const items = properties + .map(item => { + const desc = propertyDocs[item.name] || ''; + if (item.type === 'boolean') { + return [{ + caption: item.name, + content: command + ' ' + item.name, + url: 'Enable ' + desc, + }, { + caption: 'no' + item.name, + content: command + ' no' + item.name, + url: 'Disable ' + desc, + }]; + } else { + return [{ + caption: item.name, + content: name + ' ' + item.name, + url: 'Set ' + desc, + }]; + } + }) + .reduce((acc, val) => acc.concat(val), []) + .filter(item => item.caption.startsWith(query)); + const completions: Completions = [{ name: 'Properties', items }]; + return { + type: actions.CONSOLE_SET_COMPLETIONS, + completions, + completionSource: original, + } +}; const getCompletions = async(text: string): Promise => { const completions = await browser.runtime.sendMessage({ @@ -219,6 +258,6 @@ const completionPrev = (): actions.CompletionPrevAction => { export { hide, showCommand, showFind, showError, showInfo, hideCommand, setConsoleText, enterCommand, enterFind, - getCompletions, getCommandCompletions, getOpenCompletions, getTabCompletions, + getCompletions, getCommandCompletions, getOpenCompletions, getTabCompletions, getPropertyCompletions, completionNext, completionPrev, }; diff --git a/src/console/clients/CompletionClient.ts b/src/console/clients/CompletionClient.ts index 19b4d99..56dc665 100644 --- a/src/console/clients/CompletionClient.ts +++ b/src/console/clients/CompletionClient.ts @@ -1,6 +1,6 @@ import * as messages from "../../shared/messages"; import { - ConsoleGetCompletionTypesResponse, + ConsoleGetCompletionTypesResponse, ConsoleGetPropertiesResponse, ConsoleRequestBookmarksResponse, ConsoleRequestHistoryResponse, ConsoleRequestSearchEnginesResponse, ConsoleRequesttabsResponse } from "../../shared/messages"; @@ -29,6 +29,11 @@ export type TabItem = { faviconUrl?: string } +export type Property = { + name: string + type: 'string' | 'boolean' | 'number'; +} + export default class CompletionClient { async getCompletionTypes(): Promise { const resp = await browser.runtime.sendMessage({ @@ -69,4 +74,11 @@ export default class CompletionClient { }) as ConsoleRequesttabsResponse; return resp; } + + async getProperties(): Promise { + const resp = await browser.runtime.sendMessage({ + type: messages.CONSOLE_GET_PROPERTIES, + }) as ConsoleGetPropertiesResponse; + return resp; + } } diff --git a/src/console/components/Console.tsx b/src/console/components/Console.tsx index c1709cd..77f1b09 100644 --- a/src/console/components/Console.tsx +++ b/src/console/components/Console.tsx @@ -186,6 +186,9 @@ class Console extends React.Component { case Command.BuffersDeleteForce: this.props.dispatch(consoleActions.getTabCompletions(text, cmd.command, cmd.args, false)); break; + case Command.Set: + this.props.dispatch(consoleActions.getPropertyCompletions(text, cmd.command, cmd.args)); + break; default: this.props.dispatch(consoleActions.getCompletions(text)); } diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 3f6b7bf..577c34c 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -13,11 +13,12 @@ export const CONSOLE_SHOW_ERROR = 'console.show.error'; export const CONSOLE_SHOW_INFO = 'console.show.info'; export const CONSOLE_SHOW_FIND = 'console.show.find'; export const CONSOLE_HIDE = 'console.hide'; -export const CONSOLE_GET_COMPLETION_TYPES = 'console.get.completion.types' +export const CONSOLE_GET_COMPLETION_TYPES = 'console.get.completion.types'; export const CONSOLE_REQUEST_SEARCH_ENGINES_MESSAGE = 'console.qresut.searchEngines'; export const CONSOLE_REQUEST_BOOKMARKS = 'console.request.bookmarks'; export const CONSOLE_REQUEST_HISTORY = 'console.request.history'; export const CONSOLE_REQUEST_TABS = 'console.request.tabs'; +export const CONSOLE_GET_PROPERTIES = 'console.get.properties'; export const FOLLOW_START = 'follow.start'; export const FOLLOW_REQUEST_COUNT_TARGETS = 'follow.request.count.targets'; @@ -127,6 +128,10 @@ export interface ConsoleRequestTabsMessage { excludePinned: boolean; } +export interface ConsoleGetPropertiesMessage { + type: typeof CONSOLE_GET_PROPERTIES; +} + export type ConsoleRequesttabsResponse = { index: number flag: TabFlag @@ -151,6 +156,11 @@ export type ConsoleRequestHistoryResponse = { url: string; }[] +export type ConsoleGetPropertiesResponse = { + name: string + type: 'string' | 'boolean' | 'number' +}[] + export interface FollowStartMessage { type: typeof FOLLOW_START; newTab: boolean; @@ -296,6 +306,7 @@ export type Message = ConsoleRequestBookmarksMessage | ConsoleRequestHistoryMessage | ConsoleRequestTabsMessage | + ConsoleGetPropertiesMessage | ConsoleGetCompletionTypesMessage | ConsoleRequestSearchEnginesMessage | FollowStartMessage | diff --git a/test/background/completion/PropertyCompletionUseCase.test.ts b/test/background/completion/PropertyCompletionUseCase.test.ts new file mode 100644 index 0000000..57f5bff --- /dev/null +++ b/test/background/completion/PropertyCompletionUseCase.test.ts @@ -0,0 +1,15 @@ +import 'reflect-metadata'; +import PropertyCompletionUseCase from "../../../src/background/completion/PropertyCompletionUseCase"; +import { expect } from 'chai'; + +describe('PropertyCompletionUseCase', () => { + describe('getProperties', () => { + it('returns property types', async () => { + const sut = new PropertyCompletionUseCase(); + + const properties = await sut.getProperties(); + expect(properties).to.deep.contain({ name: 'smoothscroll', type: 'boolean' }); + expect(properties).to.deep.contain({ name: 'complete', type: 'string' }); + }) + }); +}); \ No newline at end of file -- cgit v1.2.3