diff options
Diffstat (limited to 'src')
31 files changed, 610 insertions, 902 deletions
diff --git a/src/background/Application.ts b/src/background/Application.ts index 69fe4a4..b439d19 100644 --- a/src/background/Application.ts +++ b/src/background/Application.ts @@ -1,8 +1,11 @@ import { injectable, inject } from "tsyringe"; import ContentMessageListener from "./infrastructures/ContentMessageListener"; +import FindPortListener from "./infrastructures/FindPortListener"; import SettingController from "./controllers/SettingController"; import VersionController from "./controllers/VersionController"; import SettingRepository from "./repositories/SettingRepository"; +import FindRepositoryImpl from "./repositories/FindRepository"; +import ReadyFrameRepository from "./repositories/ReadyFrameRepository"; @injectable() export default class Application { @@ -11,12 +14,26 @@ export default class Application { private settingController: SettingController, private versionController: VersionController, @inject("SyncSettingRepository") - private syncSettingRepository: SettingRepository + private syncSettingRepository: SettingRepository, + @inject("FindRepository") + private readonly findRepository: FindRepositoryImpl, + @inject("ReadyFrameRepository") + private readonly frameRepository: ReadyFrameRepository ) {} + private readonly findPortListener = new FindPortListener( + this.onFindPortConnect.bind(this), + this.onFindPortDisconnect.bind(this) + ); + run() { this.settingController.reload(); + browser.tabs.onUpdated.addListener((tabId: number, info) => { + if (info.status == "loading") { + this.findRepository.deleteLocalState(tabId); + } + }); browser.runtime.onInstalled.addListener((details) => { if (details.reason !== "install" && details.reason !== "update") { return; @@ -28,5 +45,26 @@ export default class Application { this.syncSettingRepository.onChange(() => { this.settingController.reload(); }); + this.findPortListener.run(); + } + + private onFindPortConnect(port: browser.runtime.Port) { + const tabId = port.sender?.tab?.id; + const frameId = port.sender?.frameId; + if (typeof tabId === "undefined" || typeof frameId === "undefined") { + return; + } + + this.frameRepository.addFrameId(tabId, frameId); + } + + private onFindPortDisconnect(port: browser.runtime.Port) { + const tabId = port.sender?.tab?.id; + const frameId = port.sender?.frameId; + if (typeof tabId === "undefined" || typeof frameId === "undefined") { + return; + } + + this.frameRepository.removeFrameId(tabId, frameId); } } diff --git a/src/background/clients/FindClient.ts b/src/background/clients/FindClient.ts new file mode 100644 index 0000000..b46b964 --- /dev/null +++ b/src/background/clients/FindClient.ts @@ -0,0 +1,45 @@ +import * as messages from "../../shared/messages"; + +export default interface FindClient { + findNext(tabId: number, frameId: number, keyword: string): Promise<boolean>; + + findPrev(tabId: number, frameId: number, keyword: string): Promise<boolean>; + + clearSelection(tabId: number, frameId: number): Promise<void>; +} + +export class FindClientImpl implements FindClient { + async findNext( + tabId: number, + frameId: number, + keyword: string + ): Promise<boolean> { + const found = (await browser.tabs.sendMessage( + tabId, + { type: messages.FIND_NEXT, keyword }, + { frameId } + )) as boolean; + return found; + } + + async findPrev( + tabId: number, + frameId: number, + keyword: string + ): Promise<boolean> { + const found = (await browser.tabs.sendMessage( + tabId, + { type: messages.FIND_PREV, keyword }, + { frameId } + )) as boolean; + return found; + } + + clearSelection(tabId: number, frameId: number): Promise<void> { + return browser.tabs.sendMessage( + tabId, + { type: messages.FIND_CLEAR_SELECTION }, + { frameId } + ); + } +} diff --git a/src/background/controllers/FindController.ts b/src/background/controllers/FindController.ts index f3ea93f..0772866 100644 --- a/src/background/controllers/FindController.ts +++ b/src/background/controllers/FindController.ts @@ -1,15 +1,11 @@ import { injectable } from "tsyringe"; -import FindUseCase from "../usecases/FindUseCase"; +import StartFindUseCase from "../usecases/StartFindUseCase"; @injectable() export default class FindController { - constructor(private findUseCase: FindUseCase) {} + constructor(private startFindUseCase: StartFindUseCase) {} - getKeyword(): Promise<string> { - return this.findUseCase.getKeyword(); - } - - setKeyword(keyword: string): Promise<void> { - return this.findUseCase.setKeyword(keyword); + startFind(tabId: number, keyword?: string): Promise<void> { + return this.startFindUseCase.startFind(tabId, keyword); } } diff --git a/src/background/di.ts b/src/background/di.ts index a571c7d..495de7c 100644 --- a/src/background/di.ts +++ b/src/background/di.ts @@ -18,7 +18,10 @@ import { BrowserSettingRepositoryImpl } from "./repositories/BrowserSettingRepos import { RepeatRepositoryImpl } from "./repositories/RepeatRepository"; import { ZoomPresenterImpl } from "./presenters/ZoomPresenter"; import { WindowPresenterImpl } from "./presenters/WindowPresenter"; +import { FindClientImpl } from "./clients/FindClient"; import { ConsoleFrameClientImpl } from "./clients/ConsoleFrameClient"; +import { FindRepositoryImpl } from "./repositories/FindRepository"; +import { ReadyFrameRepositoryImpl } from "./repositories/ReadyFrameRepository"; container.register("LocalSettingRepository", { useClass: LocalSettingRepository, @@ -40,7 +43,12 @@ container.register("TabRepository", { useClass: TabRepositoryImpl }); container.register("ZoomPresenter", { useClass: ZoomPresenterImpl }); container.register("TabPresenter", { useClass: TabPresenterImpl }); container.register("WindowPresenter", { useClass: WindowPresenterImpl }); +container.register("FindRepository", { useClass: FindRepositoryImpl }); +container.register("FindClient", { useClass: FindClientImpl }); container.register("NavigateClient", { useClass: NavigateClientImpl }); container.register("ConsoleClient", { useClass: ConsoleClientImpl }); container.register("ConsoleFrameClient", { useClass: ConsoleFrameClientImpl }); container.register("OperatorFactory", { useClass: OperatorFactoryImpl }); +container.register("ReadyFrameRepository", { + useClass: ReadyFrameRepositoryImpl, +}); diff --git a/src/background/infrastructures/ContentMessageListener.ts b/src/background/infrastructures/ContentMessageListener.ts index 6023047..ce7ff09 100644 --- a/src/background/infrastructures/ContentMessageListener.ts +++ b/src/background/infrastructures/ContentMessageListener.ts @@ -3,13 +3,13 @@ import * as messages from "../../shared/messages"; import * as operations from "../../shared/operations"; import CommandController from "../controllers/CommandController"; import SettingController from "../controllers/SettingController"; -import FindController from "../controllers/FindController"; import AddonEnabledController from "../controllers/AddonEnabledController"; import LinkController from "../controllers/LinkController"; import OperationController from "../controllers/OperationController"; import MarkController from "../controllers/MarkController"; import CompletionController from "../controllers/CompletionController"; import ConsoleController from "../controllers/ConsoleController"; +import FindController from "../controllers/FindController"; @injectable() export default class ContentMessageListener { @@ -19,12 +19,12 @@ export default class ContentMessageListener { private readonly settingController: SettingController, private readonly commandController: CommandController, private readonly completionController: CompletionController, - private readonly findController: FindController, private readonly addonEnabledController: AddonEnabledController, private readonly linkController: LinkController, private readonly operationController: OperationController, private readonly markController: MarkController, - private readonly consoleController: ConsoleController + private readonly consoleController: ConsoleController, + private readonly findController: FindController ) {} run(): void { @@ -36,6 +36,7 @@ export default class ContentMessageListener { return {}; } return ret.catch((e) => { + console.error(e); if (!sender.tab || !sender.tab.id) { return; } @@ -45,6 +46,7 @@ export default class ContentMessageListener { }); }); } catch (e) { + console.error(e); if (!sender.tab || !sender.tab.id) { return; } @@ -80,6 +82,8 @@ export default class ContentMessageListener { return this.completionController.getProperties(); case messages.CONSOLE_ENTER_COMMAND: return this.onConsoleEnterCommand(message.text); + case messages.CONSOLE_ENTER_FIND: + return this.findController.startFind(senderTab.id!, message.keyword); case messages.CONSOLE_RESIZE: return this.onConsoleResize( senderTab.id!, @@ -88,10 +92,6 @@ export default class ContentMessageListener { ); case messages.SETTINGS_QUERY: return this.onSettingsQuery(); - case messages.FIND_GET_KEYWORD: - return this.onFindGetKeyword(); - case messages.FIND_SET_KEYWORD: - return this.onFindSetKeyword(message.keyword); case messages.ADDON_ENABLED_RESPONSE: return this.onAddonEnabledResponse(message.enabled); case messages.OPEN_URL: @@ -132,14 +132,6 @@ export default class ContentMessageListener { return (await this.settingController.getSetting()).toJSON(); } - onFindGetKeyword(): Promise<string> { - return this.findController.getKeyword(); - } - - onFindSetKeyword(keyword: string): Promise<void> { - return this.findController.setKeyword(keyword); - } - onAddonEnabledResponse(enabled: boolean): Promise<void> { return this.addonEnabledController.indicate(enabled); } diff --git a/src/background/infrastructures/FindPortListener.ts b/src/background/infrastructures/FindPortListener.ts new file mode 100644 index 0000000..ca82439 --- /dev/null +++ b/src/background/infrastructures/FindPortListener.ts @@ -0,0 +1,23 @@ +import { injectable } from "tsyringe"; + +type OnConnectFunc = (port: browser.runtime.Port) => void; +type OnDisconnectFunc = (port: browser.runtime.Port) => void; + +@injectable() +export default class FindPortListener { + constructor( + private readonly onConnect: OnConnectFunc, + private readonly onDisconnect: OnDisconnectFunc + ) {} + + run(): void { + browser.runtime.onConnect.addListener((port) => { + if (port.name !== "vimvixen-find") { + return; + } + + port.onDisconnect.addListener(this.onDisconnect); + this.onConnect(port); + }); + } +} diff --git a/src/background/operators/impls/FindNextOperator.ts b/src/background/operators/impls/FindNextOperator.ts new file mode 100644 index 0000000..99f1759 --- /dev/null +++ b/src/background/operators/impls/FindNextOperator.ts @@ -0,0 +1,91 @@ +import Operator from "../Operator"; +import TabPresenter from "../../presenters/TabPresenter"; +import FindRepository from "../../repositories/FindRepository"; +import FindClient from "../../clients/FindClient"; +import ConsoleClient from "../../infrastructures/ConsoleClient"; +import ReadyFrameRepository from "../../repositories/ReadyFrameRepository"; + +export default class FindNextOperator implements Operator { + constructor( + private readonly tabPresenter: TabPresenter, + private readonly findRepository: FindRepository, + private readonly findClient: FindClient, + private readonly consoleClient: ConsoleClient, + private readonly frameRepository: ReadyFrameRepository + ) {} + + async run(): Promise<void> { + const tab = await this.tabPresenter.getCurrent(); + const tabId = tab?.id; + if (tabId == null) { + return; + } + + const frameIds = await this.frameRepository.getFrameIds(tabId); + if (typeof frameIds === "undefined") { + // No frames are ready + return; + } + + const state = await this.findRepository.getLocalState(tabId); + if (state) { + const framePos = frameIds.indexOf(state.frameId); + if (framePos !== -1) { + // Start to find the keyword from the current frame which last found on, + // and concat it to end of frame ids to perform a wrap-search + // + // ,- keyword should be in this frame + // | + // [100, 101, 0, 100] + // | + // `- continue from frame id 100 + // + const targetFrameIds = frameIds + .slice(framePos) + .concat(frameIds.slice(0, framePos), frameIds[framePos]); + + for (const frameId of targetFrameIds) { + const found = await this.findClient.findNext( + tabId, + frameId, + state.keyword + ); + if (found) { + this.findRepository.setLocalState(tabId, { + keyword: state.keyword, + frameId, + }); + return; + } + this.findClient.clearSelection(tabId, frameId); + } + + // The keyword is gone. + this.consoleClient.showError( + tabId, + "Pattern not found: " + state.keyword + ); + return; + } + } + + const keyword = await this.findRepository.getGlobalKeyword(); + if (keyword) { + for (const frameId of frameIds) { + await this.findClient.clearSelection(tabId, frameId); + } + + for (const frameId of frameIds) { + const found = await this.findClient.findNext(tabId, frameId, keyword); + if (found) { + await this.findRepository.setLocalState(tabId, { frameId, keyword }); + await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); + return; + } + } + this.consoleClient.showError(tabId, "Pattern not found: " + keyword); + return; + } + await this.consoleClient.showError(tabId, "No previous search keywords"); + } +} diff --git a/src/background/operators/impls/FindOperatorFactoryChain.ts b/src/background/operators/impls/FindOperatorFactoryChain.ts new file mode 100644 index 0000000..cc169dd --- /dev/null +++ b/src/background/operators/impls/FindOperatorFactoryChain.ts @@ -0,0 +1,49 @@ +import { inject, injectable } from "tsyringe"; +import Operator from "../Operator"; +import OperatorFactoryChain from "../OperatorFactoryChain"; +import TabPresenter from "../../presenters/TabPresenter"; +import * as operations from "../../../shared/operations"; +import FindNextOperator from "./FindNextOperator"; +import FindPrevOperator from "./FindPrevOperator"; +import FindRepository from "../../repositories/FindRepository"; +import FindClient from "../../clients/FindClient"; +import ConsoleClient from "../../infrastructures/ConsoleClient"; +import ReadyFrameRepository from "../../repositories/ReadyFrameRepository"; + +@injectable() +export default class FindOperatorFactoryChain implements OperatorFactoryChain { + constructor( + @inject("TabPresenter") + private readonly tabPresenter: TabPresenter, + @inject("FindRepository") + private readonly findRepository: FindRepository, + @inject("FindClient") + private readonly findClient: FindClient, + @inject("ConsoleClient") + private readonly consoleClient: ConsoleClient, + @inject("ReadyFrameRepository") + private readonly frameRepository: ReadyFrameRepository + ) {} + + create(op: operations.Operation): Operator | null { + switch (op.type) { + case operations.FIND_NEXT: + return new FindNextOperator( + this.tabPresenter, + this.findRepository, + this.findClient, + this.consoleClient, + this.frameRepository + ); + case operations.FIND_PREV: + return new FindPrevOperator( + this.tabPresenter, + this.findRepository, + this.findClient, + this.consoleClient, + this.frameRepository + ); + } + return null; + } +} diff --git a/src/background/operators/impls/FindPrevOperator.ts b/src/background/operators/impls/FindPrevOperator.ts new file mode 100644 index 0000000..f8506b9 --- /dev/null +++ b/src/background/operators/impls/FindPrevOperator.ts @@ -0,0 +1,92 @@ +import Operator from "../Operator"; +import TabPresenter from "../../presenters/TabPresenter"; +import FindRepository from "../../repositories/FindRepository"; +import FindClient from "../../clients/FindClient"; +import ConsoleClient from "../../infrastructures/ConsoleClient"; +import ReadyFrameRepository from "../../repositories/ReadyFrameRepository"; + +export default class FindPrevOperator implements Operator { + constructor( + private readonly tabPresenter: TabPresenter, + private readonly findRepository: FindRepository, + private readonly findClient: FindClient, + private readonly consoleClient: ConsoleClient, + private readonly frameRepository: ReadyFrameRepository + ) {} + + async run(): Promise<void> { + const tab = await this.tabPresenter.getCurrent(); + const tabId = tab?.id; + if (tabId == null) { + return; + } + + let frameIds = await this.frameRepository.getFrameIds(tabId); + if (typeof frameIds === "undefined") { + // No frames are ready + return; + } + frameIds = frameIds.slice(0).reverse(); + + const state = await this.findRepository.getLocalState(tabId); + if (state) { + const framePos = frameIds.indexOf(state.frameId); + if (framePos !== -1) { + // Start to find the keyword from the current frame which last found on, + // and concat it to end of frame ids to perform a wrap-search + // + // ,- keyword should be in this frame + // | + // [100, 101, 0, 100] + // | + // `- continue from frame id 100 + // + const targetFrameIds = frameIds + .slice(framePos) + .concat(frameIds.slice(0, framePos), frameIds[framePos]); + + for (const frameId of targetFrameIds) { + const found = await this.findClient.findPrev( + tabId, + frameId, + state.keyword + ); + if (found) { + this.findRepository.setLocalState(tabId, { + keyword: state.keyword, + frameId, + }); + return; + } + this.findClient.clearSelection(tabId, frameId); + } + + // The keyword is gone. + this.consoleClient.showError( + tabId, + "Pattern not found: " + state.keyword + ); + return; + } + } + + const keyword = await this.findRepository.getGlobalKeyword(); + if (keyword) { + for (const frameId of frameIds) { + await this.findClient.clearSelection(tabId, frameId); + } + + for (const frameId of frameIds) { + const found = await this.findClient.findPrev(tabId, frameId, keyword); + if (found) { + await this.findRepository.setLocalState(tabId, { frameId, keyword }); + await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); + return; + } + } + this.consoleClient.showError(tabId, "Pattern not found: " + keyword); + return; + } + await this.consoleClient.showError(tabId, "No previous search keywords"); + } +} diff --git a/src/background/operators/impls/OperatorFactoryImpl.ts b/src/background/operators/impls/OperatorFactoryImpl.ts index 34e7bb5..ce87491 100644 --- a/src/background/operators/impls/OperatorFactoryImpl.ts +++ b/src/background/operators/impls/OperatorFactoryImpl.ts @@ -8,6 +8,7 @@ import NavigateOperatorFactoryChain from "./NavigateOperatorFactoryChain"; import RepeatOperatorFactoryChain from "./RepeatOperatorFactoryChain"; import TabOperatorFactoryChain from "./TabOperatorFactoryChain"; import ZoomOperatorFactoryChain from "./ZoomOperatorFactoryChain"; +import FindOperatorFactoryChain from "./FindOperatorFactoryChain"; import * as operations from "../../../shared/operations"; @injectable() @@ -20,6 +21,7 @@ export class OperatorFactoryImpl implements OperatorFactory { navigateOperatorFactoryChain: NavigateOperatorFactoryChain, tabOperatorFactoryChain: TabOperatorFactoryChain, zoomOperatorFactoryChain: ZoomOperatorFactoryChain, + findOperatorFactoryChain: FindOperatorFactoryChain, @inject(delay(() => RepeatOperatorFactoryChain)) repeatOperatorFactoryChain: RepeatOperatorFactoryChain ) { @@ -30,6 +32,7 @@ export class OperatorFactoryImpl implements OperatorFactory { repeatOperatorFactoryChain, tabOperatorFactoryChain, zoomOperatorFactoryChain, + findOperatorFactoryChain, ]; } diff --git a/src/background/repositories/FindRepository.ts b/src/background/repositories/FindRepository.ts index 813e065..3492759 100644 --- a/src/background/repositories/FindRepository.ts +++ b/src/background/repositories/FindRepository.ts @@ -1,22 +1,68 @@ import { injectable } from "tsyringe"; import MemoryStorage from "../infrastructures/MemoryStorage"; -const FIND_KEYWORD_KEY = "find-keyword"; +const FIND_GLOBAL_KEYWORD_KEY = "find-global-keyword"; +const FIND_LOCAL_KEYWORD_KEY = "find-local-keyword"; + +export type FindState = { + keyword: string; + frameId: number; +}; + +export default interface FindRepository { + getGlobalKeyword(): Promise<string | undefined>; + + setGlobalKeyword(keyword: string): Promise<void>; + + getLocalState(tabId: number): Promise<undefined | FindState>; + + setLocalState(tabId: number, state: FindState): Promise<void>; + + deleteLocalState(tabId: number): Promise<void>; +} @injectable() -export default class FindRepository { +export class FindRepositoryImpl implements FindRepository { private cache: MemoryStorage; constructor() { this.cache = new MemoryStorage(); } - getKeyword(): Promise<string> { - return Promise.resolve(this.cache.get(FIND_KEYWORD_KEY)); + getGlobalKeyword(): Promise<string | undefined> { + return Promise.resolve(this.cache.get(FIND_GLOBAL_KEYWORD_KEY)); + } + + setGlobalKeyword(keyword: string): Promise<void> { + this.cache.set(FIND_GLOBAL_KEYWORD_KEY, keyword); + return Promise.resolve(); + } + + getLocalState(tabId: number): Promise<FindState | undefined> { + let states = this.cache.get(FIND_LOCAL_KEYWORD_KEY); + if (typeof states === "undefined") { + states = {}; + } + return Promise.resolve(states[tabId]); + } + + setLocalState(tabId: number, state: FindState): Promise<void> { + let states = this.cache.get(FIND_LOCAL_KEYWORD_KEY); + if (typeof states === "undefined") { + states = {}; + } + states[tabId] = state; + this.cache.set(FIND_LOCAL_KEYWORD_KEY, states); + return Promise.resolve(); } - setKeyword(keyword: string): Promise<void> { - this.cache.set(FIND_KEYWORD_KEY, keyword); + deleteLocalState(tabId: number): Promise<void> { + const states = this.cache.get(FIND_LOCAL_KEYWORD_KEY); + if (typeof states === "undefined") { + return Promise.resolve(); + } + delete states[tabId]; + this.cache.set(FIND_LOCAL_KEYWORD_KEY, states); return Promise.resolve(); } } diff --git a/src/background/repositories/ReadyFrameRepository.ts b/src/background/repositories/ReadyFrameRepository.ts new file mode 100644 index 0000000..c993858 --- /dev/null +++ b/src/background/repositories/ReadyFrameRepository.ts @@ -0,0 +1,70 @@ +import MemoryStorage from "../infrastructures/MemoryStorage"; + +const REPOSITORY_KEY = "readyFrameRepository"; + +type State = { [tabId: number]: { [frameId: number]: number } }; + +export default interface ReadyFrameRepository { + addFrameId(tabId: number, frameId: number): Promise<void>; + + removeFrameId(tabId: number, frameId: number): Promise<void>; + + getFrameIds(tabId: number): Promise<number[] | undefined>; +} + +export class ReadyFrameRepositoryImpl implements ReadyFrameRepository { + private cache: MemoryStorage; + + constructor() { + this.cache = new MemoryStorage(); + } + + addFrameId(tabId: number, frameId: number): Promise<void> { + let state: State | undefined = this.cache.get(REPOSITORY_KEY); + if (typeof state === "undefined") { + state = {}; + } + const tab = state[tabId] || {}; + tab[frameId] = (tab[frameId] || 0) + 1; + state[tabId] = tab; + this.cache.set(REPOSITORY_KEY, state); + return Promise.resolve(); + } + + removeFrameId(tabId: number, frameId: number): Promise<void> { + const state: State | undefined = this.cache.get(REPOSITORY_KEY); + if (typeof state === "undefined") { + return Promise.resolve(); + } + const ids = state[tabId]; + if (typeof ids === "undefined") { + return Promise.resolve(); + } + const tab = state[tabId] || {}; + tab[frameId] = (tab[frameId] || 0) - 1; + if (tab[frameId] == 0) { + delete tab[frameId]; + } + if (Object.keys(tab).length === 0) { + delete state[tabId]; + } + + this.cache.set(REPOSITORY_KEY, state); + return Promise.resolve(); + } + + getFrameIds(tabId: number): Promise<number[] | undefined> { + const state: State | undefined = this.cache.get(REPOSITORY_KEY); + if (typeof state === "undefined") { + return Promise.resolve(undefined); + } + const tab = state[tabId]; + if (typeof tab === "undefined") { + return Promise.resolve(undefined); + } + const frameIds = Object.keys(tab) + .map((v) => Number(v)) + .sort(); + return Promise.resolve(frameIds); + } +} diff --git a/src/background/usecases/FindUseCase.ts b/src/background/usecases/FindUseCase.ts deleted file mode 100644 index cc111f2..0000000 --- a/src/background/usecases/FindUseCase.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { injectable } from "tsyringe"; -import FindRepository from "../repositories/FindRepository"; - -@injectable() -export default class FindUseCase { - constructor(private readonly findRepository: FindRepository) {} - - getKeyword(): Promise<string> { - return this.findRepository.getKeyword(); - } - - setKeyword(keyword: string): Promise<void> { - return this.findRepository.setKeyword(keyword); - } -} diff --git a/src/background/usecases/StartFindUseCase.ts b/src/background/usecases/StartFindUseCase.ts new file mode 100644 index 0000000..6aad962 --- /dev/null +++ b/src/background/usecases/StartFindUseCase.ts @@ -0,0 +1,56 @@ +import { inject, injectable } from "tsyringe"; +import ConsoleClient from "../infrastructures/ConsoleClient"; +import FindRepositoryImpl from "../repositories/FindRepository"; +import FindClient from "../clients/FindClient"; +import ReadyFrameRepository from "../repositories/ReadyFrameRepository"; + +@injectable() +export default class StartFindUseCase { + constructor( + @inject("FindClient") + private readonly findClient: FindClient, + @inject("FindRepository") + private readonly findRepository: FindRepositoryImpl, + @inject("ConsoleClient") + private readonly consoleClient: ConsoleClient, + @inject("ReadyFrameRepository") + private readonly frameRepository: ReadyFrameRepository + ) {} + + async startFind(tabId: number, keyword?: string): Promise<void> { + if (typeof keyword === "undefined") { + keyword = (await this.findRepository.getLocalState(tabId))?.keyword; + } + if (typeof keyword === "undefined") { + keyword = await this.findRepository.getGlobalKeyword(); + } + if (typeof keyword === "undefined") { + await this.consoleClient.showError(tabId, "No previous search keywords"); + return; + } + + this.findRepository.setGlobalKeyword(keyword); + + const frameIds = await this.frameRepository.getFrameIds(tabId); + if (typeof frameIds === "undefined") { + // No frames are ready + return; + } + for (const frameId of frameIds) { + await this.findClient.clearSelection(tabId, frameId); + } + + for (const frameId of frameIds) { + const found = await this.findClient.findNext(tabId, frameId, keyword); + if (found) { + await this.findRepository.setLocalState(tabId, { + frameId, + keyword, + }); + await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); + return; + } + } + this.consoleClient.showError(tabId, "Pattern not found: " + keyword); + } +} diff --git a/src/console/app/hooks.ts b/src/console/app/hooks.ts index eefdea3..00ac05c 100644 --- a/src/console/app/hooks.ts +++ b/src/console/app/hooks.ts @@ -103,13 +103,10 @@ export const useExecCommand = () => { export const useExecFind = () => { const execFind = React.useCallback((text?: string) => { - window.top.postMessage( - JSON.stringify({ - type: messages.CONSOLE_ENTER_FIND, - text, - }), - "*" - ); + browser.runtime.sendMessage({ + type: messages.CONSOLE_ENTER_FIND, + keyword: text, + }); }, []); return execFind; }; diff --git a/src/content/Application.ts b/src/content/Application.ts index b09edfa..6b881fe 100644 --- a/src/content/Application.ts +++ b/src/content/Application.ts @@ -1,6 +1,5 @@ import { injectable } from "tsyringe"; import MessageListener from "./MessageListener"; -import FindController from "./controllers/FindController"; import MarkController from "./controllers/MarkController"; import FollowMasterController from "./controllers/FollowMasterController"; import FollowSlaveController from "./controllers/FollowSlaveController"; @@ -14,6 +13,7 @@ import SettingController from "./controllers/SettingController"; import ConsoleFrameController from "./controllers/ConsoleFrameController"; import NavigateController from "./controllers/NavigateController"; import * as messages from "../shared/messages"; +import FindController from "./controllers/FindController"; type Message = messages.Message; @@ -22,7 +22,6 @@ export default class Application { // eslint-disable-next-line max-params constructor( private messageListener: MessageListener, - private findController: FindController, private markController: MarkController, private followMasterController: FollowMasterController, private followSlaveController: FollowSlaveController, @@ -33,7 +32,8 @@ export default class Application { private addonEnabledController: AddonEnabledController, private settingController: SettingController, private consoleFrameController: ConsoleFrameController, - private navigateController: NavigateController + private navigateController: NavigateController, + private findController: FindController ) {} init(): Promise<void> { @@ -41,18 +41,26 @@ export default class Application { if (window.self === window.top) { this.routeMasterComponents(); } - return this.routeCommonComponents(); + this.routeCommonComponents(); + // Make sure the background script sends a message to the content script by + // establishing a connection. If the background script tries to send a + // message to a frame on which cannot run the content script, it fails with + // a message "Could not establish connection." + // + // The port is never used, and the messages are delivered via + // `browser.tabs.sendMessage` API because sending a message via port cannot + // receive returned value. + // + // /* on background script */ + // port.sendMessage({ type: "do something" }); <- returns void + // + browser.runtime.connect(undefined, { name: "vimvixen-find" }); + return Promise.resolve(); } private routeMasterComponents() { this.messageListener.onWebMessage((msg: Message, sender: Window) => { switch (msg.type) { - case messages.CONSOLE_ENTER_FIND: - return this.findController.start(msg); - case messages.FIND_NEXT: - return this.findController.next(msg); - case messages.FIND_PREV: - return this.findController.prev(msg); case messages.CONSOLE_UNFOCUS: return this.consoleFrameController.unfocus(msg); case messages.FOLLOW_START: @@ -64,16 +72,6 @@ export default class Application { } return undefined; }); - - this.messageListener.onBackgroundMessage((msg: Message) => { - switch (msg.type) { - case messages.ADDON_ENABLED_QUERY: - return this.addonEnabledController.getAddonEnabled(msg); - case messages.TAB_SCROLL_TO: - return this.markController.scrollTo(msg); - } - return undefined; - }); } private routeCommonComponents(): Promise<void> { @@ -109,6 +107,21 @@ export default class Application { return this.navigateController.openLinkPrev(msg); case messages.CONSOLE_RESIZE: return this.consoleFrameController.resize(msg); + case messages.FIND_NEXT: + return this.findController.findNext(msg.keyword); + case messages.FIND_PREV: + return this.findController.findPrev(msg.keyword); + case messages.FIND_CLEAR_SELECTION: + return this.findController.clearSelection(); + } + + if (window.self === window.top) { + switch (msg.type) { + case messages.ADDON_ENABLED_QUERY: + return this.addonEnabledController.getAddonEnabled(msg); + case messages.TAB_SCROLL_TO: + return this.markController.scrollTo(msg); + } } }); diff --git a/src/content/MessageListener.ts b/src/content/MessageListener.ts index 3fe1dcd..31cc1ae 100644 --- a/src/content/MessageListener.ts +++ b/src/content/MessageListener.ts @@ -27,7 +27,12 @@ export default class MessageListener { ) { browser.runtime.onMessage.addListener( (msg: any, sender: WebExtMessageSender) => { - return listener(valueOf(msg), sender); + try { + return Promise.resolve(listener(valueOf(msg), sender)); + } catch (e) { + console.warn(e); + return; + } } ); } diff --git a/src/content/client/FindClient.ts b/src/content/client/FindClient.ts deleted file mode 100644 index 7da5069..0000000 --- a/src/content/client/FindClient.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as messages from "../../shared/messages"; - -export default interface FindClient { - getGlobalLastKeyword(): Promise<string | null>; - - setGlobalLastKeyword(keyword: string): Promise<void>; -} - -export class FindClientImpl implements FindClient { - async getGlobalLastKeyword(): Promise<string | null> { - const keyword = await browser.runtime.sendMessage({ - type: messages.FIND_GET_KEYWORD, - }); - return keyword as string; - } - - async setGlobalLastKeyword(keyword: string): Promise<void> { - await browser.runtime.sendMessage({ - type: messages.FIND_SET_KEYWORD, - keyword: keyword, - }); - } -} diff --git a/src/content/client/FindMasterClient.ts b/src/content/client/FindMasterClient.ts deleted file mode 100644 index 9c3f812..0000000 --- a/src/content/client/FindMasterClient.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as messages from "../../shared/messages"; - -export default interface FindMasterClient { - findNext(): void; - - findPrev(): void; -} - -export class FindMasterClientImpl implements FindMasterClient { - findNext(): void { - window.top.postMessage( - JSON.stringify({ - type: messages.FIND_NEXT, - }), - "*" - ); - } - - findPrev(): void { - window.top.postMessage( - JSON.stringify({ - type: messages.FIND_PREV, - }), - "*" - ); - } -} diff --git a/src/content/controllers/FindController.ts b/src/content/controllers/FindController.ts index 3087d5d..adcdb0d 100644 --- a/src/content/controllers/FindController.ts +++ b/src/content/controllers/FindController.ts @@ -1,20 +1,19 @@ import { injectable } from "tsyringe"; -import * as messages from "../../shared/messages"; import FindUseCase from "../usecases/FindUseCase"; @injectable() export default class FindController { constructor(private findUseCase: FindUseCase) {} - async start(m: messages.ConsoleEnterFindMessage): Promise<void> { - await this.findUseCase.startFind(m.text); + findNext(keyword: string): boolean { + return this.findUseCase.findNext(keyword); } - async next(_: messages.FindNextMessage): Promise<void> { - await this.findUseCase.findNext(); + findPrev(keyword: string): boolean { + return this.findUseCase.findPrev(keyword); } - async prev(_: messages.FindPrevMessage): Promise<void> { - await this.findUseCase.findPrev(); + clearSelection() { + return this.findUseCase.clearSelection(); } } diff --git a/src/content/di.ts b/src/content/di.ts index e74d7ac..4c85e76 100644 --- a/src/content/di.ts +++ b/src/content/di.ts @@ -6,10 +6,6 @@ import { AddressRepositoryImpl } from "./repositories/AddressRepository"; import { ClipboardRepositoryImpl } from "./repositories/ClipboardRepository"; import { ConsoleClientImpl } from "./client/ConsoleClient"; import { ConsoleFramePresenterImpl } from "./presenters/ConsoleFramePresenter"; -import { FindClientImpl } from "./client/FindClient"; -import { FindMasterClientImpl } from "./client/FindMasterClient"; -import { FindPresenterImpl } from "./presenters/FindPresenter"; -import { FindRepositoryImpl } from "./repositories/FindRepository"; import { FocusPresenterImpl } from "./presenters/FocusPresenter"; import { FollowKeyRepositoryImpl } from "./repositories/FollowKeyRepository"; import { FollowMasterClientImpl } from "./client/FollowMasterClient"; @@ -31,6 +27,7 @@ import { TabsClientImpl } from "./client/TabsClient"; import { container } from "tsyringe"; import OperatorFactoryImpl from "./operators/impls/OperatorFactoryImpl"; import { URLRepositoryImpl } from "./operators/impls/URLRepository"; +import { FindPresenterImpl } from "./presenters/FindPresenter"; container.register("FollowMasterClient", { useValue: new FollowMasterClientImpl(window.top), @@ -49,10 +46,6 @@ container.register("ConsoleClient", { useClass: ConsoleClientImpl }); container.register("ConsoleFramePresenter", { useClass: ConsoleFramePresenterImpl, }); -container.register("FindClient", { useClass: FindClientImpl }); -container.register("FindMasterClient", { useClass: FindMasterClientImpl }); -container.register("FindPresenter", { useClass: FindPresenterImpl }); -container.register("FindRepository", { useClass: FindRepositoryImpl }); container.register("FocusPresenter", { useClass: FocusPresenterImpl }); container.register("FollowKeyRepository", { useClass: FollowKeyRepositoryImpl, @@ -79,6 +72,7 @@ container.register("NavigationPresenter", { }); container.register("OperationClient", { useClass: OperationClientImpl }); container.register("ScrollPresenter", { useClass: ScrollPresenterImpl }); +container.register("FindPresenter", { useClass: FindPresenterImpl }); container.register("SettingClient", { useClass: SettingClientImpl }); container.register("SettingRepository", { useClass: SettingRepositoryImpl }); container.register("URLRepository", { useClass: URLRepositoryImpl }); diff --git a/src/content/operators/impls/FindNextOperator.ts b/src/content/operators/impls/FindNextOperator.ts deleted file mode 100644 index c67f6d9..0000000 --- a/src/content/operators/impls/FindNextOperator.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Operator from "../Operator"; -import FindMasterClient from "../../client/FindMasterClient"; - -export default class FindNextOperator implements Operator { - constructor( - private readonly findMasterClient: FindMasterClient, - private readonly repeat: number - ) {} - - async run(): Promise<void> { - for (let i = 0; i < this.repeat; ++i) { - this.findMasterClient.findNext(); - } - } -} diff --git a/src/content/operators/impls/FindOperatorFactoryChain.ts b/src/content/operators/impls/FindOperatorFactoryChain.ts deleted file mode 100644 index b3524c1..0000000 --- a/src/content/operators/impls/FindOperatorFactoryChain.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { inject, injectable } from "tsyringe"; -import Operator from "../Operator"; -import OperatorFactoryChain from "../OperatorFactoryChain"; -import FindNextOperator from "./FindNextOperator"; -import FindPrevOperator from "./FindPrevOperator"; -import FindMasterClient from "../../client/FindMasterClient"; -import * as operations from "../../../shared/operations"; - -@injectable() -export default class FindOperatorFactoryChain implements OperatorFactoryChain { - constructor( - @inject("FindMasterClient") - private readonly findMasterClient: FindMasterClient - ) {} - - create(op: operations.Operation, repeat: number): Operator | null { - switch (op.type) { - case operations.FIND_NEXT: - return new FindNextOperator(this.findMasterClient, repeat); - case operations.FIND_PREV: - return new FindPrevOperator(this.findMasterClient, repeat); - } - return null; - } -} diff --git a/src/content/operators/impls/FindPrevOperator.ts b/src/content/operators/impls/FindPrevOperator.ts deleted file mode 100644 index f73e605..0000000 --- a/src/content/operators/impls/FindPrevOperator.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Operator from "../Operator"; -import FindMasterClient from "../../client/FindMasterClient"; - -export default class FindPrevOperator implements Operator { - constructor( - private readonly findMasterClient: FindMasterClient, - private readonly repeat: number - ) {} - - async run(): Promise<void> { - for (let i = 0; i < this.repeat; ++i) { - this.findMasterClient.findPrev(); - } - } -} diff --git a/src/content/operators/impls/OperatorFactoryImpl.ts b/src/content/operators/impls/OperatorFactoryImpl.ts index 22b35c8..bc9bbee 100644 --- a/src/content/operators/impls/OperatorFactoryImpl.ts +++ b/src/content/operators/impls/OperatorFactoryImpl.ts @@ -7,7 +7,6 @@ import { Operation } from "../../../shared/operations"; import OperationClient from "../../client/OperationClient"; import AddonOperatorFactoryChain from "./AddonOperatorFactoryChain"; import ClipboardOperatorFactoryChain from "./ClipboardOperatorFactoryChain"; -import FindOperatorFactoryChain from "./FindOperatorFactoryChain"; import FocusOperatorFactoryChain from "./FocusOperatorFactoryChain"; import FollowOperatorFactoryChain from "./FollowOperatorFactoryChain"; import MarkOperatorFactoryChain from "./MarkOperatorFactoryChain"; @@ -20,7 +19,6 @@ export default class OperatorFactoryImpl implements OperatorFactory { constructor( addonOperatorFactoryChain: AddonOperatorFactoryChain, clipboardOperatorFactoryChain: ClipboardOperatorFactoryChain, - findOperatorFactoryChain: FindOperatorFactoryChain, focusOperatorFactoryChain: FocusOperatorFactoryChain, followOperatorFactoryChain: FollowOperatorFactoryChain, markOperatorFactoryChain: MarkOperatorFactoryChain, @@ -31,7 +29,6 @@ export default class OperatorFactoryImpl implements OperatorFactory { this.factoryChains = [ addonOperatorFactoryChain, clipboardOperatorFactoryChain, - findOperatorFactoryChain, focusOperatorFactoryChain, followOperatorFactoryChain, markOperatorFactoryChain, diff --git a/src/content/presenters/FindPresenter.ts b/src/content/presenters/FindPresenter.ts index b25190c..569f161 100644 --- a/src/content/presenters/FindPresenter.ts +++ b/src/content/presenters/FindPresenter.ts @@ -7,16 +7,10 @@ export default interface FindPresenter { export class FindPresenterImpl implements FindPresenter { find(keyword: string, backwards: boolean): boolean { const caseSensitive = false; - const wrapScan = true; + const wrapScan = false; // NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work // because of same origin policy - const found = window.find(keyword, caseSensitive, backwards, wrapScan); - if (found) { - return found; - } - this.clearSelection(); - return window.find(keyword, caseSensitive, backwards, wrapScan); } diff --git a/src/content/repositories/FindRepository.ts b/src/content/repositories/FindRepository.ts deleted file mode 100644 index aeb200f..0000000 --- a/src/content/repositories/FindRepository.ts +++ /dev/null @@ -1,17 +0,0 @@ -export default interface FindRepository { - getLastKeyword(): string | null; - - setLastKeyword(keyword: string): void; -} - -let current: string | null = null; - -export class FindRepositoryImpl implements FindRepository { - getLastKeyword(): string | null { - return current; - } - - setLastKeyword(keyword: string): void { - current = keyword; - } -} diff --git a/src/content/usecases/FindUseCase.ts b/src/content/usecases/FindUseCase.ts index bff0eee..9e77124 100644 --- a/src/content/usecases/FindUseCase.ts +++ b/src/content/usecases/FindUseCase.ts @@ -1,67 +1,22 @@ -import { injectable, inject } from "tsyringe"; +import { inject, injectable } from "tsyringe"; import FindPresenter from "../presenters/FindPresenter"; -import FindRepository from "../repositories/FindRepository"; -import FindClient from "../client/FindClient"; -import ConsoleClient from "../client/ConsoleClient"; @injectable() export default class FindUseCase { constructor( - @inject("FindPresenter") private presenter: FindPresenter, - @inject("FindRepository") private repository: FindRepository, - @inject("FindClient") private client: FindClient, - @inject("ConsoleClient") private consoleClient: ConsoleClient + @inject("FindPresenter") + private readonly findPresenter: FindPresenter ) {} - async startFind(keyword?: string): Promise<void> { - this.presenter.clearSelection(); - if (keyword) { - this.saveKeyword(keyword); - } else { - const lastKeyword = await this.getKeyword(); - if (!lastKeyword) { - return this.showNoLastKeywordError(); - } - this.saveKeyword(lastKeyword); - } - return this.findNext(); + findNext(keyword: string): boolean { + return this.findPresenter.find(keyword, false); } - findNext(): Promise<void> { - return this.findNextPrev(false); + findPrev(keyword: string): boolean { + return this.findPresenter.find(keyword, true); } - findPrev(): Promise<void> { - return this.findNextPrev(true); - } - - private async findNextPrev(backwards: boolean): Promise<void> { - const keyword = await this.getKeyword(); - if (!keyword) { - return this.showNoLastKeywordError(); - } - const found = this.presenter.find(keyword, backwards); - if (found) { - this.consoleClient.info("Pattern found: " + keyword); - } else { - this.consoleClient.error("Pattern not found: " + keyword); - } - } - - private async getKeyword(): Promise<string | null> { - let keyword = this.repository.getLastKeyword(); - if (!keyword) { - keyword = await this.client.getGlobalLastKeyword(); - } - return keyword; - } - - private async saveKeyword(keyword: string): Promise<void> { - this.repository.setLastKeyword(keyword); - await this.client.setGlobalLastKeyword(keyword); - } - - private async showNoLastKeywordError(): Promise<void> { - await this.consoleClient.error("No previous search keywords"); + clearSelection() { + this.findPresenter.clearSelection(); } } diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 59329e7..8b9e598 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -37,8 +37,7 @@ export const TAB_SCROLL_TO = "tab.scroll.to"; export const FIND_NEXT = "find.next"; export const FIND_PREV = "find.prev"; -export const FIND_GET_KEYWORD = "find.get.keyword"; -export const FIND_SET_KEYWORD = "find.set.keyword"; +export const FIND_CLEAR_SELECTION = "find.clear.selection"; export const ADDON_ENABLED_QUERY = "addon.enabled.query"; export const ADDON_ENABLED_RESPONSE = "addon.enabled.response"; @@ -73,7 +72,7 @@ export interface ConsoleEnterCommandMessage { export interface ConsoleEnterFindMessage { type: typeof CONSOLE_ENTER_FIND; - text?: string; + keyword?: string; } export interface ConsoleShowCommandMessage { @@ -229,20 +228,16 @@ export interface TabScrollToMessage { export interface FindNextMessage { type: typeof FIND_NEXT; + keyword: string; } export interface FindPrevMessage { type: typeof FIND_PREV; + keyword: string; } -export interface FindGetKeywordMessage { - type: typeof FIND_GET_KEYWORD; -} - -export interface FindSetKeywordMessage { - type: typeof FIND_SET_KEYWORD; - keyword: string; - found: boolean; +export interface FindClearSelection { + type: typeof FIND_CLEAR_SELECTION; } export interface AddonEnabledQueryMessage { @@ -324,8 +319,7 @@ export type Message = | TabScrollToMessage | FindNextMessage | FindPrevMessage - | FindGetKeywordMessage - | FindSetKeywordMessage + | FindClearSelection | AddonEnabledQueryMessage | AddonEnabledResponseMessage | AddonToggleEnabledMessage @@ -362,8 +356,7 @@ export const valueOf = (o: any): Message => { case TAB_SCROLL_TO: case FIND_NEXT: case FIND_PREV: - case FIND_GET_KEYWORD: - case FIND_SET_KEYWORD: + case FIND_CLEAR_SELECTION: case ADDON_ENABLED_QUERY: case ADDON_ENABLED_RESPONSE: case ADDON_TOGGLE_ENABLED: diff --git a/src/shared/settings/Settings.ts b/src/shared/settings/Settings.ts index 6c4fa0a..2294c67 100644 --- a/src/shared/settings/Settings.ts +++ b/src/shared/settings/Settings.ts @@ -44,7 +44,7 @@ export default class Settings { if (!valid) { const message = (validate as any) .errors!.map((err: Ajv.ErrorObject) => { - return `'${err.dataPath}' ${err.message}`; + return `'${err.instancePath}' ${err.message}`; }) .join("; "); throw new TypeError(message); diff --git a/src/shared/settings/validate.js b/src/shared/settings/validate.js index 30f7888..b784854 100644 --- a/src/shared/settings/validate.js +++ b/src/shared/settings/validate.js @@ -1,612 +1 @@ -'use strict'; -var equal = require('ajv/lib/compile/equal'); -var validate = (function() { - var pattern0 = new RegExp('.*'); - var refVal = []; - return function validate(data, dataPath, parentData, parentDataProperty, rootData) { - 'use strict'; - var vErrors = null; - var errors = 0; - if ((data && typeof data === "object" && !Array.isArray(data))) { - var errs__0 = errors; - var valid1 = true; - for (var key0 in data) { - var isAdditional0 = !(false || key0 == 'keymaps' || key0 == 'search' || key0 == 'properties' || key0 == 'blacklist'); - if (isAdditional0) { - valid1 = false; - validate.errors = [{ - keyword: 'additionalProperties', - dataPath: (dataPath || '') + "", - schemaPath: '#/additionalProperties', - params: { - additionalProperty: '' + key0 + '' - }, - message: 'should NOT have additional properties' - }]; - return false; - break; - } - } - if (valid1) { - var data1 = data.keymaps; - if (data1 === undefined) { - valid1 = true; - } else { - var errs_1 = errors; - if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) { - var errs__1 = errors; - var valid2 = true; - for (var key1 in data1) { - if (pattern0.test(key1)) { - var data2 = data1[key1]; - var errs_2 = errors; - if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) { - if (true) { - var errs__2 = errors; - var valid3 = true; - if (data2.type === undefined) { - valid3 = false; - validate.errors = [{ - keyword: 'required', - dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\']', - schemaPath: '#/properties/keymaps/patternProperties/.*/required', - params: { - missingProperty: 'type' - }, - message: 'should have required property \'type\'' - }]; - return false; - } else { - var errs_3 = errors; - if (typeof data2.type !== "string") { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\'].type', - schemaPath: '#/properties/keymaps/patternProperties/.*/properties/type/type', - params: { - type: 'string' - }, - message: 'should be string' - }]; - return false; - } - var valid3 = errors === errs_3; - } - } - } else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\']', - schemaPath: '#/properties/keymaps/patternProperties/.*/type', - params: { - type: 'object' - }, - message: 'should be object' - }]; - return false; - } - var valid2 = errors === errs_2; - if (!valid2) break; - } else valid2 = true; - } - } else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.keymaps', - schemaPath: '#/properties/keymaps/type', - params: { - type: 'object' - }, - message: 'should be object' - }]; - return false; - } - var valid1 = errors === errs_1; - } - if (valid1) { - var data1 = data.search; - if (data1 === undefined) { - valid1 = true; - } else { - var errs_1 = errors; - if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) { - if (true) { - var errs__1 = errors; - var valid2 = true; - if (data1.default === undefined) { - valid2 = false; - validate.errors = [{ - keyword: 'required', - dataPath: (dataPath || '') + '.search', - schemaPath: '#/properties/search/required', - params: { - missingProperty: 'default' - }, - message: 'should have required property \'default\'' - }]; - return false; - } else { - var errs_2 = errors; - if (typeof data1.default !== "string") { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.search.default', - schemaPath: '#/properties/search/properties/default/type', - params: { - type: 'string' - }, - message: 'should be string' - }]; - return false; - } - var valid2 = errors === errs_2; - } - if (valid2) { - var data2 = data1.engines; - if (data2 === undefined) { - valid2 = false; - validate.errors = [{ - keyword: 'required', - dataPath: (dataPath || '') + '.search', - schemaPath: '#/properties/search/required', - params: { - missingProperty: 'engines' - }, - message: 'should have required property \'engines\'' - }]; - return false; - } else { - var errs_2 = errors; - if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) { - var errs__2 = errors; - var valid3 = true; - for (var key2 in data2) { - if (pattern0.test(key2)) { - var errs_3 = errors; - if (typeof data2[key2] !== "string") { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.search.engines[\'' + key2 + '\']', - schemaPath: '#/properties/search/properties/engines/patternProperties/.*/type', - params: { - type: 'string' - }, - message: 'should be string' - }]; - return false; - } - var valid3 = errors === errs_3; - if (!valid3) break; - } else valid3 = true; - } - } else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.search.engines', - schemaPath: '#/properties/search/properties/engines/type', - params: { - type: 'object' - }, - message: 'should be object' - }]; - return false; - } - var valid2 = errors === errs_2; - } - } - } - } else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.search', - schemaPath: '#/properties/search/type', - params: { - type: 'object' - }, - message: 'should be object' - }]; - return false; - } - var valid1 = errors === errs_1; - } - if (valid1) { - var data1 = data.properties; - if (data1 === undefined) { - valid1 = true; - } else { - var errs_1 = errors; - if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) { - var errs__1 = errors; - var valid2 = true; - if (data1.hintchars === undefined) { - valid2 = true; - } else { - var errs_2 = errors; - if (typeof data1.hintchars !== "string") { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.properties.hintchars', - schemaPath: '#/properties/properties/properties/hintchars/type', - params: { - type: 'string' - }, - message: 'should be string' - }]; - return false; - } - var valid2 = errors === errs_2; - } - if (valid2) { - if (data1.smoothscroll === undefined) { - valid2 = true; - } else { - var errs_2 = errors; - if (typeof data1.smoothscroll !== "boolean") { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.properties.smoothscroll', - schemaPath: '#/properties/properties/properties/smoothscroll/type', - params: { - type: 'boolean' - }, - message: 'should be boolean' - }]; - return false; - } - var valid2 = errors === errs_2; - } - if (valid2) { - if (data1.complete === undefined) { - valid2 = true; - } else { - var errs_2 = errors; - if (typeof data1.complete !== "string") { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.properties.complete', - schemaPath: '#/properties/properties/properties/complete/type', - params: { - type: 'string' - }, - message: 'should be string' - }]; - return false; - } - var valid2 = errors === errs_2; - } - if (valid2) { - var data2 = data1.colorscheme; - if (data2 === undefined) { - valid2 = true; - } else { - var errs_2 = errors; - if (typeof data2 !== "string") { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.properties.colorscheme', - schemaPath: '#/properties/properties/properties/colorscheme/type', - params: { - type: 'string' - }, - message: 'should be string' - }]; - return false; - } - var schema2 = validate.schema.properties.properties.properties.colorscheme.enum; - var valid2; - valid2 = false; - for (var i2 = 0; i2 < schema2.length; i2++) - if (equal(data2, schema2[i2])) { - valid2 = true; - break; - } if (!valid2) { - validate.errors = [{ - keyword: 'enum', - dataPath: (dataPath || '') + '.properties.colorscheme', - schemaPath: '#/properties/properties/properties/colorscheme/enum', - params: { - allowedValues: schema2 - }, - message: 'should be equal to one of the allowed values' - }]; - return false; - } - var valid2 = errors === errs_2; - } - } - } - } - } else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.properties', - schemaPath: '#/properties/properties/type', - params: { - type: 'object' - }, - message: 'should be object' - }]; - return false; - } - var valid1 = errors === errs_1; - } - if (valid1) { - var data1 = data.blacklist; - if (data1 === undefined) { - valid1 = true; - } else { - var errs_1 = errors; - if (Array.isArray(data1)) { - var errs__1 = errors; - var valid1; - for (var i1 = 0; i1 < data1.length; i1++) { - var data2 = data1[i1]; - var errs_2 = errors; - var errs__2 = errors; - var valid2 = false; - var errs_3 = errors; - if (typeof data2 !== "string") { - var err = { - keyword: 'type', - dataPath: (dataPath || '') + '.blacklist[' + i1 + ']', - schemaPath: '#/properties/blacklist/items/anyOf/0/type', - params: { - type: 'string' - }, - message: 'should be string' - }; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } - var valid3 = errors === errs_3; - valid2 = valid2 || valid3; - if (!valid2) { - var errs_3 = errors; - if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) { - if (true) { - var errs__3 = errors; - var valid4 = true; - if (data2.url === undefined) { - valid4 = false; - var err = { - keyword: 'required', - dataPath: (dataPath || '') + '.blacklist[' + i1 + ']', - schemaPath: '#/properties/blacklist/items/anyOf/1/required', - params: { - missingProperty: 'url' - }, - message: 'should have required property \'url\'' - }; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } else { - var errs_4 = errors; - if (typeof data2.url !== "string") { - var err = { - keyword: 'type', - dataPath: (dataPath || '') + '.blacklist[' + i1 + '].url', - schemaPath: '#/properties/blacklist/items/anyOf/1/properties/url/type', - params: { - type: 'string' - }, - message: 'should be string' - }; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } - var valid4 = errors === errs_4; - } - if (valid4) { - var data3 = data2.keys; - if (data3 === undefined) { - valid4 = false; - var err = { - keyword: 'required', - dataPath: (dataPath || '') + '.blacklist[' + i1 + ']', - schemaPath: '#/properties/blacklist/items/anyOf/1/required', - params: { - missingProperty: 'keys' - }, - message: 'should have required property \'keys\'' - }; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } else { - var errs_4 = errors; - if (Array.isArray(data3)) { - var errs__4 = errors; - var valid4; - for (var i4 = 0; i4 < data3.length; i4++) { - var errs_5 = errors; - if (typeof data3[i4] !== "string") { - var err = { - keyword: 'type', - dataPath: (dataPath || '') + '.blacklist[' + i1 + '].keys[' + i4 + ']', - schemaPath: '#/properties/blacklist/items/anyOf/1/properties/keys/items/type', - params: { - type: 'string' - }, - message: 'should be string' - }; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } - var valid5 = errors === errs_5; - if (!valid5) break; - } - } else { - var err = { - keyword: 'type', - dataPath: (dataPath || '') + '.blacklist[' + i1 + '].keys', - schemaPath: '#/properties/blacklist/items/anyOf/1/properties/keys/type', - params: { - type: 'array' - }, - message: 'should be array' - }; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } - var valid4 = errors === errs_4; - } - } - } - } else { - var err = { - keyword: 'type', - dataPath: (dataPath || '') + '.blacklist[' + i1 + ']', - schemaPath: '#/properties/blacklist/items/anyOf/1/type', - params: { - type: 'object' - }, - message: 'should be object' - }; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } - var valid3 = errors === errs_3; - valid2 = valid2 || valid3; - } - if (!valid2) { - var err = { - keyword: 'anyOf', - dataPath: (dataPath || '') + '.blacklist[' + i1 + ']', - schemaPath: '#/properties/blacklist/items/anyOf', - params: {}, - message: 'should match some schema in anyOf' - }; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - validate.errors = vErrors; - return false; - } else { - errors = errs__2; - if (vErrors !== null) { - if (errs__2) vErrors.length = errs__2; - else vErrors = null; - } - } - var valid2 = errors === errs_2; - if (!valid2) break; - } - } else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.blacklist', - schemaPath: '#/properties/blacklist/type', - params: { - type: 'array' - }, - message: 'should be array' - }]; - return false; - } - var valid1 = errors === errs_1; - } - } - } - } - } - } else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + "", - schemaPath: '#/type', - params: { - type: 'object' - }, - message: 'should be object' - }]; - return false; - } - validate.errors = vErrors; - return errors === 0; - }; -})(); -validate.schema = { - "type": "object", - "properties": { - "keymaps": { - "type": "object", - "patternProperties": { - ".*": { - "type": "object", - "properties": { - "type": { - "type": "string" - } - }, - "required": ["type"] - } - } - }, - "search": { - "type": "object", - "properties": { - "default": { - "type": "string" - }, - "engines": { - "type": "object", - "patternProperties": { - ".*": { - "type": "string" - } - } - } - }, - "required": ["default", "engines"] - }, - "properties": { - "type": "object", - "properties": { - "hintchars": { - "type": "string" - }, - "smoothscroll": { - "type": "boolean" - }, - "complete": { - "type": "string" - }, - "colorscheme": { - "type": "string", - "enum": ["system", "light", "dark"] - } - } - }, - "blacklist": { - "type": "array", - "items": { - "anyOf": [{ - "type": "string" - }, { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "keys": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["url", "keys"] - }] - } - } - }, - "additionalProperties": false -}; -validate.errors = null; -module.exports = validate;
\ No newline at end of file +"use strict";module.exports = validate20;module.exports.default = validate20;const schema22 = {"type":"object","properties":{"keymaps":{"type":"object","patternProperties":{".*":{"type":"object","properties":{"type":{"type":"string"}},"required":["type"]}}},"search":{"type":"object","properties":{"default":{"type":"string"},"engines":{"type":"object","patternProperties":{".*":{"type":"string"}}}},"required":["default","engines"]},"properties":{"type":"object","properties":{"hintchars":{"type":"string"},"smoothscroll":{"type":"boolean"},"complete":{"type":"string"},"colorscheme":{"type":"string","enum":["system","light","dark"]}}},"blacklist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"url":{"type":"string"},"keys":{"type":"array","items":{"type":"string"}}},"required":["url","keys"]}]}}},"additionalProperties":false};const pattern0 = new RegExp(".*", "u");const func0 = require("ajv/dist/runtime/equal").default;function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(errors === 0){if(data && typeof data == "object" && !Array.isArray(data)){const _errs1 = errors;for(const key0 in data){if(!((((key0 === "keymaps") || (key0 === "search")) || (key0 === "properties")) || (key0 === "blacklist"))){validate20.errors = [{instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}];return false;break;}}if(_errs1 === errors){if(data.keymaps !== undefined){let data0 = data.keymaps;const _errs2 = errors;if(errors === _errs2){if(data0 && typeof data0 == "object" && !Array.isArray(data0)){var valid1 = true;for(const key1 in data0){if(pattern0.test(key1)){let data1 = data0[key1];const _errs4 = errors;if(errors === _errs4){if(data1 && typeof data1 == "object" && !Array.isArray(data1)){let missing0;if((data1.type === undefined) && (missing0 = "type")){validate20.errors = [{instancePath:instancePath+"/keymaps/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/keymaps/patternProperties/.*/required",keyword:"required",params:{missingProperty: missing0},message:"must have required property '"+missing0+"'"}];return false;}else {if(data1.type !== undefined){if(typeof data1.type !== "string"){validate20.errors = [{instancePath:instancePath+"/keymaps/" + key1.replace(/~/g, "~0").replace(/\//g, "~1")+"/type",schemaPath:"#/properties/keymaps/patternProperties/.*/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}}}else {validate20.errors = [{instancePath:instancePath+"/keymaps/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/keymaps/patternProperties/.*/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid1 = _errs4 === errors;if(!valid1){break;}}}}else {validate20.errors = [{instancePath:instancePath+"/keymaps",schemaPath:"#/properties/keymaps/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs2 === errors;}else {var valid0 = true;}if(valid0){if(data.search !== undefined){let data3 = data.search;const _errs8 = errors;if(errors === _errs8){if(data3 && typeof data3 == "object" && !Array.isArray(data3)){let missing1;if(((data3.default === undefined) && (missing1 = "default")) || ((data3.engines === undefined) && (missing1 = "engines"))){validate20.errors = [{instancePath:instancePath+"/search",schemaPath:"#/properties/search/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}];return false;}else {if(data3.default !== undefined){const _errs10 = errors;if(typeof data3.default !== "string"){validate20.errors = [{instancePath:instancePath+"/search/default",schemaPath:"#/properties/search/properties/default/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid3 = _errs10 === errors;}else {var valid3 = true;}if(valid3){if(data3.engines !== undefined){let data5 = data3.engines;const _errs12 = errors;if(errors === _errs12){if(data5 && typeof data5 == "object" && !Array.isArray(data5)){var valid4 = true;for(const key2 in data5){if(pattern0.test(key2)){const _errs14 = errors;if(typeof data5[key2] !== "string"){validate20.errors = [{instancePath:instancePath+"/search/engines/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/search/properties/engines/patternProperties/.*/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid4 = _errs14 === errors;if(!valid4){break;}}}}else {validate20.errors = [{instancePath:instancePath+"/search/engines",schemaPath:"#/properties/search/properties/engines/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid3 = _errs12 === errors;}else {var valid3 = true;}}}}else {validate20.errors = [{instancePath:instancePath+"/search",schemaPath:"#/properties/search/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs8 === errors;}else {var valid0 = true;}if(valid0){if(data.properties !== undefined){let data7 = data.properties;const _errs16 = errors;if(errors === _errs16){if(data7 && typeof data7 == "object" && !Array.isArray(data7)){if(data7.hintchars !== undefined){const _errs18 = errors;if(typeof data7.hintchars !== "string"){validate20.errors = [{instancePath:instancePath+"/properties/hintchars",schemaPath:"#/properties/properties/properties/hintchars/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid5 = _errs18 === errors;}else {var valid5 = true;}if(valid5){if(data7.smoothscroll !== undefined){const _errs20 = errors;if(typeof data7.smoothscroll !== "boolean"){validate20.errors = [{instancePath:instancePath+"/properties/smoothscroll",schemaPath:"#/properties/properties/properties/smoothscroll/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid5 = _errs20 === errors;}else {var valid5 = true;}if(valid5){if(data7.complete !== undefined){const _errs22 = errors;if(typeof data7.complete !== "string"){validate20.errors = [{instancePath:instancePath+"/properties/complete",schemaPath:"#/properties/properties/properties/complete/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid5 = _errs22 === errors;}else {var valid5 = true;}if(valid5){if(data7.colorscheme !== undefined){let data11 = data7.colorscheme;const _errs24 = errors;if(typeof data11 !== "string"){validate20.errors = [{instancePath:instancePath+"/properties/colorscheme",schemaPath:"#/properties/properties/properties/colorscheme/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}if(!(((data11 === "system") || (data11 === "light")) || (data11 === "dark"))){validate20.errors = [{instancePath:instancePath+"/properties/colorscheme",schemaPath:"#/properties/properties/properties/colorscheme/enum",keyword:"enum",params:{allowedValues: schema22.properties.properties.properties.colorscheme.enum},message:"must be equal to one of the allowed values"}];return false;}var valid5 = _errs24 === errors;}else {var valid5 = true;}}}}}else {validate20.errors = [{instancePath:instancePath+"/properties",schemaPath:"#/properties/properties/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs16 === errors;}else {var valid0 = true;}if(valid0){if(data.blacklist !== undefined){let data12 = data.blacklist;const _errs26 = errors;if(errors === _errs26){if(Array.isArray(data12)){var valid6 = true;const len0 = data12.length;for(let i0=0; i0<len0; i0++){let data13 = data12[i0];const _errs28 = errors;const _errs29 = errors;let valid7 = false;const _errs30 = errors;if(typeof data13 !== "string"){const err0 = {instancePath:instancePath+"/blacklist/" + i0,schemaPath:"#/properties/blacklist/items/anyOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}var _valid0 = _errs30 === errors;valid7 = valid7 || _valid0;if(!valid7){const _errs32 = errors;if(errors === _errs32){if(data13 && typeof data13 == "object" && !Array.isArray(data13)){let missing2;if(((data13.url === undefined) && (missing2 = "url")) || ((data13.keys === undefined) && (missing2 = "keys"))){const err1 = {instancePath:instancePath+"/blacklist/" + i0,schemaPath:"#/properties/blacklist/items/anyOf/1/required",keyword:"required",params:{missingProperty: missing2},message:"must have required property '"+missing2+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}else {if(data13.url !== undefined){const _errs34 = errors;if(typeof data13.url !== "string"){const err2 = {instancePath:instancePath+"/blacklist/" + i0+"/url",schemaPath:"#/properties/blacklist/items/anyOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}var valid8 = _errs34 === errors;}else {var valid8 = true;}if(valid8){if(data13.keys !== undefined){let data15 = data13.keys;const _errs36 = errors;if(errors === _errs36){if(Array.isArray(data15)){var valid9 = true;const len1 = data15.length;for(let i1=0; i1<len1; i1++){const _errs38 = errors;if(typeof data15[i1] !== "string"){const err3 = {instancePath:instancePath+"/blacklist/" + i0+"/keys/" + i1,schemaPath:"#/properties/blacklist/items/anyOf/1/properties/keys/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}var valid9 = _errs38 === errors;if(!valid9){break;}}}else {const err4 = {instancePath:instancePath+"/blacklist/" + i0+"/keys",schemaPath:"#/properties/blacklist/items/anyOf/1/properties/keys/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}var valid8 = _errs36 === errors;}else {var valid8 = true;}}}}else {const err5 = {instancePath:instancePath+"/blacklist/" + i0,schemaPath:"#/properties/blacklist/items/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}var _valid0 = _errs32 === errors;valid7 = valid7 || _valid0;}if(!valid7){const err6 = {instancePath:instancePath+"/blacklist/" + i0,schemaPath:"#/properties/blacklist/items/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;validate20.errors = vErrors;return false;}else {errors = _errs29;if(vErrors !== null){if(_errs29){vErrors.length = _errs29;}else {vErrors = null;}}}var valid6 = _errs28 === errors;if(!valid6){break;}}}else {validate20.errors = [{instancePath:instancePath+"/blacklist",schemaPath:"#/properties/blacklist/type",keyword:"type",params:{type: "array"},message:"must be array"}];return false;}}var valid0 = _errs26 === errors;}else {var valid0 = true;}}}}}}else {validate20.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}validate20.errors = vErrors;return errors === 0;}
\ No newline at end of file |