diff --git a/.gitignore b/.gitignore index 2978361..9af12e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. /es /dist +/temp .rpt2_cache +tsdoc-metadata.json # dependencies /node_modules /.pnp diff --git a/.npmignore b/.npmignore index 4a09b64..3f3bcc8 100644 --- a/.npmignore +++ b/.npmignore @@ -1,7 +1,27 @@ +.DS_Store +*.log* +*.tsbuildinfo +/temp .rpt2_cache -node_modules +tsdoc-metadata.json +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc .DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + npm-debug.log* yarn-debug.log* yarn-error.log* -*.tsbuildinfo \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 00ad71f..e997ffc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "typescript.tsdk": "node_modules\\typescript\\lib" + "typescript.tsdk": "node_modules\\typescript\\lib", + "search.exclude": { + "**/api-document": true + } } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7decbeb --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ +// 有关 tasks.json 格式的文档,请参见 + // https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start-tsc", + "problemMatcher": [ + "$tsc-watch" + ], + "isBackground": true + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index a9d2af5..67c0aee 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -# @holoflows/kit · ![GitHub license](https://img.shields.io/badge/license-AGPL-blue.svg?style=flat-square) [![npm version](https://img.shields.io/npm/v/@holoflows/kit.svg?style=flat-square)](https://www.npmjs.com/package/@holoflows/kit) ![Ciecle CI](https://img.shields.io/circleci/project/github/DimensionFoundation/holoflows-kit.svg?style=flat-square&logo=circleci) +# @holoflows/kit · ![GitHub license](https://img.shields.io/badge/license-AGPL-blue.svg?style=flat-square) [![npm version](https://img.shields.io/npm/v/@holoflows/kit.svg?style=flat-square)](https://www.npmjs.com/package/@holoflows/kit) ![Ciecle CI](https://img.shields.io/circleci/project/github/DimensionDev/Holoflows-Kit.svg?style=flat-square&logo=circleci) Toolkit for modern web browser extension developing. ## Documentation -[English documentation](./doc/en/index.md) +[Get Started](./doc/en/index.md) -[简体中文文档](./doc/zh-CN/index.md) +[初次使用](./doc/zh-CN/index.md) + +[API Documentation](./api-documents/kit.md) ## License diff --git a/api-documents/kit.asynccall.md b/api-documents/kit.asynccall.md new file mode 100644 index 0000000..4989426 --- /dev/null +++ b/api-documents/kit.asynccall.md @@ -0,0 +1,84 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AsyncCall](./kit.asynccall.md) + +## AsyncCall() function + +Async call between different context. + +Signature: + +```typescript +export declare function AsyncCall(implementation: Default, options?: Partial): OtherSideImplementedFunctions; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| implementation | Default | Implementation of this side. | +| options | Partial<AsyncCallOptions> | Define your own serializer, MessageCenter or other options. | + +Returns: + +`OtherSideImplementedFunctions` + +## Remarks + +Async call is a high level abstraction of MessageCenter. + +\# Shared code + +- How to stringify/parse parameters/returns should be shared, defaults to NoSerialization. + +- `key` should be shared. + +\# One side + +- Should provide some functions then export its type (for example, `BackgroundCalls`) + +- `const call = AsyncCall(backgroundCalls)` + +- Then you can `call` any method on `ForegroundCalls` + +\# Other side + +- Should provide some functions then export its type (for example, `ForegroundCalls`) + +- `const call = AsyncCall(foregroundCalls)` + +- Then you can `call` any method on `BackgroundCalls` + +Note: Two sides can implement the same function + +## Example + +For example, here is a mono repo. + +Code for UI part: + +```ts +const UI = { + async dialog(text: string) { + alert(text) + }, +} +export type UI = typeof UI +const callsClient = AsyncCall(UI) +callsClient.sendMail('hello world', 'what') + +``` +Code for server part + +```ts +const Server = { + async sendMail(text: string, to: string) { + return true + } +} +export type Server = typeof Server +const calls = AsyncCall(Server) +calls.dialog('hello') + +``` + diff --git a/api-documents/kit.asynccalloptions.dontthrowonnotimplemented.md b/api-documents/kit.asynccalloptions.dontthrowonnotimplemented.md new file mode 100644 index 0000000..01f491b --- /dev/null +++ b/api-documents/kit.asynccalloptions.dontthrowonnotimplemented.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AsyncCallOptions](./kit.asynccalloptions.md) > [dontThrowOnNotImplemented](./kit.asynccalloptions.dontthrowonnotimplemented.md) + +## AsyncCallOptions.dontThrowOnNotImplemented property + +Signature: + +```typescript +dontThrowOnNotImplemented: boolean; +``` diff --git a/api-documents/kit.asynccalloptions.key.md b/api-documents/kit.asynccalloptions.key.md new file mode 100644 index 0000000..1a69c5e --- /dev/null +++ b/api-documents/kit.asynccalloptions.key.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AsyncCallOptions](./kit.asynccalloptions.md) > [key](./kit.asynccalloptions.key.md) + +## AsyncCallOptions.key property + +Signature: + +```typescript +key: string; +``` diff --git a/api-documents/kit.asynccalloptions.md b/api-documents/kit.asynccalloptions.md new file mode 100644 index 0000000..ddbc1fe --- /dev/null +++ b/api-documents/kit.asynccalloptions.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AsyncCallOptions](./kit.asynccalloptions.md) + +## AsyncCallOptions interface + +Options for [AsyncCall()](./kit.asynccall.md) + +Signature: + +```typescript +export interface AsyncCallOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [dontThrowOnNotImplemented](./kit.asynccalloptions.dontthrowonnotimplemented.md) | boolean | | +| [key](./kit.asynccalloptions.key.md) | string | | +| [MessageCenter](./kit.asynccalloptions.messagecenter.md) | {
new (): {
on(event: string, cb: (data: any) => void): void;
send(event: string, data: any): void;
};
} | | +| [serializer](./kit.asynccalloptions.serializer.md) | Serialization | | +| [writeToConsole](./kit.asynccalloptions.writetoconsole.md) | boolean | | + diff --git a/api-documents/kit.asynccalloptions.messagecenter.md b/api-documents/kit.asynccalloptions.messagecenter.md new file mode 100644 index 0000000..75a2c4d --- /dev/null +++ b/api-documents/kit.asynccalloptions.messagecenter.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AsyncCallOptions](./kit.asynccalloptions.md) > [MessageCenter](./kit.asynccalloptions.messagecenter.md) + +## AsyncCallOptions.MessageCenter property + +Signature: + +```typescript +MessageCenter: { + new (): { + on(event: string, cb: (data: any) => void): void; + send(event: string, data: any): void; + }; + }; +``` diff --git a/api-documents/kit.asynccalloptions.serializer.md b/api-documents/kit.asynccalloptions.serializer.md new file mode 100644 index 0000000..e447818 --- /dev/null +++ b/api-documents/kit.asynccalloptions.serializer.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AsyncCallOptions](./kit.asynccalloptions.md) > [serializer](./kit.asynccalloptions.serializer.md) + +## AsyncCallOptions.serializer property + +Signature: + +```typescript +serializer: Serialization; +``` diff --git a/api-documents/kit.asynccalloptions.writetoconsole.md b/api-documents/kit.asynccalloptions.writetoconsole.md new file mode 100644 index 0000000..9576b79 --- /dev/null +++ b/api-documents/kit.asynccalloptions.writetoconsole.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AsyncCallOptions](./kit.asynccalloptions.md) > [writeToConsole](./kit.asynccalloptions.writetoconsole.md) + +## AsyncCallOptions.writeToConsole property + +Signature: + +```typescript +writeToConsole: boolean; +``` diff --git a/api-documents/kit.automatedtabtask.md b/api-documents/kit.automatedtabtask.md new file mode 100644 index 0000000..85387d9 --- /dev/null +++ b/api-documents/kit.automatedtabtask.md @@ -0,0 +1,47 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AutomatedTabTask](./kit.automatedtabtask.md) + +## AutomatedTabTask() function + +Open a new page in the background, execute some task, then close it automatically. + +Signature: + +```typescript +export declare function AutomatedTabTask Promise>>(taskImplements: T, options?: Partial): ((url: string, options?: Partial) => T) | null; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| taskImplements | T | All tasks that background page can call. | +| options | Partial<AutomatedTabTaskDefineTimeOptions> | Options | + +Returns: + +`((url: string, options?: Partial) => T) | null` + +## Example + +In content script: (You must run this in the page you wanted to run task in!) + +```ts +export const task = AutomatedTabTask({ + async taskA() { + return 'Done!' + }, +}) + +``` +In background script: + +Open https://example.com/ then run taskA() on that page, which will return 'Done!' + +```ts +import { task } from '...' +task('https://example.com/').taskA() + +``` + diff --git a/api-documents/kit.automatedtabtaskdefinetimeoptions.concurrent.md b/api-documents/kit.automatedtabtaskdefinetimeoptions.concurrent.md new file mode 100644 index 0000000..3b83014 --- /dev/null +++ b/api-documents/kit.automatedtabtaskdefinetimeoptions.concurrent.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AutomatedTabTaskDefineTimeOptions](./kit.automatedtabtaskdefinetimeoptions.md) > [concurrent](./kit.automatedtabtaskdefinetimeoptions.concurrent.md) + +## AutomatedTabTaskDefineTimeOptions.concurrent property + +At most run `concurrent` tasks. Prevents open too many tabs at the same time. + +Signature: + +```typescript +concurrent: number; +``` diff --git a/api-documents/kit.automatedtabtaskdefinetimeoptions.key.md b/api-documents/kit.automatedtabtaskdefinetimeoptions.key.md new file mode 100644 index 0000000..971a143 --- /dev/null +++ b/api-documents/kit.automatedtabtaskdefinetimeoptions.key.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AutomatedTabTaskDefineTimeOptions](./kit.automatedtabtaskdefinetimeoptions.md) > [key](./kit.automatedtabtaskdefinetimeoptions.key.md) + +## AutomatedTabTaskDefineTimeOptions.key property + +A unique key + +Signature: + +```typescript +key: string; +``` diff --git a/api-documents/kit.automatedtabtaskdefinetimeoptions.md b/api-documents/kit.automatedtabtaskdefinetimeoptions.md new file mode 100644 index 0000000..5536faa --- /dev/null +++ b/api-documents/kit.automatedtabtaskdefinetimeoptions.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AutomatedTabTaskDefineTimeOptions](./kit.automatedtabtaskdefinetimeoptions.md) + +## AutomatedTabTaskDefineTimeOptions interface + +Define-time options for [AutomatedTabTask()](./kit.automatedtabtask.md) + +Signature: + +```typescript +export interface AutomatedTabTaskDefineTimeOptions extends AutomatedTabTaskSharedOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [concurrent](./kit.automatedtabtaskdefinetimeoptions.concurrent.md) | number | At most run concurrent tasks. Prevents open too many tabs at the same time. | +| [key](./kit.automatedtabtaskdefinetimeoptions.key.md) | string | A unique key | +| [memorizeTTL](./kit.automatedtabtaskdefinetimeoptions.memorizettl.md) | number | TTL for memorize | + diff --git a/api-documents/kit.automatedtabtaskdefinetimeoptions.memorizettl.md b/api-documents/kit.automatedtabtaskdefinetimeoptions.memorizettl.md new file mode 100644 index 0000000..42344b8 --- /dev/null +++ b/api-documents/kit.automatedtabtaskdefinetimeoptions.memorizettl.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AutomatedTabTaskDefineTimeOptions](./kit.automatedtabtaskdefinetimeoptions.md) > [memorizeTTL](./kit.automatedtabtaskdefinetimeoptions.memorizettl.md) + +## AutomatedTabTaskDefineTimeOptions.memorizeTTL property + +TTL for memorize + +Signature: + +```typescript +memorizeTTL: number; +``` diff --git a/api-documents/kit.automatedtabtaskruntimeoptions.important.md b/api-documents/kit.automatedtabtaskruntimeoptions.important.md new file mode 100644 index 0000000..476f541 --- /dev/null +++ b/api-documents/kit.automatedtabtaskruntimeoptions.important.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AutomatedTabTaskRuntimeOptions](./kit.automatedtabtaskruntimeoptions.md) > [important](./kit.automatedtabtaskruntimeoptions.important.md) + +## AutomatedTabTaskRuntimeOptions.important property + +This task is important, need to start now without queue. + +Signature: + +```typescript +important: boolean; +``` diff --git a/api-documents/kit.automatedtabtaskruntimeoptions.md b/api-documents/kit.automatedtabtaskruntimeoptions.md new file mode 100644 index 0000000..1d7d7af --- /dev/null +++ b/api-documents/kit.automatedtabtaskruntimeoptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [AutomatedTabTaskRuntimeOptions](./kit.automatedtabtaskruntimeoptions.md) + +## AutomatedTabTaskRuntimeOptions interface + +Runtime options for [AutomatedTabTask()](./kit.automatedtabtask.md) + +Signature: + +```typescript +export interface AutomatedTabTaskRuntimeOptions extends AutomatedTabTaskSharedOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [important](./kit.automatedtabtaskruntimeoptions.important.md) | boolean | This task is important, need to start now without queue. | + diff --git a/api-documents/kit.contexts.md b/api-documents/kit.contexts.md new file mode 100644 index 0000000..45c3a99 --- /dev/null +++ b/api-documents/kit.contexts.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Contexts](./kit.contexts.md) + +## Contexts type + +All context that possible in when developing a WebExtension + +Signature: + +```typescript +export declare type Contexts = 'background' | 'content' | 'webpage' | 'unknown' | 'options' | 'debugging'; +``` diff --git a/api-documents/kit.domproxy.after.md b/api-documents/kit.domproxy.after.md new file mode 100644 index 0000000..102bf13 --- /dev/null +++ b/api-documents/kit.domproxy.after.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [after](./kit.domproxy.after.md) + +## DomProxy.after property + +Returns the `after` element, if it doesn't exist, create it implicitly. + +Signature: + +```typescript +readonly after: After; +``` diff --git a/api-documents/kit.domproxy.aftershadow.md b/api-documents/kit.domproxy.aftershadow.md new file mode 100644 index 0000000..95271d7 --- /dev/null +++ b/api-documents/kit.domproxy.aftershadow.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [afterShadow](./kit.domproxy.aftershadow.md) + +## DomProxy.afterShadow property + +Returns the `ShadowRoot` of the `after` element. + +Signature: + +```typescript +readonly afterShadow: ShadowRoot; +``` diff --git a/api-documents/kit.domproxy.before.md b/api-documents/kit.domproxy.before.md new file mode 100644 index 0000000..a4146cf --- /dev/null +++ b/api-documents/kit.domproxy.before.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [before](./kit.domproxy.before.md) + +## DomProxy.before property + +Returns the `before` element, if it doesn't exist, create it implicitly. + +Signature: + +```typescript +readonly before: Before; +``` diff --git a/api-documents/kit.domproxy.beforeshadow.md b/api-documents/kit.domproxy.beforeshadow.md new file mode 100644 index 0000000..680541a --- /dev/null +++ b/api-documents/kit.domproxy.beforeshadow.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [beforeShadow](./kit.domproxy.beforeshadow.md) + +## DomProxy.beforeShadow property + +Returns the `ShadowRoot` of the `before` element. + +Signature: + +```typescript +readonly beforeShadow: ShadowRoot; +``` diff --git a/api-documents/kit.domproxy.current.md b/api-documents/kit.domproxy.current.md new file mode 100644 index 0000000..0c62f40 --- /dev/null +++ b/api-documents/kit.domproxy.current.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [current](./kit.domproxy.current.md) + +## DomProxy.current property + +A proxy that always point to `realCurrent`, and if `realCurrent` changes, all action will be forwarded to new `realCurrent` + +Signature: + +```typescript +readonly current: ProxiedElement; +``` diff --git a/api-documents/kit.domproxy.destroy.md b/api-documents/kit.domproxy.destroy.md new file mode 100644 index 0000000..9b1f505 --- /dev/null +++ b/api-documents/kit.domproxy.destroy.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [destroy](./kit.domproxy.destroy.md) + +## DomProxy.destroy() method + +Destroy the DomProxy + +Signature: + +```typescript +destroy(): void; +``` +Returns: + +`void` + diff --git a/api-documents/kit.domproxy.md b/api-documents/kit.domproxy.md new file mode 100644 index 0000000..f4c233b --- /dev/null +++ b/api-documents/kit.domproxy.md @@ -0,0 +1,34 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) + +## DomProxy interface + +A DomProxy object + +Signature: + +```typescript +export interface DomProxy +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [after](./kit.domproxy.after.md) | After | Returns the after element, if it doesn't exist, create it implicitly. | +| [afterShadow](./kit.domproxy.aftershadow.md) | ShadowRoot | Returns the ShadowRoot of the after element. | +| [before](./kit.domproxy.before.md) | Before | Returns the before element, if it doesn't exist, create it implicitly. | +| [beforeShadow](./kit.domproxy.beforeshadow.md) | ShadowRoot | Returns the ShadowRoot of the before element. | +| [current](./kit.domproxy.current.md) | ProxiedElement | A proxy that always point to realCurrent, and if realCurrent changes, all action will be forwarded to new realCurrent | +| [observer](./kit.domproxy.observer.md) | {
readonly observer: MutationObserver | null;
callback: MutationCallback | undefined;
init: MutationObserverInit | undefined;
} | Observer for the current node. You need to set callback and init to activate it. | +| [realCurrent](./kit.domproxy.realcurrent.md) | ProxiedElement | null | The real current of the current | +| [weakAfter](./kit.domproxy.weakafter.md) | After | null | Returns the after element without implicitly create it. | +| [weakBefore](./kit.domproxy.weakbefore.md) | Before | null | Returns the before element without implicitly create it. | + +## Methods + +| Method | Description | +| --- | --- | +| [destroy()](./kit.domproxy.destroy.md) | Destroy the DomProxy | + diff --git a/api-documents/kit.domproxy.observer.md b/api-documents/kit.domproxy.observer.md new file mode 100644 index 0000000..2d98401 --- /dev/null +++ b/api-documents/kit.domproxy.observer.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [observer](./kit.domproxy.observer.md) + +## DomProxy.observer property + +Observer for the current node. You need to set callback and init to activate it. + +Signature: + +```typescript +readonly observer: { + readonly observer: MutationObserver | null; + callback: MutationCallback | undefined; + init: MutationObserverInit | undefined; + }; +``` diff --git a/api-documents/kit.domproxy.realcurrent.md b/api-documents/kit.domproxy.realcurrent.md new file mode 100644 index 0000000..148ddf5 --- /dev/null +++ b/api-documents/kit.domproxy.realcurrent.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [realCurrent](./kit.domproxy.realcurrent.md) + +## DomProxy.realCurrent property + +The real current of the `current` + +Signature: + +```typescript +realCurrent: ProxiedElement | null; +``` diff --git a/api-documents/kit.domproxy.weakafter.md b/api-documents/kit.domproxy.weakafter.md new file mode 100644 index 0000000..70341dc --- /dev/null +++ b/api-documents/kit.domproxy.weakafter.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [weakAfter](./kit.domproxy.weakafter.md) + +## DomProxy.weakAfter property + +Returns the `after` element without implicitly create it. + +Signature: + +```typescript +readonly weakAfter: After | null; +``` diff --git a/api-documents/kit.domproxy.weakbefore.md b/api-documents/kit.domproxy.weakbefore.md new file mode 100644 index 0000000..a72a14f --- /dev/null +++ b/api-documents/kit.domproxy.weakbefore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxy](./kit.domproxy.md) > [weakBefore](./kit.domproxy.weakbefore.md) + +## DomProxy.weakBefore property + +Returns the `before` element without implicitly create it. + +Signature: + +```typescript +readonly weakBefore: Before | null; +``` diff --git a/api-documents/kit.domproxyoptions.aftershadowrootinit.md b/api-documents/kit.domproxyoptions.aftershadowrootinit.md new file mode 100644 index 0000000..6ed1276 --- /dev/null +++ b/api-documents/kit.domproxyoptions.aftershadowrootinit.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxyOptions](./kit.domproxyoptions.md) > [afterShadowRootInit](./kit.domproxyoptions.aftershadowrootinit.md) + +## DomProxyOptions.afterShadowRootInit property + +ShadowRootInit for creating the shadow of `after` + +Signature: + +```typescript +afterShadowRootInit: ShadowRootInit; +``` diff --git a/api-documents/kit.domproxyoptions.beforeshadowrootinit.md b/api-documents/kit.domproxyoptions.beforeshadowrootinit.md new file mode 100644 index 0000000..43096a9 --- /dev/null +++ b/api-documents/kit.domproxyoptions.beforeshadowrootinit.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxyOptions](./kit.domproxyoptions.md) > [beforeShadowRootInit](./kit.domproxyoptions.beforeshadowrootinit.md) + +## DomProxyOptions.beforeShadowRootInit property + +ShadowRootInit for creating the shadow of `before` + +Signature: + +```typescript +beforeShadowRootInit: ShadowRootInit; +``` diff --git a/api-documents/kit.domproxyoptions.createafter.md b/api-documents/kit.domproxyoptions.createafter.md new file mode 100644 index 0000000..99379a5 --- /dev/null +++ b/api-documents/kit.domproxyoptions.createafter.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxyOptions](./kit.domproxyoptions.md) > [createAfter](./kit.domproxyoptions.createafter.md) + +## DomProxyOptions.createAfter() method + +Create the `after` node of the DomProxy + +Signature: + +```typescript +createAfter(): After; +``` +Returns: + +`After` + diff --git a/api-documents/kit.domproxyoptions.createbefore.md b/api-documents/kit.domproxyoptions.createbefore.md new file mode 100644 index 0000000..62ccdbe --- /dev/null +++ b/api-documents/kit.domproxyoptions.createbefore.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxyOptions](./kit.domproxyoptions.md) > [createBefore](./kit.domproxyoptions.createbefore.md) + +## DomProxyOptions.createBefore() method + +Create the `before` node of the DomProxy + +Signature: + +```typescript +createBefore(): Before; +``` +Returns: + +`Before` + diff --git a/api-documents/kit.domproxyoptions.md b/api-documents/kit.domproxyoptions.md new file mode 100644 index 0000000..f7a1693 --- /dev/null +++ b/api-documents/kit.domproxyoptions.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [DomProxyOptions](./kit.domproxyoptions.md) + +## DomProxyOptions interface + +Options for DomProxy + +Signature: + +```typescript +export interface DomProxyOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [afterShadowRootInit](./kit.domproxyoptions.aftershadowrootinit.md) | ShadowRootInit | ShadowRootInit for creating the shadow of after | +| [beforeShadowRootInit](./kit.domproxyoptions.beforeshadowrootinit.md) | ShadowRootInit | ShadowRootInit for creating the shadow of before | + +## Methods + +| Method | Description | +| --- | --- | +| [createAfter()](./kit.domproxyoptions.createafter.md) | Create the after node of the DomProxy | +| [createBefore()](./kit.domproxyoptions.createbefore.md) | Create the before node of the DomProxy | + diff --git a/api-documents/kit.eventwatcher.eventlistener.md b/api-documents/kit.eventwatcher.eventlistener.md new file mode 100644 index 0000000..287babc --- /dev/null +++ b/api-documents/kit.eventwatcher.eventlistener.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [EventWatcher](./kit.eventwatcher.md) > [eventListener](./kit.eventwatcher.eventlistener.md) + +## EventWatcher.eventListener property + +Use this function as event listener to invoke watcher. + +Signature: + +```typescript +eventListener: () => void; +``` diff --git a/api-documents/kit.eventwatcher.md b/api-documents/kit.eventwatcher.md new file mode 100644 index 0000000..cbc5e17 --- /dev/null +++ b/api-documents/kit.eventwatcher.md @@ -0,0 +1,36 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [EventWatcher](./kit.eventwatcher.md) + +## EventWatcher class + +A Watcher based on event handlers. + +Signature: + +```typescript +export declare class EventWatcher extends Watcher +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [eventListener](./kit.eventwatcher.eventlistener.md) | | () => void | Use this function as event listener to invoke watcher. | +| [watching](./kit.eventwatcher.watching.md) | | boolean | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [startWatch()](./kit.eventwatcher.startwatch.md) | | | + +## Example + + +```ts +const e = new EventWatcher(ls) +document.addEventListener('event', e.eventListener) + +``` + diff --git a/api-documents/kit.eventwatcher.startwatch.md b/api-documents/kit.eventwatcher.startwatch.md new file mode 100644 index 0000000..c29481c --- /dev/null +++ b/api-documents/kit.eventwatcher.startwatch.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [EventWatcher](./kit.eventwatcher.md) > [startWatch](./kit.eventwatcher.startwatch.md) + +## EventWatcher.startWatch() method + +Signature: + +```typescript +startWatch(): this; +``` +Returns: + +`this` + diff --git a/api-documents/kit.eventwatcher.watching.md b/api-documents/kit.eventwatcher.watching.md new file mode 100644 index 0000000..9f271b3 --- /dev/null +++ b/api-documents/kit.eventwatcher.watching.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [EventWatcher](./kit.eventwatcher.md) > [watching](./kit.eventwatcher.watching.md) + +## EventWatcher.watching property + +Signature: + +```typescript +protected watching: boolean; +``` diff --git a/api-documents/kit.getcontext.md b/api-documents/kit.getcontext.md new file mode 100644 index 0000000..ae394ea --- /dev/null +++ b/api-documents/kit.getcontext.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [GetContext](./kit.getcontext.md) + +## GetContext() function + +Get current running context. + +Signature: + +```typescript +export declare function GetContext(): Contexts; +``` +Returns: + +`Contexts` + +## Remarks + +- background: background script - content: content script - webpage: a normal webpage - unknown: unknown context + diff --git a/api-documents/kit.intervalwatcher.md b/api-documents/kit.intervalwatcher.md new file mode 100644 index 0000000..9f07963 --- /dev/null +++ b/api-documents/kit.intervalwatcher.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [IntervalWatcher](./kit.intervalwatcher.md) + +## IntervalWatcher class + +A watcher based on time interval. + +Signature: + +```typescript +export declare class IntervalWatcher extends Watcher +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [startWatch(interval)](./kit.intervalwatcher.startwatch.md) | | Start to watch the LiveSelector at a interval(ms). | +| [stopWatch()](./kit.intervalwatcher.stopwatch.md) | | | + diff --git a/api-documents/kit.intervalwatcher.startwatch.md b/api-documents/kit.intervalwatcher.startwatch.md new file mode 100644 index 0000000..4e28b36 --- /dev/null +++ b/api-documents/kit.intervalwatcher.startwatch.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [IntervalWatcher](./kit.intervalwatcher.md) > [startWatch](./kit.intervalwatcher.startwatch.md) + +## IntervalWatcher.startWatch() method + +Start to watch the LiveSelector at a interval(ms). + +Signature: + +```typescript +startWatch(interval: number): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| interval | number | | + +Returns: + +`this` + diff --git a/api-documents/kit.intervalwatcher.stopwatch.md b/api-documents/kit.intervalwatcher.stopwatch.md new file mode 100644 index 0000000..50eba35 --- /dev/null +++ b/api-documents/kit.intervalwatcher.stopwatch.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [IntervalWatcher](./kit.intervalwatcher.md) > [stopWatch](./kit.intervalwatcher.stopwatch.md) + +## IntervalWatcher.stopWatch() method + +Signature: + +```typescript +stopWatch(): void; +``` +Returns: + +`void` + diff --git a/api-documents/kit.jsonserialization.md b/api-documents/kit.jsonserialization.md new file mode 100644 index 0000000..521e47e --- /dev/null +++ b/api-documents/kit.jsonserialization.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [JSONSerialization](./kit.jsonserialization.md) + +## JSONSerialization variable + +Serialization implementation by JSON.parse/stringify + +Signature: + +```typescript +JSONSerialization: (replacer?: ((this: any, key: string, value: any) => any) | undefined) => Serialization +``` diff --git a/api-documents/kit.liveselector.clone.md b/api-documents/kit.liveselector.clone.md new file mode 100644 index 0000000..7104164 --- /dev/null +++ b/api-documents/kit.liveselector.clone.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [clone](./kit.liveselector.clone.md) + +## LiveSelector.clone() method + +Clone this LiveSelector and return a new LiveSelector. + +Signature: + +```typescript +clone(): LiveSelector; +``` +Returns: + +`LiveSelector` + +a new LiveSelector with same action + +## Example + + +```ts +ls.clone() + +``` + diff --git a/api-documents/kit.liveselector.closest.md b/api-documents/kit.liveselector.closest.md new file mode 100644 index 0000000..b012bfd --- /dev/null +++ b/api-documents/kit.liveselector.closest.md @@ -0,0 +1,33 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [closest](./kit.liveselector.closest.md) + +## LiveSelector.closest() method + +Reversely select element in the parent + +Signature: + +```typescript +closest(parentOfNth: number): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| parentOfNth | number | | + +Returns: + +`LiveSelector` + +## Example + + +```ts +ls.closest('div') +ls.closest(2) // parentElement.parentElement + +``` + diff --git a/api-documents/kit.liveselector.closest_1.md b/api-documents/kit.liveselector.closest_1.md new file mode 100644 index 0000000..eeae54a --- /dev/null +++ b/api-documents/kit.liveselector.closest_1.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [closest](./kit.liveselector.closest_1.md) + +## LiveSelector.closest() method + +Signature: + +```typescript +closest(selectors: K): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selectors | K | | + +Returns: + +`LiveSelector` + diff --git a/api-documents/kit.liveselector.closest_2.md b/api-documents/kit.liveselector.closest_2.md new file mode 100644 index 0000000..56be45e --- /dev/null +++ b/api-documents/kit.liveselector.closest_2.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [closest](./kit.liveselector.closest_2.md) + +## LiveSelector.closest() method + +Signature: + +```typescript +closest(selectors: K): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selectors | K | | + +Returns: + +`LiveSelector` + diff --git a/api-documents/kit.liveselector.closest_3.md b/api-documents/kit.liveselector.closest_3.md new file mode 100644 index 0000000..64f1d64 --- /dev/null +++ b/api-documents/kit.liveselector.closest_3.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [closest](./kit.liveselector.closest_3.md) + +## LiveSelector.closest() method + +Signature: + +```typescript +closest(selectors: string): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selectors | string | | + +Returns: + +`LiveSelector` + diff --git a/api-documents/kit.liveselector.concat.md b/api-documents/kit.liveselector.concat.md new file mode 100644 index 0000000..4bde930 --- /dev/null +++ b/api-documents/kit.liveselector.concat.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [concat](./kit.liveselector.concat.md) + +## LiveSelector.concat() method + +Combines two LiveSelector. + +Signature: + +```typescript +concat(newEle: LiveSelector): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| newEle | LiveSelector<NextType> | Additional LiveSelector to combine. | + +Returns: + +`LiveSelector` + +## Example + + +```ts +ls.concat(new LiveSelector().querySelector('#root')) + +``` + diff --git a/api-documents/kit.liveselector.evaluateonce.md b/api-documents/kit.liveselector.evaluateonce.md new file mode 100644 index 0000000..c5b2584 --- /dev/null +++ b/api-documents/kit.liveselector.evaluateonce.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [evaluateOnce](./kit.liveselector.evaluateonce.md) + +## LiveSelector.evaluateOnce() method + +Evaluate selector expression + +Signature: + +```typescript +evaluateOnce(): T[]; +``` +Returns: + +`T[]` + diff --git a/api-documents/kit.liveselector.filter.md b/api-documents/kit.liveselector.filter.md new file mode 100644 index 0000000..f933011 --- /dev/null +++ b/api-documents/kit.liveselector.filter.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [filter](./kit.liveselector.filter.md) + +## LiveSelector.filter() method + +Select the elements of a LiveSelector that meet the condition specified in a callback function. + +Signature: + +```typescript +filter(f: (value: T, index: number, array: T[]) => any): LiveSelector>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| f | (value: T, index: number, array: T[]) => any | The filter method | + +Returns: + +`LiveSelector>` + +## Example + + +```ts +ls.filter(x => x.innerText.match('hello')) + +``` + diff --git a/api-documents/kit.liveselector.flat.md b/api-documents/kit.liveselector.flat.md new file mode 100644 index 0000000..7d3396a --- /dev/null +++ b/api-documents/kit.liveselector.flat.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [flat](./kit.liveselector.flat.md) + +## LiveSelector.flat() method + +Flat T\[\]\[\] to T\[\] + +Signature: + +```typescript +flat(): LiveSelector ? U : never>; +``` +Returns: + +`LiveSelector ? U : never>` + +## Example + + +```ts +ls.flat() + +``` + diff --git a/api-documents/kit.liveselector.getelementsbyclassname.md b/api-documents/kit.liveselector.getelementsbyclassname.md new file mode 100644 index 0000000..767c342 --- /dev/null +++ b/api-documents/kit.liveselector.getelementsbyclassname.md @@ -0,0 +1,33 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [getElementsByClassName](./kit.liveselector.getelementsbyclassname.md) + +## LiveSelector.getElementsByClassName() method + +Select all element base on the current result. + +Signature: + +```typescript +getElementsByClassName(className: string): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| className | string | Class name | + +Returns: + +`LiveSelector` + +## Example + +Equal to ls.querySelectorAll('.a .b') + +```ts +ls.getElementsByClassName('a').getElementsByClassName('b') + +``` + diff --git a/api-documents/kit.liveselector.getelementsbytagname.md b/api-documents/kit.liveselector.getelementsbytagname.md new file mode 100644 index 0000000..d06716e --- /dev/null +++ b/api-documents/kit.liveselector.getelementsbytagname.md @@ -0,0 +1,33 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [getElementsByTagName](./kit.liveselector.getelementsbytagname.md) + +## LiveSelector.getElementsByTagName() method + +Select all element base on the current result. + +Signature: + +```typescript +getElementsByTagName(tag: K): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| tag | K | Tag name | + +Returns: + +`LiveSelector` + +## Example + +Equal to ls.querySelectorAll('a b') + +```ts +ls.getElementsByTagName('a').getElementsByTagName('b') + +``` + diff --git a/api-documents/kit.liveselector.getelementsbytagname_1.md b/api-documents/kit.liveselector.getelementsbytagname_1.md new file mode 100644 index 0000000..9c8e049 --- /dev/null +++ b/api-documents/kit.liveselector.getelementsbytagname_1.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [getElementsByTagName](./kit.liveselector.getelementsbytagname_1.md) + +## LiveSelector.getElementsByTagName() method + +Signature: + +```typescript +getElementsByTagName(tag: K): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| tag | K | | + +Returns: + +`LiveSelector` + diff --git a/api-documents/kit.liveselector.getelementsbytagname_2.md b/api-documents/kit.liveselector.getelementsbytagname_2.md new file mode 100644 index 0000000..c17bbf3 --- /dev/null +++ b/api-documents/kit.liveselector.getelementsbytagname_2.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [getElementsByTagName](./kit.liveselector.getelementsbytagname_2.md) + +## LiveSelector.getElementsByTagName() method + +Signature: + +```typescript +getElementsByTagName(tag: string): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| tag | string | | + +Returns: + +`LiveSelector` + diff --git a/api-documents/kit.liveselector.map.md b/api-documents/kit.liveselector.map.md new file mode 100644 index 0000000..265a773 --- /dev/null +++ b/api-documents/kit.liveselector.map.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [map](./kit.liveselector.map.md) + +## LiveSelector.map() method + +Calls a defined callback function on each element of a LiveSelector, and continues with the results. + +Signature: + +```typescript +map(callbackfn: (element: T) => NextType): LiveSelector>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| callbackfn | (element: T) => NextType | Map function | + +Returns: + +`LiveSelector>` + +## Example + + +```ts +ls.map(x => x.parentElement) + +``` + diff --git a/api-documents/kit.liveselector.md b/api-documents/kit.liveselector.md new file mode 100644 index 0000000..6e810e0 --- /dev/null +++ b/api-documents/kit.liveselector.md @@ -0,0 +1,48 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) + +## LiveSelector class + +Create a live selector that can continuously select the element you want. + +Signature: + +```typescript +export declare class LiveSelector +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [clone()](./kit.liveselector.clone.md) | | Clone this LiveSelector and return a new LiveSelector. | +| [closest(parentOfNth)](./kit.liveselector.closest.md) | | Reversely select element in the parent | +| [closest(selectors)](./kit.liveselector.closest_1.md) | | | +| [closest(selectors)](./kit.liveselector.closest_2.md) | | | +| [closest(selectors)](./kit.liveselector.closest_3.md) | | | +| [concat(newEle)](./kit.liveselector.concat.md) | | Combines two LiveSelector. | +| [evaluateOnce()](./kit.liveselector.evaluateonce.md) | | Evaluate selector expression | +| [filter(f)](./kit.liveselector.filter.md) | | Select the elements of a LiveSelector that meet the condition specified in a callback function. | +| [flat()](./kit.liveselector.flat.md) | | Flat T\[\]\[\] to T\[\] | +| [getElementsByClassName(className)](./kit.liveselector.getelementsbyclassname.md) | | Select all element base on the current result. | +| [getElementsByTagName(tag)](./kit.liveselector.getelementsbytagname.md) | | Select all element base on the current result. | +| [getElementsByTagName(tag)](./kit.liveselector.getelementsbytagname_1.md) | | | +| [getElementsByTagName(tag)](./kit.liveselector.getelementsbytagname_2.md) | | | +| [map(callbackfn)](./kit.liveselector.map.md) | | Calls a defined callback function on each element of a LiveSelector, and continues with the results. | +| [nth(n)](./kit.liveselector.nth.md) | | Select only nth element | +| [querySelector(selector)](./kit.liveselector.queryselector.md) | | Select the first element that is a descendant of node that matches selectors. | +| [querySelector(selector)](./kit.liveselector.queryselector_1.md) | | | +| [querySelector(selector)](./kit.liveselector.queryselector_2.md) | | | +| [querySelectorAll(selector)](./kit.liveselector.queryselectorall.md) | | Select all element descendants of node that match selectors. | +| [querySelectorAll(selector)](./kit.liveselector.queryselectorall_1.md) | | | +| [querySelectorAll(selector)](./kit.liveselector.queryselectorall_2.md) | | | +| [replace(f)](./kit.liveselector.replace.md) | | Replace the whole array. | +| [reverse()](./kit.liveselector.reverse.md) | | Reverses the elements in an Array. | +| [slice(start, end)](./kit.liveselector.slice.md) | | Returns a section of an array. | +| [sort(compareFn)](./kit.liveselector.sort.md) | | Sorts an array. | + +## Remarks + +Call [\#evaluateOnce](./kit.liveselector.evaluateonce.md) to evaluate the element. Falsy value will be ignored. + diff --git a/api-documents/kit.liveselector.nth.md b/api-documents/kit.liveselector.nth.md new file mode 100644 index 0000000..a5205bf --- /dev/null +++ b/api-documents/kit.liveselector.nth.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [nth](./kit.liveselector.nth.md) + +## LiveSelector.nth() method + +Select only nth element + +Signature: + +```typescript +nth(n: number): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| n | number | Select only nth element, allow negative number. | + +Returns: + +`LiveSelector` + +## Example + + +```ts +ls.nth(-1) + +``` + diff --git a/api-documents/kit.liveselector.queryselector.md b/api-documents/kit.liveselector.queryselector.md new file mode 100644 index 0000000..1cdb51e --- /dev/null +++ b/api-documents/kit.liveselector.queryselector.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [querySelector](./kit.liveselector.queryselector.md) + +## LiveSelector.querySelector() method + +Select the first element that is a descendant of node that matches selectors. + +Signature: + +```typescript +querySelector(selector: K): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selector | K | Selector | + +Returns: + +`LiveSelector` + +## Example + + +```ts +ls.querySelector('div#root') + +``` + diff --git a/api-documents/kit.liveselector.queryselector_1.md b/api-documents/kit.liveselector.queryselector_1.md new file mode 100644 index 0000000..94314bc --- /dev/null +++ b/api-documents/kit.liveselector.queryselector_1.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [querySelector](./kit.liveselector.queryselector_1.md) + +## LiveSelector.querySelector() method + +Signature: + +```typescript +querySelector(selector: K): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selector | K | | + +Returns: + +`LiveSelector` + diff --git a/api-documents/kit.liveselector.queryselector_2.md b/api-documents/kit.liveselector.queryselector_2.md new file mode 100644 index 0000000..866fd5b --- /dev/null +++ b/api-documents/kit.liveselector.queryselector_2.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [querySelector](./kit.liveselector.queryselector_2.md) + +## LiveSelector.querySelector() method + +Signature: + +```typescript +querySelector(selector: string): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selector | string | | + +Returns: + +`LiveSelector` + diff --git a/api-documents/kit.liveselector.queryselectorall.md b/api-documents/kit.liveselector.queryselectorall.md new file mode 100644 index 0000000..57b68ac --- /dev/null +++ b/api-documents/kit.liveselector.queryselectorall.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [querySelectorAll](./kit.liveselector.queryselectorall.md) + +## LiveSelector.querySelectorAll() method + +Select all element descendants of node that match selectors. + +Signature: + +```typescript +querySelectorAll(selector: K): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selector | K | Selector | + +Returns: + +`LiveSelector` + +## Example + + +```ts +ls.querySelector('div > div') + +``` + diff --git a/api-documents/kit.liveselector.queryselectorall_1.md b/api-documents/kit.liveselector.queryselectorall_1.md new file mode 100644 index 0000000..8fefa5b --- /dev/null +++ b/api-documents/kit.liveselector.queryselectorall_1.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [querySelectorAll](./kit.liveselector.queryselectorall_1.md) + +## LiveSelector.querySelectorAll() method + +Signature: + +```typescript +querySelectorAll(selector: K): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selector | K | | + +Returns: + +`LiveSelector` + diff --git a/api-documents/kit.liveselector.queryselectorall_2.md b/api-documents/kit.liveselector.queryselectorall_2.md new file mode 100644 index 0000000..5ec1f07 --- /dev/null +++ b/api-documents/kit.liveselector.queryselectorall_2.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [querySelectorAll](./kit.liveselector.queryselectorall_2.md) + +## LiveSelector.querySelectorAll() method + +Signature: + +```typescript +querySelectorAll(selector: string): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selector | string | | + +Returns: + +`LiveSelector` + diff --git a/api-documents/kit.liveselector.replace.md b/api-documents/kit.liveselector.replace.md new file mode 100644 index 0000000..b586b90 --- /dev/null +++ b/api-documents/kit.liveselector.replace.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [replace](./kit.liveselector.replace.md) + +## LiveSelector.replace() method + +Replace the whole array. + +Signature: + +```typescript +replace(f: (arr: T[]) => NextType[]): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| f | (arr: T[]) => NextType[] | returns new array. | + +Returns: + +`LiveSelector` + +## Example + + +```ts +ls.replace(x => lodash.dropRight(x, 2)) + +``` + diff --git a/api-documents/kit.liveselector.reverse.md b/api-documents/kit.liveselector.reverse.md new file mode 100644 index 0000000..0048b86 --- /dev/null +++ b/api-documents/kit.liveselector.reverse.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [reverse](./kit.liveselector.reverse.md) + +## LiveSelector.reverse() method + +Reverses the elements in an Array. + +Signature: + +```typescript +reverse(): LiveSelector; +``` +Returns: + +`LiveSelector` + +## Example + + +```ts +ls.reverse() + +``` + diff --git a/api-documents/kit.liveselector.slice.md b/api-documents/kit.liveselector.slice.md new file mode 100644 index 0000000..a8e8980 --- /dev/null +++ b/api-documents/kit.liveselector.slice.md @@ -0,0 +1,33 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [slice](./kit.liveselector.slice.md) + +## LiveSelector.slice() method + +Returns a section of an array. + +Signature: + +```typescript +slice(start?: number, end?: number): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| start | number | The beginning of the specified portion of the array. | +| end | number | The end of the specified portion of the array. | + +Returns: + +`LiveSelector` + +## Example + + +```ts +ls.slice(2, 4) + +``` + diff --git a/api-documents/kit.liveselector.sort.md b/api-documents/kit.liveselector.sort.md new file mode 100644 index 0000000..2f4c338 --- /dev/null +++ b/api-documents/kit.liveselector.sort.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [LiveSelector](./kit.liveselector.md) > [sort](./kit.liveselector.sort.md) + +## LiveSelector.sort() method + +Sorts an array. + +Signature: + +```typescript +sort(compareFn?: (a: T, b: T) => number): LiveSelector; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| compareFn | (a: T, b: T) => number | The name of the function used to determine the order of the elements. If omitted, the elements are sorted in ascending, ASCII character order. | + +Returns: + +`LiveSelector` + +## Example + + +```ts +ls.sort((a, b) => a.innerText.length - b.innerText.length) + +``` + diff --git a/api-documents/kit.md b/api-documents/kit.md new file mode 100644 index 0000000..28e62a8 --- /dev/null +++ b/api-documents/kit.md @@ -0,0 +1,55 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) + +## kit package + +A toolkit for browser extension developing. + +## Classes + +| Class | Description | +| --- | --- | +| [EventWatcher](./kit.eventwatcher.md) | A Watcher based on event handlers. | +| [IntervalWatcher](./kit.intervalwatcher.md) | A watcher based on time interval. | +| [LiveSelector](./kit.liveselector.md) | Create a live selector that can continuously select the element you want. | +| [MessageCenter](./kit.messagecenter.md) | Send and receive messages in different contexts. | +| [MutationObserverWatcher](./kit.mutationobserverwatcher.md) | A watcher based on MutationObserver | +| [ValueRef](./kit.valueref.md) | A ref object with addListener | +| [Watcher](./kit.watcher.md) | Use LiveSelector to watch dom changeYou need to implement startWatch | + +## Functions + +| Function | Description | +| --- | --- | +| [AsyncCall(implementation, options)](./kit.asynccall.md) | Async call between different context. | +| [AutomatedTabTask(taskImplements, options)](./kit.automatedtabtask.md) | Open a new page in the background, execute some task, then close it automatically. | +| [DomProxy(options)](./kit.domproxy.md) | DomProxy provide an interface that be stable even dom is changed. | +| [GetContext()](./kit.getcontext.md) | Get current running context. | +| [OnlyRunInContext(context, name)](./kit.onlyrunincontext.md) | Make sure this file only run in wanted context | +| [OnlyRunInContext(context, throws)](./kit.onlyrunincontext_1.md) | | + +## Interfaces + +| Interface | Description | +| --- | --- | +| [AsyncCallOptions](./kit.asynccalloptions.md) | Options for [AsyncCall()](./kit.asynccall.md) | +| [AutomatedTabTaskDefineTimeOptions](./kit.automatedtabtaskdefinetimeoptions.md) | Define-time options for [AutomatedTabTask()](./kit.automatedtabtask.md) | +| [AutomatedTabTaskRuntimeOptions](./kit.automatedtabtaskruntimeoptions.md) | Runtime options for [AutomatedTabTask()](./kit.automatedtabtask.md) | +| [DomProxy](./kit.domproxy.md) | A DomProxy object | +| [DomProxyOptions](./kit.domproxyoptions.md) | Options for DomProxy | +| [Serialization](./kit.serialization.md) | Define how to do serialization and deserialization of remote procedure call | + +## Variables + +| Variable | Description | +| --- | --- | +| [JSONSerialization](./kit.jsonserialization.md) | Serialization implementation by JSON.parse/stringify | +| [NoSerialization](./kit.noserialization.md) | Serialization implementation that do nothing | + +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [Contexts](./kit.contexts.md) | All context that possible in when developing a WebExtension | + diff --git a/api-documents/kit.messagecenter.md b/api-documents/kit.messagecenter.md new file mode 100644 index 0000000..0901dbe --- /dev/null +++ b/api-documents/kit.messagecenter.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [MessageCenter](./kit.messagecenter.md) + +## MessageCenter class + +Send and receive messages in different contexts. + +Signature: + +```typescript +export declare class MessageCenter +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [writeToConsole](./kit.messagecenter.writetoconsole.md) | | boolean | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [on(event, handler)](./kit.messagecenter.on.md) | | Listen to an event | +| [send(key, data, alsoSendToDocument)](./kit.messagecenter.send.md) | | Send message to local or other instance of extension | + diff --git a/api-documents/kit.messagecenter.on.md b/api-documents/kit.messagecenter.on.md new file mode 100644 index 0000000..82056e8 --- /dev/null +++ b/api-documents/kit.messagecenter.on.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [MessageCenter](./kit.messagecenter.md) > [on](./kit.messagecenter.on.md) + +## MessageCenter.on() method + +Listen to an event + +Signature: + +```typescript +on(event: Key, handler: (data: ITypedMessages[Key]) => void): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | Key | Name of the event | +| handler | (data: ITypedMessages[Key]) => void | Handler of the event | + +Returns: + +`void` + diff --git a/api-documents/kit.messagecenter.send.md b/api-documents/kit.messagecenter.send.md new file mode 100644 index 0000000..10c6d1b --- /dev/null +++ b/api-documents/kit.messagecenter.send.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [MessageCenter](./kit.messagecenter.md) > [send](./kit.messagecenter.send.md) + +## MessageCenter.send() method + +Send message to local or other instance of extension + +Signature: + +```typescript +send(key: Key, data: ITypedMessages[Key], alsoSendToDocument?: boolean): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| key | Key | Key of the message | +| data | ITypedMessages[Key] | Data of the message | +| alsoSendToDocument | boolean | ! Send message to document. This may leaks secret! Only open in localhost! | + +Returns: + +`void` + diff --git a/api-documents/kit.messagecenter.writetoconsole.md b/api-documents/kit.messagecenter.writetoconsole.md new file mode 100644 index 0000000..081cdc0 --- /dev/null +++ b/api-documents/kit.messagecenter.writetoconsole.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [MessageCenter](./kit.messagecenter.md) > [writeToConsole](./kit.messagecenter.writetoconsole.md) + +## MessageCenter.writeToConsole property + +Signature: + +```typescript +writeToConsole: boolean; +``` diff --git a/api-documents/kit.mutationobserverwatcher.liveselector.md b/api-documents/kit.mutationobserverwatcher.liveselector.md new file mode 100644 index 0000000..7c37a54 --- /dev/null +++ b/api-documents/kit.mutationobserverwatcher.liveselector.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [MutationObserverWatcher](./kit.mutationobserverwatcher.md) > [liveSelector](./kit.mutationobserverwatcher.liveselector.md) + +## MutationObserverWatcher.liveSelector property + +Signature: + +```typescript +protected liveSelector: LiveSelector; +``` diff --git a/api-documents/kit.mutationobserverwatcher.md b/api-documents/kit.mutationobserverwatcher.md new file mode 100644 index 0000000..65a4bae --- /dev/null +++ b/api-documents/kit.mutationobserverwatcher.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [MutationObserverWatcher](./kit.mutationobserverwatcher.md) + +## MutationObserverWatcher class + +A watcher based on MutationObserver + +Signature: + +```typescript +export declare class MutationObserverWatcher extends Watcher +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [liveSelector](./kit.mutationobserverwatcher.liveselector.md) | | LiveSelector<T> | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [startWatch(options)](./kit.mutationobserverwatcher.startwatch.md) | | | +| [stopWatch()](./kit.mutationobserverwatcher.stopwatch.md) | | | + diff --git a/api-documents/kit.mutationobserverwatcher.startwatch.md b/api-documents/kit.mutationobserverwatcher.startwatch.md new file mode 100644 index 0000000..7fc575c --- /dev/null +++ b/api-documents/kit.mutationobserverwatcher.startwatch.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [MutationObserverWatcher](./kit.mutationobserverwatcher.md) > [startWatch](./kit.mutationobserverwatcher.startwatch.md) + +## MutationObserverWatcher.startWatch() method + +Signature: + +```typescript +startWatch(options?: MutationObserverInit): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | MutationObserverInit | | + +Returns: + +`this` + diff --git a/api-documents/kit.mutationobserverwatcher.stopwatch.md b/api-documents/kit.mutationobserverwatcher.stopwatch.md new file mode 100644 index 0000000..7c789b4 --- /dev/null +++ b/api-documents/kit.mutationobserverwatcher.stopwatch.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [MutationObserverWatcher](./kit.mutationobserverwatcher.md) > [stopWatch](./kit.mutationobserverwatcher.stopwatch.md) + +## MutationObserverWatcher.stopWatch() method + +Signature: + +```typescript +stopWatch(): void; +``` +Returns: + +`void` + diff --git a/api-documents/kit.noserialization.md b/api-documents/kit.noserialization.md new file mode 100644 index 0000000..2419b28 --- /dev/null +++ b/api-documents/kit.noserialization.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [NoSerialization](./kit.noserialization.md) + +## NoSerialization variable + +Serialization implementation that do nothing + +Signature: + +```typescript +NoSerialization: Serialization +``` diff --git a/api-documents/kit.onlyrunincontext.md b/api-documents/kit.onlyrunincontext.md new file mode 100644 index 0000000..04e1a4c --- /dev/null +++ b/api-documents/kit.onlyrunincontext.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [OnlyRunInContext](./kit.onlyrunincontext.md) + +## OnlyRunInContext() function + +Make sure this file only run in wanted context + +Signature: + +```typescript +export declare function OnlyRunInContext(context: Contexts | Contexts[], name: string): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| context | Contexts | Contexts[] | Wanted context or contexts | +| name | string | name to throw | + +Returns: + +`void` + diff --git a/api-documents/kit.onlyrunincontext_1.md b/api-documents/kit.onlyrunincontext_1.md new file mode 100644 index 0000000..04c6b54 --- /dev/null +++ b/api-documents/kit.onlyrunincontext_1.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [OnlyRunInContext](./kit.onlyrunincontext_1.md) + +## OnlyRunInContext() function + +Signature: + +```typescript +export declare function OnlyRunInContext(context: Contexts | Contexts[], throws: false): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| context | Contexts | Contexts[] | | +| throws | false | | + +Returns: + +`boolean` + diff --git a/api-documents/kit.serialization.deserialization.md b/api-documents/kit.serialization.deserialization.md new file mode 100644 index 0000000..3220edb --- /dev/null +++ b/api-documents/kit.serialization.deserialization.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Serialization](./kit.serialization.md) > [deserialization](./kit.serialization.deserialization.md) + +## Serialization.deserialization() method + +Signature: + +```typescript +deserialization(serialized: unknown): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| serialized | unknown | | + +Returns: + +`Promise` + diff --git a/api-documents/kit.serialization.md b/api-documents/kit.serialization.md new file mode 100644 index 0000000..1418ed5 --- /dev/null +++ b/api-documents/kit.serialization.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Serialization](./kit.serialization.md) + +## Serialization interface + +Define how to do serialization and deserialization of remote procedure call + +Signature: + +```typescript +export interface Serialization +``` + +## Methods + +| Method | Description | +| --- | --- | +| [deserialization(serialized)](./kit.serialization.deserialization.md) | | +| [serialization(from)](./kit.serialization.serialization.md) | | + diff --git a/api-documents/kit.serialization.serialization.md b/api-documents/kit.serialization.serialization.md new file mode 100644 index 0000000..70c5c8a --- /dev/null +++ b/api-documents/kit.serialization.serialization.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Serialization](./kit.serialization.md) > [serialization](./kit.serialization.serialization.md) + +## Serialization.serialization() method + +Signature: + +```typescript +serialization(from: any): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| from | any | | + +Returns: + +`Promise` + diff --git a/api-documents/kit.valueref.addlistener.md b/api-documents/kit.valueref.addlistener.md new file mode 100644 index 0000000..b759682 --- /dev/null +++ b/api-documents/kit.valueref.addlistener.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [ValueRef](./kit.valueref.md) > [addListener](./kit.valueref.addlistener.md) + +## ValueRef.addListener() method + +Add a listener. This will return a remover. + +Signature: + +```typescript +addListener(fn: Fn): () => void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fn | Fn<T> | | + +Returns: + +`() => void` + +## Example + + +```ts +React.useEffect(() => ref.addListener(() => {...})) + +``` + diff --git a/api-documents/kit.valueref.md b/api-documents/kit.valueref.md new file mode 100644 index 0000000..797cd7c --- /dev/null +++ b/api-documents/kit.valueref.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [ValueRef](./kit.valueref.md) + +## ValueRef class + +A `ref` object with `addListener` + +Signature: + +```typescript +export declare class ValueRef +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [value](./kit.valueref.value.md) | | T | Set current value of a ValueRef | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [addListener(fn)](./kit.valueref.addlistener.md) | | Add a listener. This will return a remover. | +| [removeAllListener()](./kit.valueref.removealllistener.md) | | Remove all listeners | +| [removeListener(fn)](./kit.valueref.removelistener.md) | | Remove a listener | + diff --git a/api-documents/kit.valueref.removealllistener.md b/api-documents/kit.valueref.removealllistener.md new file mode 100644 index 0000000..a54c66a --- /dev/null +++ b/api-documents/kit.valueref.removealllistener.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [ValueRef](./kit.valueref.md) > [removeAllListener](./kit.valueref.removealllistener.md) + +## ValueRef.removeAllListener() method + +Remove all listeners + +Signature: + +```typescript +removeAllListener(): void; +``` +Returns: + +`void` + diff --git a/api-documents/kit.valueref.removelistener.md b/api-documents/kit.valueref.removelistener.md new file mode 100644 index 0000000..963abcf --- /dev/null +++ b/api-documents/kit.valueref.removelistener.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [ValueRef](./kit.valueref.md) > [removeListener](./kit.valueref.removelistener.md) + +## ValueRef.removeListener() method + +Remove a listener + +Signature: + +```typescript +removeListener(fn: Fn): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fn | Fn<T> | | + +Returns: + +`void` + diff --git a/api-documents/kit.valueref.value.md b/api-documents/kit.valueref.value.md new file mode 100644 index 0000000..081b0d9 --- /dev/null +++ b/api-documents/kit.valueref.value.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [ValueRef](./kit.valueref.md) > [value](./kit.valueref.value.md) + +## ValueRef.value property + +Set current value of a ValueRef + +Signature: + +```typescript +value: T; +``` diff --git a/api-documents/kit.watcher._omitwarningforrepeatedkeys.md b/api-documents/kit.watcher._omitwarningforrepeatedkeys.md new file mode 100644 index 0000000..5cdc527 --- /dev/null +++ b/api-documents/kit.watcher._omitwarningforrepeatedkeys.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [\_omitWarningForRepeatedKeys](./kit.watcher._omitwarningforrepeatedkeys.md) + +## Watcher.\_omitWarningForRepeatedKeys property + +Signature: + +```typescript +protected _omitWarningForRepeatedKeys: boolean; +``` diff --git a/api-documents/kit.watcher.addlistener.md b/api-documents/kit.watcher.addlistener.md new file mode 100644 index 0000000..15a123f --- /dev/null +++ b/api-documents/kit.watcher.addlistener.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [addListener](./kit.watcher.addlistener.md) + +## Watcher.addListener() method + +Signature: + +```typescript +addListener(event: 'onChange', fn: EventCallback<{ + oldNode: T; + newNode: T; + oldKey: unknown; + newKey: unknown; + }[]>): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | 'onChange' | | +| fn | EventCallback<{
oldNode: T;
newNode: T;
oldKey: unknown;
newKey: unknown;
}[]> | | + +Returns: + +`this` + diff --git a/api-documents/kit.watcher.addlistener_1.md b/api-documents/kit.watcher.addlistener_1.md new file mode 100644 index 0000000..f5bff2f --- /dev/null +++ b/api-documents/kit.watcher.addlistener_1.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [addListener](./kit.watcher.addlistener_1.md) + +## Watcher.addListener() method + +Signature: + +```typescript +addListener(event: 'onChangeFull', fn: EventCallback): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | 'onChangeFull' | | +| fn | EventCallback<T[]> | | + +Returns: + +`this` + diff --git a/api-documents/kit.watcher.addlistener_2.md b/api-documents/kit.watcher.addlistener_2.md new file mode 100644 index 0000000..e32d86a --- /dev/null +++ b/api-documents/kit.watcher.addlistener_2.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [addListener](./kit.watcher.addlistener_2.md) + +## Watcher.addListener() method + +Signature: + +```typescript +addListener(event: 'onRemove' | 'onAdd', fn: EventCallback<{ + node: T; + key: unknown; + }[]>): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | 'onRemove' | 'onAdd' | | +| fn | EventCallback<{
node: T;
key: unknown;
}[]> | | + +Returns: + +`this` + diff --git a/api-documents/kit.watcher.assignkeys.md b/api-documents/kit.watcher.assignkeys.md new file mode 100644 index 0000000..08109a5 --- /dev/null +++ b/api-documents/kit.watcher.assignkeys.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [assignKeys](./kit.watcher.assignkeys.md) + +## Watcher.assignKeys() method + +To help identify same nodes in different iteration, you need to implement a map function that map `node` to `key` + +If the key is changed, the same node will call through `forEachRemove` then `forEach` + +Signature: + +```typescript +assignKeys(assigner: (node: T, index: number, arr: T[]) => Q, comparer?: (a: Q, b: Q) => boolean): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| assigner | (node: T, index: number, arr: T[]) => Q | map node to key, defaults to node => node | +| comparer | (a: Q, b: Q) => boolean | compare between two keys, defaults to === | + +Returns: + +`this` + diff --git a/api-documents/kit.watcher.domproxyoption.md b/api-documents/kit.watcher.domproxyoption.md new file mode 100644 index 0000000..944b72e --- /dev/null +++ b/api-documents/kit.watcher.domproxyoption.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [domProxyOption](./kit.watcher.domproxyoption.md) + +## Watcher.domProxyOption property + +Signature: + +```typescript +protected domProxyOption: Partial>; +``` diff --git a/api-documents/kit.watcher.emit.md b/api-documents/kit.watcher.emit.md new file mode 100644 index 0000000..93050e8 --- /dev/null +++ b/api-documents/kit.watcher.emit.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [emit](./kit.watcher.emit.md) + +## Watcher.emit() method + +Signature: + +```typescript +protected emit(event: 'onChange', data: { + oldNode: T; + newNode: T; + oldKey: unknown; + newKey: unknown; + }[]): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | 'onChange' | | +| data | {
oldNode: T;
newNode: T;
oldKey: unknown;
newKey: unknown;
}[] | | + +Returns: + +`boolean` + diff --git a/api-documents/kit.watcher.emit_1.md b/api-documents/kit.watcher.emit_1.md new file mode 100644 index 0000000..0ca9049 --- /dev/null +++ b/api-documents/kit.watcher.emit_1.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [emit](./kit.watcher.emit_1.md) + +## Watcher.emit() method + +Signature: + +```typescript +protected emit(event: 'onChangeFull', data: T[]): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | 'onChangeFull' | | +| data | T[] | | + +Returns: + +`boolean` + diff --git a/api-documents/kit.watcher.emit_2.md b/api-documents/kit.watcher.emit_2.md new file mode 100644 index 0000000..7f8fe06 --- /dev/null +++ b/api-documents/kit.watcher.emit_2.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [emit](./kit.watcher.emit_2.md) + +## Watcher.emit() method + +Signature: + +```typescript +protected emit(event: 'onRemove' | 'onAdd', data: { + node: T; + key: unknown; + }[]): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | 'onRemove' | 'onAdd' | | +| data | {
node: T;
key: unknown;
}[] | | + +Returns: + +`boolean` + diff --git a/api-documents/kit.watcher.eventemitter.md b/api-documents/kit.watcher.eventemitter.md new file mode 100644 index 0000000..43c40ad --- /dev/null +++ b/api-documents/kit.watcher.eventemitter.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [eventEmitter](./kit.watcher.eventemitter.md) + +## Watcher.eventEmitter property + +Event emitter + +Signature: + +```typescript +protected readonly eventEmitter: EventEmitter; +``` diff --git a/api-documents/kit.watcher.findnodefromlistbykey.md b/api-documents/kit.watcher.findnodefromlistbykey.md new file mode 100644 index 0000000..ba83aeb --- /dev/null +++ b/api-documents/kit.watcher.findnodefromlistbykey.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [findNodeFromListByKey](./kit.watcher.findnodefromlistbykey.md) + +## Watcher.findNodeFromListByKey property + +Find node from the given list by key + +Signature: + +```typescript +protected findNodeFromListByKey: (list: T[], keys: unknown[]) => (key: unknown) => T | null; +``` diff --git a/api-documents/kit.watcher.firstvirtualnode.md b/api-documents/kit.watcher.firstvirtualnode.md new file mode 100644 index 0000000..4e14792 --- /dev/null +++ b/api-documents/kit.watcher.firstvirtualnode.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [firstVirtualNode](./kit.watcher.firstvirtualnode.md) + +## Watcher.firstVirtualNode property + +This virtualNode always point to the first node in the LiveSelector + +Signature: + +```typescript +readonly firstVirtualNode: RequireElement, DomProxyBefore, DomProxyAfter>>; +``` diff --git a/api-documents/kit.watcher.getvirtualnodebykey.md b/api-documents/kit.watcher.getvirtualnodebykey.md new file mode 100644 index 0000000..6fb0ce7 --- /dev/null +++ b/api-documents/kit.watcher.getvirtualnodebykey.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [getVirtualNodeByKey](./kit.watcher.getvirtualnodebykey.md) + +## Watcher.getVirtualNodeByKey() method + +Get virtual node by key. Virtual node will be unavailable if it is deleted + +Signature: + +```typescript +getVirtualNodeByKey(key: unknown): DomProxy, DomProxyBefore, DomProxyAfter> | null; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| key | unknown | Key used to find DomProxy | + +Returns: + +`DomProxy, DomProxyBefore, DomProxyAfter> | null` + diff --git a/api-documents/kit.watcher.keycomparer.md b/api-documents/kit.watcher.keycomparer.md new file mode 100644 index 0000000..de7a4e1 --- /dev/null +++ b/api-documents/kit.watcher.keycomparer.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [keyComparer](./kit.watcher.keycomparer.md) + +## Watcher.keyComparer() method + +Compare between `key` and `key`, in case of you don't want the default behavior + +Signature: + +```typescript +protected keyComparer(a: unknown, b: unknown): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| a | unknown | | +| b | unknown | | + +Returns: + +`boolean` + diff --git a/api-documents/kit.watcher.lastcallbackmap.md b/api-documents/kit.watcher.lastcallbackmap.md new file mode 100644 index 0000000..c08afda --- /dev/null +++ b/api-documents/kit.watcher.lastcallbackmap.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [lastCallbackMap](./kit.watcher.lastcallbackmap.md) + +## Watcher.lastCallbackMap property + +Saved callback map of last watch + +Signature: + +```typescript +protected lastCallbackMap: Map>; +``` diff --git a/api-documents/kit.watcher.lastkeylist.md b/api-documents/kit.watcher.lastkeylist.md new file mode 100644 index 0000000..69ef0fe --- /dev/null +++ b/api-documents/kit.watcher.lastkeylist.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [lastKeyList](./kit.watcher.lastkeylist.md) + +## Watcher.lastKeyList property + +Found key list of last watch + +Signature: + +```typescript +protected lastKeyList: unknown[]; +``` diff --git a/api-documents/kit.watcher.lastnodelist.md b/api-documents/kit.watcher.lastnodelist.md new file mode 100644 index 0000000..81f322f --- /dev/null +++ b/api-documents/kit.watcher.lastnodelist.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [lastNodeList](./kit.watcher.lastnodelist.md) + +## Watcher.lastNodeList property + +Found Node list of last watch + +Signature: + +```typescript +protected lastNodeList: T[]; +``` diff --git a/api-documents/kit.watcher.lastvirtualnodesmap.md b/api-documents/kit.watcher.lastvirtualnodesmap.md new file mode 100644 index 0000000..f31059f --- /dev/null +++ b/api-documents/kit.watcher.lastvirtualnodesmap.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [lastVirtualNodesMap](./kit.watcher.lastvirtualnodesmap.md) + +## Watcher.lastVirtualNodesMap property + +Saved virtual node of last watch + +Signature: + +```typescript +protected lastVirtualNodesMap: Map, DomProxyBefore, DomProxyAfter>>; +``` diff --git a/api-documents/kit.watcher.liveselector.md b/api-documents/kit.watcher.liveselector.md new file mode 100644 index 0000000..7f9554e --- /dev/null +++ b/api-documents/kit.watcher.liveselector.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [liveSelector](./kit.watcher.liveselector.md) + +## Watcher.liveSelector property + +Signature: + +```typescript +protected liveSelector: LiveSelector; +``` diff --git a/api-documents/kit.watcher.mapnodetokey.md b/api-documents/kit.watcher.mapnodetokey.md new file mode 100644 index 0000000..8f9ddfd --- /dev/null +++ b/api-documents/kit.watcher.mapnodetokey.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [mapNodeToKey](./kit.watcher.mapnodetokey.md) + +## Watcher.mapNodeToKey() method + +Map `Node -> Key`, in case of you don't want the default behavior + +Signature: + +```typescript +protected mapNodeToKey(node: T, index: number, arr: T[]): unknown; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| node | T | | +| index | number | | +| arr | T[] | | + +Returns: + +`unknown` + diff --git a/api-documents/kit.watcher.md b/api-documents/kit.watcher.md new file mode 100644 index 0000000..6ced2ff --- /dev/null +++ b/api-documents/kit.watcher.md @@ -0,0 +1,60 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) + +## Watcher class + +Use LiveSelector to watch dom change + +You need to implement `startWatch` + +Signature: + +```typescript +export declare abstract class Watcher +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [\_omitWarningForRepeatedKeys](./kit.watcher._omitwarningforrepeatedkeys.md) | | boolean | | +| [domProxyOption](./kit.watcher.domproxyoption.md) | | Partial<DomProxyOptions<DomProxyBefore, DomProxyAfter>> | | +| [eventEmitter](./kit.watcher.eventemitter.md) | | EventEmitter | Event emitter | +| [findNodeFromListByKey](./kit.watcher.findnodefromlistbykey.md) | | (list: T[], keys: unknown[]) => (key: unknown) => T | null | Find node from the given list by key | +| [firstVirtualNode](./kit.watcher.firstvirtualnode.md) | | RequireElement<T, DomProxy<ElementLikeT<T>, DomProxyBefore, DomProxyAfter>> | This virtualNode always point to the first node in the LiveSelector | +| [lastCallbackMap](./kit.watcher.lastcallbackmap.md) | | Map<unknown, useNodeForeachReturns<T>> | Saved callback map of last watch | +| [lastKeyList](./kit.watcher.lastkeylist.md) | | unknown[] | Found key list of last watch | +| [lastNodeList](./kit.watcher.lastnodelist.md) | | T[] | Found Node list of last watch | +| [lastVirtualNodesMap](./kit.watcher.lastvirtualnodesmap.md) | | Map<unknown, DomProxy<ElementLikeT<T>, DomProxyBefore, DomProxyAfter>> | Saved virtual node of last watch | +| [liveSelector](./kit.watcher.liveselector.md) | | LiveSelector<T> | | +| [useNodeForeachFn](./kit.watcher.usenodeforeachfn.md) | | Parameters<Watcher<T, DomProxyBefore, DomProxyAfter>['useNodeForeach']>[0] | null | Saved useNodeForeach | +| [watcherCallback](./kit.watcher.watchercallback.md) | | (deadline?: Deadline | undefined) => void | Should be called every watch | +| [watching](./kit.watcher.watching.md) | | boolean | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [addListener(event, fn)](./kit.watcher.addlistener.md) | | | +| [addListener(event, fn)](./kit.watcher.addlistener_1.md) | | | +| [addListener(event, fn)](./kit.watcher.addlistener_2.md) | | | +| [assignKeys(assigner, comparer)](./kit.watcher.assignkeys.md) | | To help identify same nodes in different iteration, you need to implement a map function that map node to keyIf the key is changed, the same node will call through forEachRemove then forEach | +| [emit(event, data)](./kit.watcher.emit.md) | | | +| [emit(event, data)](./kit.watcher.emit_1.md) | | | +| [emit(event, data)](./kit.watcher.emit_2.md) | | | +| [getVirtualNodeByKey(key)](./kit.watcher.getvirtualnodebykey.md) | | Get virtual node by key. Virtual node will be unavailable if it is deleted | +| [keyComparer(a, b)](./kit.watcher.keycomparer.md) | | Compare between key and key, in case of you don't want the default behavior | +| [mapNodeToKey(node, index, arr)](./kit.watcher.mapnodetokey.md) | | Map Node -> Key, in case of you don't want the default behavior | +| [omitWarningForRepeatedKeys()](./kit.watcher.omitwarningforrepeatedkeys.md) | | If you're expecting repeating keys, call this function, this will omit the warning. | +| [once()](./kit.watcher.once.md) | | Run the Watcher once. Once it emit data, stop watching. | +| [once(fn)](./kit.watcher.once_1.md) | | | +| [removeListener(event, fn)](./kit.watcher.removelistener.md) | | | +| [removeListener(event, fn)](./kit.watcher.removelistener_1.md) | | | +| [removeListener(event, fn)](./kit.watcher.removelistener_2.md) | | | +| [requestIdleCallback(fn, timeout)](./kit.watcher.requestidlecallback.md) | | Is the watcher running | +| [setDomProxyOption(option)](./kit.watcher.setdomproxyoption.md) | | Set option for DomProxy | +| [startWatch(args)](./kit.watcher.startwatch.md) | | | +| [stopWatch(args)](./kit.watcher.stopwatch.md) | | | +| [useNodeForeach(fn)](./kit.watcher.usenodeforeach.md) | | Just like React hooks. Provide callbacks for each node changes. | + diff --git a/api-documents/kit.watcher.omitwarningforrepeatedkeys.md b/api-documents/kit.watcher.omitwarningforrepeatedkeys.md new file mode 100644 index 0000000..c7e3c6b --- /dev/null +++ b/api-documents/kit.watcher.omitwarningforrepeatedkeys.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [omitWarningForRepeatedKeys](./kit.watcher.omitwarningforrepeatedkeys.md) + +## Watcher.omitWarningForRepeatedKeys() method + +If you're expecting repeating keys, call this function, this will omit the warning. + +Signature: + +```typescript +omitWarningForRepeatedKeys(): this; +``` +Returns: + +`this` + diff --git a/api-documents/kit.watcher.once.md b/api-documents/kit.watcher.once.md new file mode 100644 index 0000000..1ec941d --- /dev/null +++ b/api-documents/kit.watcher.once.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [once](./kit.watcher.once.md) + +## Watcher.once() method + +Run the Watcher once. Once it emit data, stop watching. + +Signature: + +```typescript +once(): Promise; +``` +Returns: + +`Promise` + diff --git a/api-documents/kit.watcher.once_1.md b/api-documents/kit.watcher.once_1.md new file mode 100644 index 0000000..32be6d8 --- /dev/null +++ b/api-documents/kit.watcher.once_1.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [once](./kit.watcher.once_1.md) + +## Watcher.once() method + +Signature: + +```typescript +once(fn: (data: T) => PromiseLike | Result): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fn | (data: T) => PromiseLike<Result> | Result | | + +Returns: + +`Promise` + diff --git a/api-documents/kit.watcher.removelistener.md b/api-documents/kit.watcher.removelistener.md new file mode 100644 index 0000000..608e492 --- /dev/null +++ b/api-documents/kit.watcher.removelistener.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [removeListener](./kit.watcher.removelistener.md) + +## Watcher.removeListener() method + +Signature: + +```typescript +removeListener(event: 'onChange', fn: EventCallback<{ + oldNode: T; + newNode: T; + oldKey: unknown; + newKey: unknown; + }[]>): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | 'onChange' | | +| fn | EventCallback<{
oldNode: T;
newNode: T;
oldKey: unknown;
newKey: unknown;
}[]> | | + +Returns: + +`this` + diff --git a/api-documents/kit.watcher.removelistener_1.md b/api-documents/kit.watcher.removelistener_1.md new file mode 100644 index 0000000..b78a81c --- /dev/null +++ b/api-documents/kit.watcher.removelistener_1.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [removeListener](./kit.watcher.removelistener_1.md) + +## Watcher.removeListener() method + +Signature: + +```typescript +removeListener(event: 'onChangeFull', fn: EventCallback): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | 'onChangeFull' | | +| fn | EventCallback<T[]> | | + +Returns: + +`this` + diff --git a/api-documents/kit.watcher.removelistener_2.md b/api-documents/kit.watcher.removelistener_2.md new file mode 100644 index 0000000..e800f4c --- /dev/null +++ b/api-documents/kit.watcher.removelistener_2.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [removeListener](./kit.watcher.removelistener_2.md) + +## Watcher.removeListener() method + +Signature: + +```typescript +removeListener(event: 'onRemove' | 'onAdd', fn: EventCallback<{ + node: T; + key: unknown; + }[]>): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | 'onRemove' | 'onAdd' | | +| fn | EventCallback<{
node: T;
key: unknown;
}[]> | | + +Returns: + +`this` + diff --git a/api-documents/kit.watcher.requestidlecallback.md b/api-documents/kit.watcher.requestidlecallback.md new file mode 100644 index 0000000..35b0c2f --- /dev/null +++ b/api-documents/kit.watcher.requestidlecallback.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [requestIdleCallback](./kit.watcher.requestidlecallback.md) + +## Watcher.requestIdleCallback() method + +Is the watcher running + +Signature: + +```typescript +protected requestIdleCallback(fn: (t: Deadline) => void, timeout?: { + timeout: number; + }): NodeJS.Timeout; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fn | (t: Deadline) => void | | +| timeout | {
timeout: number;
} | | + +Returns: + +`NodeJS.Timeout` + diff --git a/api-documents/kit.watcher.setdomproxyoption.md b/api-documents/kit.watcher.setdomproxyoption.md new file mode 100644 index 0000000..942de16 --- /dev/null +++ b/api-documents/kit.watcher.setdomproxyoption.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [setDomProxyOption](./kit.watcher.setdomproxyoption.md) + +## Watcher.setDomProxyOption() method + +Set option for DomProxy + +Signature: + +```typescript +setDomProxyOption(option: Partial>): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| option | Partial<DomProxyOptions<DomProxyBefore, DomProxyAfter>> | DomProxy options | + +Returns: + +`this` + diff --git a/api-documents/kit.watcher.startwatch.md b/api-documents/kit.watcher.startwatch.md new file mode 100644 index 0000000..bd84399 --- /dev/null +++ b/api-documents/kit.watcher.startwatch.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [startWatch](./kit.watcher.startwatch.md) + +## Watcher.startWatch() method + +Signature: + +```typescript +abstract startWatch(...args: any[]): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| args | any[] | | + +Returns: + +`this` + diff --git a/api-documents/kit.watcher.stopwatch.md b/api-documents/kit.watcher.stopwatch.md new file mode 100644 index 0000000..eec5f6d --- /dev/null +++ b/api-documents/kit.watcher.stopwatch.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [stopWatch](./kit.watcher.stopwatch.md) + +## Watcher.stopWatch() method + +Signature: + +```typescript +stopWatch(...args: any[]): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| args | any[] | | + +Returns: + +`void` + diff --git a/api-documents/kit.watcher.usenodeforeach.md b/api-documents/kit.watcher.usenodeforeach.md new file mode 100644 index 0000000..3039cf1 --- /dev/null +++ b/api-documents/kit.watcher.usenodeforeach.md @@ -0,0 +1,40 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [useNodeForeach](./kit.watcher.usenodeforeach.md) + +## Watcher.useNodeForeach() method + +Just like React hooks. Provide callbacks for each node changes. + +Signature: + +```typescript +useNodeForeach(fn: RequireElement, DomProxyBefore, DomProxyAfter>, key: unknown, realNode: T) => useNodeForeachReturns>): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fn | RequireElement<T, (virtualNode: DomProxy<ElementLikeT<T>, DomProxyBefore, DomProxyAfter>, key: unknown, realNode: T) => useNodeForeachReturns<T>> | You can return a set of functions that will be called on changes. | + +Returns: + +`this` + +## Remarks + +Return value of `fn` + +- `void`: No-op + +- `((oldNode: T) => void)`: it will be called when the node is removed. + +- `{ onRemove?: (old: T) => void; onTargetChanged?: (oldNode: T, newNode: T) => void; onNodeMutation?: (node: T) => void }`, + +- - `onRemove` will be called when node is removed. + +- - `onTargetChanged` will be called when the node is still existing but target has changed. + +- - `onNodeMutation` will be called when the node is the same, but it inner content or attributes are modified. + diff --git a/api-documents/kit.watcher.usenodeforeachfn.md b/api-documents/kit.watcher.usenodeforeachfn.md new file mode 100644 index 0000000..d9871d1 --- /dev/null +++ b/api-documents/kit.watcher.usenodeforeachfn.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [useNodeForeachFn](./kit.watcher.usenodeforeachfn.md) + +## Watcher.useNodeForeachFn property + +Saved useNodeForeach + +Signature: + +```typescript +protected useNodeForeachFn: Parameters['useNodeForeach']>[0] | null; +``` diff --git a/api-documents/kit.watcher.watchercallback.md b/api-documents/kit.watcher.watchercallback.md new file mode 100644 index 0000000..27009ea --- /dev/null +++ b/api-documents/kit.watcher.watchercallback.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [watcherCallback](./kit.watcher.watchercallback.md) + +## Watcher.watcherCallback property + +Should be called every watch + +Signature: + +```typescript +protected watcherCallback: (deadline?: Deadline | undefined) => void; +``` diff --git a/api-documents/kit.watcher.watching.md b/api-documents/kit.watcher.watching.md new file mode 100644 index 0000000..414ad8f --- /dev/null +++ b/api-documents/kit.watcher.watching.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holoflows/kit](./kit.md) > [Watcher](./kit.watcher.md) > [watching](./kit.watcher.watching.md) + +## Watcher.watching property + +Signature: + +```typescript +protected watching: boolean; +``` diff --git a/api-extractor.json b/api-extractor.json new file mode 100644 index 0000000..b2d5de1 --- /dev/null +++ b/api-extractor.json @@ -0,0 +1,349 @@ +/** + * Config file for API Extractor. For more info, please visit: https://api-extractor.com + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + + /** + * Optionally specifies another JSON config file that this file extends from. This provides a way for + * standard settings to be shared across multiple projects. + * + * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains + * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be + * resolved using NodeJS require(). + * + * SUPPORTED TOKENS: none + * DEFAULT VALUE: "" + */ + // "extends": "./shared/api-extractor-base.json" + // "extends": "my-package/include/api-extractor-base.json" + + /** + * Determines the "" token that can be used with other config file settings. The project folder + * typically contains the tsconfig.json and package.json config files, but the path is user-defined. + * + * The path is resolved relative to the folder of the config file that contains the setting. + * + * The default value for "projectFolder" is the token "", which means the folder is determined by traversing + * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder + * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error + * will be reported. + * + * SUPPORTED TOKENS: + * DEFAULT VALUE: "" + */ + // "projectFolder": "..", + + /** + * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor + * analyzes the symbols exported by this module. + * + * The file extension must be ".d.ts" and not ".ts". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + */ + "mainEntryPointFilePath": "/es/index.d.ts", + + /** + * Determines how the TypeScript compiler engine will be invoked by API Extractor. + */ + "compiler": { + /** + * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * Note: This setting will be ignored if "overrideTsconfig" is used. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/tsconfig.json" + */ + // "tsconfigFilePath": "/tsconfig.json", + /** + * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. + * The object must conform to the TypeScript tsconfig schema: + * + * http://json.schemastore.org/tsconfig + * + * If omitted, then the tsconfig.json file will be read from the "projectFolder". + * + * DEFAULT VALUE: no overrideTsconfig section + */ + // "overrideTsconfig": { + // . . . + // } + /** + * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended + * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when + * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses + * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. + * + * DEFAULT VALUE: false + */ + // "skipLibCheck": true, + }, + + /** + * Configures how the API report file (*.api.md) will be generated. + */ + "apiReport": { + /** + * (REQUIRED) Whether to generate an API report. + */ + "enabled": false + + /** + * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce + * a full file path. + * + * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". + * + * SUPPORTED TOKENS: , + * DEFAULT VALUE: ".api.md" + */ + // "reportFileName": ".api.md", + + /** + * Specifies the folder where the API report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, + * e.g. for an API review. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/etc/" + */ + // "reportFolder": "/etc/", + + /** + * Specifies the folder where the temporary report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * After the temporary file is written to disk, it is compared with the file in the "reportFolder". + * If they are different, a production build will fail. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/" + */ + // "reportTempFolder": "/temp/" + }, + + /** + * Configures how the doc model file (*.api.json) will be generated. + */ + "docModel": { + /** + * (REQUIRED) Whether to generate a doc model file. + */ + "enabled": true + + /** + * The output path for the doc model file. The file extension should be ".api.json". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/.api.json" + */ + // "apiJsonFilePath": "/temp/.api.json" + }, + + /** + * Configures how the .d.ts rollup file will be generated. + */ + "dtsRollup": { + /** + * (REQUIRED) Whether to generate the .d.ts rollup file. + */ + "enabled": false + + /** + * Specifies the output path for a .d.ts rollup file to be generated without any trimming. + * This file will include all declarations that are exported by the main entry point. + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/dist/.d.ts" + */ + // "untrimmedFilePath": "/dist/.d.ts", + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. + * This file will include only declarations that are marked as "@public" or "@beta". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "betaTrimmedFilePath": "/dist/-beta.d.ts", + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. + * This file will include only declarations that are marked as "@public". + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "publicTrimmedFilePath": "/dist/-public.d.ts", + + /** + * When a declaration is trimmed, by default it will be replaced by a code comment such as + * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the + * declaration completely. + * + * DEFAULT VALUE: false + */ + // "omitTrimmingComments": true + }, + + /** + * Configures how the tsdoc-metadata.json file will be generated. + */ + "tsdocMetadata": { + /** + * Whether to generate the tsdoc-metadata.json file. + * + * DEFAULT VALUE: true + */ + // "enabled": true, + /** + * Specifies where the TSDoc metadata file should be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", + * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup + * falls back to "tsdoc-metadata.json" in the package folder. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" + }, + + /** + * Configures how API Extractor reports error and warning messages produced during analysis. + * + * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. + */ + "messages": { + /** + * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing + * the input .d.ts files. + * + * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "compilerMessageReporting": { + /** + * Configures the default routing for messages that don't match an explicit rule in this table. + */ + "default": { + /** + * Specifies whether the message should be written to the the tool's output log. Note that + * the "addToApiReportFile" property may supersede this option. + * + * Possible values: "error", "warning", "none" + * + * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail + * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes + * the "--local" option), the warning is displayed but the build will not fail. + * + * DEFAULT VALUE: "warning" + */ + "logLevel": "warning" + + /** + * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), + * then the message will be written inside that file; otherwise, the message is instead logged according to + * the "logLevel" option. + * + * DEFAULT VALUE: false + */ + // "addToApiReportFile": false + } + + // "TS2551": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + + /** + * Configures handling of messages reported by API Extractor during its analysis. + * + * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" + * + * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings + */ + "extractorMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + }, + "ae-forgotten-export": { + "logLevel": "none" + }, + "ae-incompatible-release-tags": { + "logLevel": "none" + }, + "ae-missing-release-tag": { + "logLevel": "none" + } + + // "ae-extra-release-tag": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + + /** + * Configures handling of messages reported by the TSDoc parser when analyzing code comments. + * + * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "tsdocMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + } + + // "tsdoc-link-tag-unescaped-text": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + } + } +} diff --git a/doc/en/DOM.md b/doc/en/DOM.md index f91a419..fb62570 100644 --- a/doc/en/DOM.md +++ b/doc/en/DOM.md @@ -15,7 +15,7 @@ It will automatically refresh, just like today's PWAs. You want to add price in USD after every ticket. -```typescript +```ts import { LiveSelector, MutationObserverWatcher } from '@holoflows/kit/DOM' const price = new LiveSelector() @@ -139,418 +139,3 @@ A complete `useNodeForeach` call is like this: Tutorial is over. - -# Documentation - -## class LiveSelector - -All methods except `evaluateOnce()` can be chained. - -```ts -const ls = new LiveSelector() -``` - -> Typescript: `LiveSelector` have a generic `` to tell you the current type. - -### `.querySelector(selector)` - -Same as [document.querySelector](https://mdn.io/document.querySelector), add element to the list when evaluating. - -```typescript -ls.querySelector('div#root').querySelector('.nav') -``` - -**Warn: Example above is union of `div#root` and `.nav`, is NOT `div#root .nav`** - -> Typescript: Generic is the same as `document.querySelector` - -> Typescript: For complex CSS selector, you need to specify it manually. Or you will get type `Element`. `.querySelector('div > div')` - -### `.querySelectorAll(selector)` - -Same as [document.querySelectorAll](https://mdn.io/document.querySelectorAll), add elements to the list when evaluating. - -```typescript -ls.querySelectorAll('div').querySelectorAll('h1') -``` - -**Warn: Example above is union of `div` and `h1`, is NOT `div h1`** - -> Typescript: Generic is the same as `document.querySelectorAll` - -> Typescript: For complex CSS selector, you need to specify it manually. Or you will get type `Element`. `.querySelectorAll('div > div')` - -### `.filter(callbackfn)` - -Like [Array#filter](https://mdn.io/Array.filter). Remove items you don't want. - -```ts -ls.filter(x => x.innerText.match('hello')) -``` - -### `.map(callbackfn)` - -Like [Array#map](https://mdn.io/Array.map). Map an item to another. - -```ts -ls.map(x => x.parentElement) -``` - -**Tips: You can map to anything you want!** - -### `.concat(newLS: LiveSelector)` - -Like [Array#concat](https://mdn.io/Array.concat). Concat the result of another `LiveSelector.evaluateOnce()` into the current list. - -```ts -ls.concat(new LiveSelector().querySelector('#root')) -``` - -### `.reverse()` - -Like [Array#reverse](https://mdn.io/Array.reverse). Reverse the list. - -```ts -ls.reverse() -``` - -### `.slice(start?, end?)` - -Like [Array#slice](https://mdn.io/Array.slice). Cut the list. - -```ts -ls.slice(2, 4) -``` - -### `.sort(compareFn)` - -Like [Array#sort](https://mdn.io/Array.sort). Sort the list. - -```ts -ls.sort((a, b) => a.innerText.length - b.innerText.length) -``` - -### `.flat()` - -Like [Array#flat](https://mdn.io/Array.flat). Flatten the list. - -**Notice: Not recursively flating the list** - -```ts -ls.flat() -``` - -### `.nth(n: number)` - -Remove anything except nth element. - -```ts -ls.nth(-1) -``` - -### `.replace(f: (list: T[]) => NextT[])` - -If you think methods above is not enought, you can replace the list directly when evaluating. - -```ts -ls.replace(x => lodash.dropRight(x, 2)) -``` - -### `.evaluateOnce()` - -Evaluate. This should be the last method to be called. - -```ts -ls.evaluateOnce() -``` - -## Watchers - -Every `Watcher` extends from abstract class [Watcher](#abstract-class-Watcher-public)。 - -### abstract class Watcher (public) - -Extends [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) - -#### `constructor (liveSelector)` - -Create a new `Watcher`。 - -- liveSelector: `LiveSelector`, LiveSelector to watch - -> Typescript: Generic `T` equals to generic of the `LiveSelector` passed in. - -#### abstract `.startWatch(?): this` - -Start watching. - -#### abstract `.stopWatch()` - -Stop watching. - -#### `.addListener(event, fn)` - -See [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). -Only these events are available. - -- onChange - When list updated, changed part -- onChangeFull - When list updated, full list -- onRemove - When list updated, removed part -- onAdd - When list updated, added part - -#### `.firstVirtualNode` - -**Notice: T must be sub type of HTMLElement, or it will be type `never`.** - -A [DomProxy](#doc-domproxy), always points to the first result of `LiveSelector`. - -#### `.assignKeys(assigner, comparer?)` - -- assigner: assign a key for each element. Key can be anything. - Defaults to `x => x` - -```ts -assigner: (node: T, index: number, arr: T[]) => Q -// node: node that needs a key -// index: current index -// arr: whole array -``` - -- comparer: If your key can't be compared by `===`, provide a comparer. - Defaults to `(a, b) => a === b` - -```ts -comparer: (a: Q, b: Q) => boolean -// a: returned by assigner -// b: returned by assigner -``` - -#### `.useNodeForEach(fn)` - -**Notice: T must be sub type of HTMLElement, or it will be type `never`.** - -Everytime that a new item is added to the list, it will be called. - -##### fn and it's return value - -```ts -(fn: (node: DomProxy, key: Q, realNode: HTMLElement) => ...) -// node: a DomProxy -// key: returned from assigner -// realNode: real node after DomProxy -``` - -- undefined: nothing will happen -- A function: `(oldNode: T) => void`, when `key` is gone, it will be called -- An object: `{ onRemove?: (old: T) => void; onTargetChanged?: (oldNode: T, newNode: T) => void; onNodeMutation?: (node: T) => void }` -- - `onRemove(oldNode: T) => void`, when key is gone, it will be called -- - `onTargetChanged(oldNode: T, newNode: T) => void`, when `key` is still exist but `realNode` has changed, it will be called. -- - `onNodeMutation: (node: T) => void`, when `key` and `realCurrent` is not changed, but there are some mutations inside the `realCurrent`, it will be called. - -#### `.getVirtualNodeByKey(key)` - -**Notice: T must be sub type of HTMLElement, or it will be type `never`.** - -- key: Q. Find [DomProxy](#doc-domproxy) by `key` - -### MutationObserverWatcher - -#### `constructor (liveSelector, consistentWatchRoot = document.body)` - -Create a new `MutationObserverWatcher`。 - -It will listen on `consistentWatchRoot`. If any changes happened, it will do a check. - -> At most 1 query every requestAnimateFrame. Don't worry - -- liveSelector: `LiveSelector`, LiveSelector to watch -- consistentWatchRoot: `Element | Document`, it should be consistent (if it is deleted, watcher will not work.). Defaults to `document.body`, if you think `MutationObserverWatcher` cause some performance problem, use this. - -#### `startWatch(options?: MutationObserverInit)` - -Start watching. - -- options: `MutationObserverInit`, you can remove some options by yourself. See: [MutationObserver](https://mdn.io/MutationObserver) - -### IntervalWatcher - -Check when time passed. - -#### `.startWatch(interval)` - -Start watching. - -- options: `numebr`, check it at `options`ms - -### EventWatcher - -Check when you want. - -#### `.startWatch()` - -Start watching. - -#### `.eventListener()` - -Event listener. -Usage: - -```ts -ele.addEventListener('click', watcher.eventListener) -``` - -### ValueRef - -ValueRef is not related to `Watcher`. It listen to `.value =`. e.g.: - -```ts -const ref = new ValueRef(0) -ref.value // 0 -ref.addListener((newVal, old) => console.log(newVal, old)) -ref.value = 1 -// Log: 1, 0 -ref.value // 1 -``` - -#### `constructor(value: T)` - -- value: Initial value - -#### `.value` - -Current value. If you assign it, then it will emit event. - -#### `.removeListener(fn: (newValue: T, oldValue: T) => void): void` - -Remove listener. - -#### abstract class Watcher (protected) - -Here are `protected` fields and methods in the `Watcher` class. If you are not extending it, you don't need it. - -#### protected readonly `.nodeWatcher: MutationWatcherHelper` - -Used to invoke `onNodeMutation` - -`MutationWatcherHelper` is an un-exported internal class. Don't use it. - -#### protected `.watching: boolean` - -Is it watching now? - -If false, `.watcherCallback()` will do nothing. - -#### protected `.lastKeyList: unknown[]` - -Key list of last time. - -#### protected `.lastNodeList: T[]` - -Element list of last time. - -#### protected `.lastCallbackMap: Map>` - -All living return functions(like `onRemove` `onNodeMutation`) - -#### protected `.lastVirtualNodesMap: Map` - -All living DomProxy - -#### protected `.findNodeFromListByKey(list: T[], keys: unknown: []): (key: unknown) => T | null` - -A high order function. Find key in the list. - -```ts -.findNodeFromListByKey(this.lastNodeList, this.lastKeyList)(key) -``` - -#### protected `.watcherCallback()` - -Core of watcher. If you want to do a check, call it. - -It does: - -- Call `LiveSelector.evaluateOnce()` -- Call `assignKeys` to get `key`s - -- For deleted nodes, call `onRemove` -- For new nodes, call `useNodeForeachFn()` -- For changed nodes, call `onTargetChanged()` -- Set `lastCallbackMap` `lastVirtualNodesMap` `lastKeyList` `lastNodeList` -- Emit `onChangeFull` `onChange` `onRemove` `onAdd` -- Bind `.firstVirtualNode.realCurrent` to new value. - -#### protected `.mapNodeToKey(node: T, index: number: arr: T[]): unknown` - -map node to key (can be overwrited by `assignKeys`) - -#### protected `.keyComparer(a: unknown, b: unknown): boolean` - -check if `a` and `b` are equal (can be overwrited by `assignKeys`) - -#### protected `.useNodeForeachFn(...): ...` - -See [.useNodeForeach](#doc-watcher-usenodeforeach-fn) - -## DomProxy - -DomProxy provide an interface that be stable even dom is changed. - -Return this object - -### `.before` - -A span element, always at before of [realCurrent](#doc-domproxy-realCurrent) - -### `.current` - -A fake HTML, all operations on it will be forwarded to [realCurrent](#doc-domproxy-realCurrent). After `realCurrent` changes, some of effects will be move to the new target. - -### `.after` - -A span element, alwyas at after of [realCurrent](#doc-domproxy-realCurrent) - -### `.realCurrent` - -The real current. If you change it, [after](#doc-domproxy-after), [current](#doc-domproxy-current), [before](#doc-domproxy-before) will be affected too. - -### `.destroy()` - -Destroy the DomProxy. - -### DOMProxy behavior - -**Notice, only changes happened on current will be forwarded.** - -- forward: forward to the current`realCurrent` -- undo: If `realCurrent` changed, effect will be undoed. -- move: If `realCurrent` changed, effect will be moved. - -| Attributes | forward | undo | move | -| ---------------- | ------- | ---- | ---- | -| style | Yes | No | Yes | -| addEventListener | Yes | Yes | Yes | -| appendChild | Yes | Yes | Yes | -| ...default | Yes | No | No | - -This means, if you change `current.style.opacity`, then change `realCurrent`, new `realCurrent` will set `current.style.opacity` automatically. - -**We will add more behavior later. If you need something, issues or pull requests wellcome!** diff --git a/doc/en/Extension.md b/doc/en/Extension.md deleted file mode 100644 index 1a9bd28..0000000 --- a/doc/en/Extension.md +++ /dev/null @@ -1,159 +0,0 @@ -# @holoflows/kit/Extension/ - -Some tools that useful in extension developing - -# class MessageCenter - -A class that can help you to send messages in different context. - -> Typescript: Messages are typed! - -```ts -interface AllEvents { - event1: { x: number; y: number } -} -const mc = new MessageCenter() -mc.on // Can only listen to keyof AllEvents -mc.send // Can only send event of keyof AllEvents and data need to be type AllEvents[key] -``` - -## `constructor(key?: string)` - -- key?: Whatever, but need to be same in all instances. - -## `.on(event, handler)` - -Listen to event - -## `.on(event, data)` - -Send event - -## `.writeToConsole` - -Log to console when receive/send event - -# AsyncCall - -Async call between different context. - -A High level abstraction of MessageCenter. - -> Shared code - -- How to stringify/parse parameters/returns should be shared, defaults to NoSerialization. -- `key` should be shared. - -> One side - -- Should provide some functions then export its type (for example, `BackgroundCalls`) -- `const call = AsyncCall(backgroundCalls)` -- Then you can `call` any method on `ForegroundCalls` - -> Other side - -- Should provide some functions then export its type (for example, `ForegroundCalls`) -- `const call = AsyncCall(foregroundCalls)` -- Then you can `call` any method on `BackgroundCalls` - -_Note: Two sides can implement the same function_ - -Example: - -```typescript -// Mono repo -// On UI -const UI = { - async dialog(text: string) { - alert(text) - }, -} -export type UI = typeof UI -const callsClient = AsyncCall(UI) -callsClient.sendMail('hello world', 'what') - -// On server -const Server = { - async sendMail(text: string, to: string) { - return true - }, -} -export type Server = typeof Server -const calls = AsyncCall(Server) -calls.dialog('hello') -``` - -## Options - -- key: A key to prevent collision with other AsyncCalls. Can be anything, but need to be the same on the both side. (Defaults to `default`) -- serializer: How to serialization and deserialization parameters and return values (Defaults to `NoSerialization`) -- MessageCenter: A class that can let you transfer messages between two sides (Defaults to `MessageCenter` of @holoflows/kit) -- dontThrowOnNotImplemented: If this side receive messages that we didn't implemented, throw an error (Defaults to `true`) -- writeToConsole: Write all calls to console. (Defaults to `true`) - -## Serializer: - -We offer some built-in serializer: - -- `NoSerialization` (Do not do any serialization) -- `JSONSerialization` (Use JSON.parse/stringify) (You can provide a `replacer`! See [JSON.stringify](https://mdn.io/JSON.stringify)) - -You can also build your own serializer by implement interface `Serialization` - -## Return: - -`typeof` the type parameter. (``) - -# AutomatedTabTask - -Based on AsyncCall. Open a new page in the background, execute some task, then close it automatically. - -Usage: - -> In content script: (You must run this in the page you wanted to run task in!) - -```ts -export const task = AutomatedTabTask({ - async taskA() { - return 'Done!' - }, -}) -``` - -> In background script: - -```ts -import { task } from '...' -task('https://example.com/').taskA() -// Open https://example.com/ then run taskA() on that page, which will return 'Done!' -``` - -## Parameters: - -- taskImplements: All tasks that background page can call. -- AsyncCallKey: A unique key, defaults to a extension specific url. - -## Return: - -- `null` on content script -- `typeof taskImplements` on background page - -# Contexts - -Identify the current running context - -## type `Contexts` - -- background: background page -- content: content script -- unknown: unknown - -## `GetContext():`Contexts - -Get current context - -## `OnlyRunInContext`(context: Contexts | Contexts[], name: string) - -Check if the current context is the wanted context, if not, throws. - -- `name` is the name while you throw diff --git a/doc/en/index.md b/doc/en/index.md index a122b95..3f7a300 100644 --- a/doc/en/index.md +++ b/doc/en/index.md @@ -4,10 +4,9 @@ A toolkit for browser extension developing. ## Components -@holoflows/kit 由以下部分组成 +See [API documents](../../api-documents/kit.md) -- [DOM](./DOM.md) - Help developer to track changes in the content script -- [Extension](./Extension.md) - Some tools that useful in extension developing +See tutorial of [DOM watcher](./DOM.md) ## Installation @@ -15,7 +14,10 @@ Use `yarn` or `npm`. Or use UMD. > https://unpkg.com/@holoflows/kit@latest/dist/out.js -使用模块加载器时,使用 `@holoflows/kit` 导入;使用 umd 时,使用 `window.HoloflowsKit`。 +If you're using module bundler, use `@holoflows/kit` to import; +using umd, use `window.HoloflowsKit`. + +You need to load a polyfill for WebExtension in Chrome (`webextension-polyfill`) ### ECMAScript version diff --git a/doc/zh-CN/DOM.md b/doc/zh-CN/DOM.md index 9529e94..ed58e1c 100644 --- a/doc/zh-CN/DOM.md +++ b/doc/zh-CN/DOM.md @@ -2,8 +2,6 @@ 提供了一组方便追踪被注入页面中内容变化的工具。 -只是来查阅文档的?跳过教程,直接跳转到 [文档](#doc) 吧 - ## 例子
@@ -13,7 +11,7 @@ 假设被注入页面是由 React 生成的机票价格页面,它会动态刷新。你想在每张机票的价格后面加上它的美元价格。 -```typescript +```ts import { LiveSelector, MutationObserverWatcher } from '@holoflows/kit/DOM' const price = new LiveSelector() @@ -66,7 +64,7 @@ setTimeout(() => { }) ``` -关于 LiveSelector 的完整用法,参见 [LiveSelector 的文档](#doc-LiveSelector)。 +关于 LiveSelector 的完整用法,参见 [LiveSelector 的文档](../../api-documents/kit.liveselector.md)。 那么 `MutationObserverWatcher` 又是什么? @@ -138,415 +136,3 @@ Watcher 有以下几种:
教程到这里就结束了,你可以回头看看最开始的例子。 - -# 文档 - -## class LiveSelector - -LiveSelector 上的方法,除了 `evaluateOnce()` 都可以链式调用。 - -```ts -const ls = new LiveSelector() -``` - -> Typescript: `LiveSelector` 每一次都有一个类型参数 `` 告诉你计算到这一步时它的类型。 - -### `.querySelector(selector)` - -同 [document.querySelector](https://mdn.io/document.querySelector),计算时,将选中的元素加入列表。 - -```typescript -ls.querySelector('div#root').querySelector('.nav') -``` - -**注意:以上的例子等于 `div#root` 和 `.nav` 的并集,并不是 `div#root .nav` 的意思!** - -> Typescript: 这个函数的泛型与 `document.querySelector` 相同 - -> Typescript: 对于复杂的 CSS 选择器,无法推断它的类型,你需要手动指定。`.querySelector('div > div')` - -### `.querySelectorAll(selector)` - -同 [document.querySelectorAll](https://mdn.io/document.querySelectorAll),计算时,将选中的元素加入列表。 - -```typescript -ls.querySelectorAll('div').querySelectorAll('h1') -``` - -**注意:以上的例子等于 `div` 和 `h1` 的并集,并不是 `div h1` 的意思!** - -> Typescript: 这个函数的泛型与 `document.querySelectorAll` 相同 - -> Typescript: 对于复杂的 CSS 选择器,无法推断它的类型,你需要手动指定。`.querySelectorAll('div > div')` - -### `.filter(callbackfn)` - -与 [Array#filter](https://mdn.io/Array.filter) 类似。计算时,过滤掉列表中不符合的内容。 - -```ts -ls.filter(x => x.innerText.match('hello')) -``` - -### `.map(callbackfn)` - -与 [Array#map](https://mdn.io/Array.map) 类似。计算时,将列表中的每个元素映射为另一个元素。 - -```ts -ls.map(x => x.parentElement) -``` - -**提示:map 不只限于 map 到 Dom 元素,你可以 map 到任何东西** - -### `.concat(newLS: LiveSelector)` - -与 [Array#concat](https://mdn.io/Array.concat) 类似。计算时,将一个新的 `LiveSelector` 的计算结果合并进当前的计算结果。 - -```ts -ls.concat(new LiveSelector().querySelector('#root')) -``` - -### `.reverse()` - -与 [Array#reverse](https://mdn.io/Array.reverse) 类似。计算时,将列表反转。 - -```ts -ls.reverse() -``` - -### `.slice(start?, end?)` - -与 [Array#slice](https://mdn.io/Array.slice) 类似。计算时,截取列表的一部分。 - -```ts -ls.slice(2, 4) -``` - -### `.sort(compareFn)` - -与 [Array#sort](https://mdn.io/Array.sort) 类似。计算时,对列表排序。 - -```ts -ls.sort((a, b) => a.innerText.length - b.innerText.length) -``` - -### `.flat()` - -与 [Array#flat](https://mdn.io/Array.flat) 类似。计算时,将列表变平。 - -**注意:这不是递归的操作!** - -```ts -ls.flat() -``` - -### `.nth(n: number)` - -计算时,只保留第 N 个元素。 - -```ts -ls.nth(-1) -``` - -### `.replace(f: (list: T[]) => NextT[])` - -如果认为上面这些类 Array 的方法还不足以满足你的要求,你可以直接在计算时替换列表。 - -```ts -ls.replace(x => lodash.dropRight(x, 2)) -``` - -### `.evaluateOnce()` - -执行一次计算。这应该是最后一个被调用的方法。每一次调用,它都会重新根据你之前所执行的 `querySelector` 等方法查询一次。 - -```ts -ls.evaluateOnce() -``` - -## Watchers - -所有 Watcher 都继承自抽象类 [Watcher](#abstract-class-Watcher-public)。 - -### abstract class Watcher (public) - -继承自 [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) - -#### `constructor (liveSelector)` - -创建一个新的 `Watcher`。 - -- liveSelector: `LiveSelector`, 要监听的 LiveSelector - -> Typescript: 泛型 `T` 等于 LiveSelector 的泛型 - -#### abstract `.startWatch(?): this` - -开始监听。参数请参见实现的说明。 - -#### abstract `.stopWatch()` - -停止监听。 - -#### `.addListener(event, fn)` - -见 [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)。 -仅提供以下事件: - -- onChange - 每次列表更新时,发生变化的部分 -- onChangeFull - 每次列表更新时,新的完整的列表 -- onRemove - 每次列表更新时,被删除的部分 -- onAdd - 每次列表更新时,被添加的部分 - -#### `.firstVirtualNode` - -**注意:如果 T 不是 HTMLElement 的子类型,那么该对象不可用。** - -一个 [DomProxy](#doc-domproxy),始终指向 LiveSelector 的第一个元素 - -#### `.assignKeys(assigner, comparer?)` - -- assigner: 为每个元素分配一个 key,key 可以是任何内容。 - 默认为 `x => x` - -```ts -assigner: (node: T, index: number, arr: T[]) => Q -// node: 需要被分配 key 的元素 -// index: 当前位置 -// arr: 整个数组 -``` - -- comparer: 如果你的 key 不能用 `===` 来直接比较的话,用这个参数自定义一个比较方式。默认为 `(a, b) => a === b` - -```ts -comparer: (a: Q, b: Q) => boolean -// a: 一种 assigner 返回的对象 -// b: 另一种 assigner 返回的对象 -``` - -#### `.useNodeForEach(fn)` - -**注意:如果 T 不是 HTMLElement 的子类型,那么该函数不可用。** - -列表里每添加一个新项目,会调用一次 fn。 - -##### fn 与它的返回值: - -```ts -(fn: (node: DomProxy, key: Q, realNode: HTMLElement) => ...) -// node: 一个 DomProxy -// key: assigner 返回的 key -// realNode: node 所对应的真实 node -``` - -- undefined: 什么也不会发生 -- 一个函数: `(oldNode: T) => void`,当 `key` 在某一次迭代中从列表中消失了,该函数会被调用 -- 一个对象: `{ onRemove?: (old: T) => void; onTargetChanged?: (oldNode: T, newNode: T) => void; onNodeMutation?: (node: T) => void }` -- - `onRemove(oldNode: T) => void` 当 `key` 在某一次迭代中从列表中消失了,该函数会被调用 -- - `onTargetChanged(oldNode: T, newNode: T) => void` 当 `key` 在两次迭代中依然存在,但它对应的元素不同时会调用 -- - `onNodeMutation: (node: T) => void` 当 `key` 和元素都没有变化,而是元素内部发生了变化时会调用。 - -#### `.getVirtualNodeByKey(key)` - -**注意:如果 T 不是 HTMLElement 的子类型,那么该函数不可用。** - -- key: Q. 根据 key 寻找 key 对应的 [DomProxy](#doc-domproxy) - -### MutationObserverWatcher - -#### `constructor (liveSelector, consistentWatchRoot = document.body)` - -创建一个新的 `MutationObserverWatcher`。 - -它会在 `consistentWatchRoot` 上发生任何变化时计算变化。 - -> 每 requestAnimateFrame 最多查询一次,不会导致页面卡顿。 - -- liveSelector: `LiveSelector`, 要监听的 LiveSelector -- consistentWatchRoot: `Element | Document`, 保证稳定(不会被删除从而导致监听失效)的元素,默认为 `document.body`,如果你怀疑 `MutationObserverWatcher` 导致了性能问题,请提供这个参数缩小监听范围。 - -#### `startWatch(options?: MutationObserverInit)` - -开始监听。 - -- options: `MutationObserverInit`,你可以自行排除掉对某些修改事件的监听。(见: [MutationObserver](https://mdn.io/MutationObserver) ) - -### IntervalWatcher - -通过时间流逝触发检查。 - -#### `.startWatch(interval)` - -开始监听。 - -- options: `numebr`,每隔多少毫秒检查一次。 - -### EventWatcher - -手动触发检查。 - -#### `.startWatch()` - -开始监听。 - -#### `.eventListener()` - -事件监听器。 -用法: - -```ts -ele.addEventListener('click', watcher.eventListener) -``` - -### ValueRef - -ValueRef 与 `Watcher` 无关,它监听 `.value =`。例如: - -```ts -const ref = new ValueRef(0) -ref.value // 0 -ref.addListener((newVal, old) => console.log(newVal, old)) -ref.value = 1 -// Log: 1, 0 -ref.value // 1 -``` - -#### `constructor(value: T)` - -- value: 初始值。 - -#### `.value` - -当前值。写入的话会触发事件。 - -#### `.removeListener(fn: (newValue: T, oldValue: T) => void): void` - -取消监听器。 - -#### abstract class Watcher (protected) - -这里 Watcher 的 protected 属性与方法。如果你不是在自己继承 `Watcher`,那么你用不到这里提到的属性和方法。 - -#### protected readonly `.nodeWatcher: MutationWatcherHelper` - -用于触发 onNodeMutation 的 Watcher。 - -`MutationWatcherHelper` 是内部类,没有导出。你不应该使用它。 - -#### protected `.watching: boolean` - -当前是否正在监听。如果为 `false`,那么 `.watcherCallback()` 不会有反应。 - -#### protected `.lastKeyList: unknown[]` - -上一次检查的 Key 列表。 - -#### protected `.lastNodeList: T[]` - -上一次检查的元素列表。 - -#### protected `.lastCallbackMap: Map>` - -所有还未销毁的 `useNodeForeach()` 所返回的函数(`onRemove` `onNodeMutation` 等) - -#### protected `.lastVirtualNodesMap: Map` - -所有还未销毁的 DomProxy - -#### protected `.findNodeFromListByKey(list: T[], keys: unknown: []): (key: unknown) => T | null` - -一个高阶函数。通过 key 在 list 中寻找元素。(因为元素可能是复杂的对象,不能直接比较。) - -```ts -.findNodeFromListByKey(this.lastNodeList, this.lastKeyList)(key) -``` - -#### protected `.watcherCallback()` - -检查逻辑。当你需要触发检查时,调用它。 - -它做了以下这些事情: - -- 调用 `LiveSelector` 的 `EvaluateOnce` -- 通过 `assignKeys` 为每个元素设置 `key` - -- 寻找被删除的元素并调用 `onRemove` -- 寻找新增的元素并调用 `useNodeForeachFn()` -- 寻找 `key` 相同但变化了的元素并调用 `onTargetChanged()` -- 重新设置 `lastCallbackMap` `lastVirtualNodesMap` `lastKeyList` `lastNodeList` -- 发出 `onChangeFull` `onChange` `onRemove` `onAdd` 事件 -- 为 `.firstVirtualNode` 绑定新的 Node - -#### protected `.mapNodeToKey(node: T, index: number: arr: T[]): unknown` - -返回 node 对应的 key(可被 `assignKeys` 覆盖) - -#### protected `.keyComparer(a: unknown, b: unknown): boolean` - -返回 `a` 和 `b` 是否相等 (可被 `assignKeys` 覆盖) - -#### protected `.useNodeForeachFn(...): ...` - -见 [.useNodeForeach](#doc-watcher-usenodeforeach-fn) - -## DomProxy - -DomProxy 提供抽象的 Dom,其引用不随 Dom 变化而变化。 - -调用后返回以下对象 - -### `.before` - -一个 span 元素,始终位于 [realCurrent](#doc-domproxy-realCurrent) 前面 - -### `.current` - -一个伪装的 HTML 元素,对其的操作会转发到 [realCurrent](#doc-domproxy-realCurrent),realCurrent 改变后,曾经的部分操作会被转发。 - -### `.after` - -一个 span 元素,始终位于 [realCurrent](#doc-domproxy-realCurrent) 后面 - -### `.realCurrent` - -真实的 current。修改的话会触发 [after](#doc-domproxy-after), [current](#doc-domproxy-current), [before](#doc-domproxy-before) 的修改。 - -### `.destroy()` - -调用后该 DomProxy 不再可用。 - -### DOMProxy 当前的转发策略 - -**注意:只有通过 current 进行的修改才会被转发** - -- forward: 转发到当前的 `realCurrent` -- undo: `realCurrent` 修改时被撤销 -- move: `realCurrent` 修改时移动到新的 `realCurrent` 上 - -| 属性 | forward | undo | move | -| ---------------- | ------- | ---- | ---- | -| style | Yes | No | Yes | -| addEventListener | Yes | Yes | Yes | -| appendChild | Yes | Yes | Yes | -| ...默认 | Yes | No | No | - -这意味着,你修改 `current.style.opacity`,在 `realCurrent` 被修改后,新的 `realCurrent` 也会自动修改 `current.style.opacity` - -**将来会添加更多转发策略,如有需要,欢迎到 issue 提出,或自己实现!** diff --git a/doc/zh-CN/Extension.md b/doc/zh-CN/Extension.md deleted file mode 100644 index ec6e143..0000000 --- a/doc/zh-CN/Extension.md +++ /dev/null @@ -1,159 +0,0 @@ -# @holoflows/kit/Extension/ - -一些扩展开发会使用到的实用工具 - -# class MessageCenter - -一个在 Chrome 扩展不同上下文间传递消息的类。 - -> Typescript: 消息是有类型的! - -```ts -interface AllEvents { - event1: { x: number; y: number } -} -const mc = new MessageCenter() -mc.on // 只接受监听 keyof AllEvents -mc.send // 只允许发送 keyof AllEvents, 且 data 需要是 AllEvents[key] -``` - -## `constructor(key?: string)` - -- key?: 可随意指定,但需要在各个实例之间保持一致。 - -## `.on(event, handler)` - -接受事件。 - -## `.on(event, data)` - -发送事件。 - -## `.writeToConsole` - -发送、接受事件时打印到控制台。 - -# AsyncCall - -在不同环境之间进行远程过程调用。 - -一个 MessageCenter 的高阶抽象。 - -> 两端共享的代码 - -- 如何对参数和返回值进行序列化、反序列化的代码应该在两端共享,默认值为不进行序列化。 -- `key` should be shared. - -> 甲端 - -- 应该提供一些函数供另一端调用。(例如, `BackgroundCalls`) -- `const call = AsyncCall(backgroundCalls)` -- 然后你就能把 `call` 当作 `ForegroundCalls` 类型的对象来调用另一端的代码了。 - -> 乙端 - -- 应该提供一些函数供另一端调用。(例如, `ForegroundCalls`) -- `const call = AsyncCall(foregroundCalls)` -- 然后你就能把 `call` 当作 `BackgroundCalls` 类型的对象来调用另一端的代码了。 - -_提示: 两端可以定义同一个函数_ - -例子: - -```typescript -// Mono repo -// UI 一端 -const UI = { - async dialog(text: string) { - alert(text) - }, -} -export type UI = typeof UI -const callsClient = AsyncCall(UI) -callsClient.sendMail('hello world', 'what') - -// 服务器一端 -const Server = { - async sendMail(text: string, to: string) { - return true - }, -} -export type Server = typeof Server -const calls = AsyncCall(Server) -calls.dialog('hello') -``` - -## 选项 - -- key: 一个 Key,以防与其他同一信道上通信的 AsyncCall 冲突,可以是任何内容,但需要在两端一致。(默认为 `default`) -- serializer: 如何序列化、反序列化参数和返回值。(默认为 `NoSerialization`) -- MessageCenter: 一个消息中心,作为通信信道,只要实现了对应接口即可使用任何信道通信,比如 `WebSocket` `chrome.runtime` 等。(默认为 `@holoflows/kit` 自带的 `MessageCenter`) -- dontThrowOnNotImplemented: 如果本端收到了远程调用,但本端没有实现该函数时,是否忽略错误。(默认为 `true`) -- writeToConsole: 是否把所有调用输出到控制台以便调试。(默认为 `true`) - -## 序列化 - -有一些内置的序列化办法: - -- `NoSerialization` (不进行任何序列化) -- `JSONSerialization` (使用 JSON.parse/stringify 进行序列化) (你可以提供一个 `replacer`!见 [JSON.stringify](https://mdn.io/JSON.stringify)) - -你也可以实现自己的序列化,只需要实现 `Serialization` 接口即可。 - -## 返回值 - -返回一个类型为 `OtherSideImplementedFunctions` 的对象。 - -# AutomatedTabTask - -基于 AsyncCall。打开一个新标签页,执行一些任务,然后自动关闭标签页。 - -例子: - -> 在 content script 中(一定要在你需要执行任务的页面里注入!): - -```ts -export const task = AutomatedTabTask({ - async taskA() { - return 'Done!' - }, -}) -``` - -> 在背景页中: - -```ts -import { task } from '...' -task('https://example.com/').taskA() -// 打开 https://example.com/,在上面运行 taskA(),等待返回结果('Done!')然后自动关闭页面 -``` - -## 参数 - -- taskImplements: Content script 能执行的任务。 -- AsyncCallKey: 一个 Key,默认对每个插件不同。 - -## 返回值 - -- 在 content script 上为 `null` -- 在背景页上为 `typeof taskImplements` - -# Contexts - -辨别当前运行的上下文 - -## type `Contexts` - -- background: 背景页 -- content: 内容脚本 -- unknown: 未知 - -## `GetContext():`Contexts - -获取当前上下文 - -## `OnlyRunInContext`(context: Contexts | Contexts[], name: string) - -只允许本段代码在某个上下文中运行,否则报错。 - -- name 是报错时提供的名字。 diff --git a/doc/zh-CN/index.md b/doc/zh-CN/index.md index ef4d41f..5510feb 100644 --- a/doc/zh-CN/index.md +++ b/doc/zh-CN/index.md @@ -6,8 +6,20 @@ @holoflows/kit 由以下部分组成 -- [DOM](./DOM.md) - 帮助扩展开发者追踪被注入网页中的内容变化 -- [Extension](./Extension.md) - 一些扩展开发会使用到的实用工具 +- [DOM 相关的工具的教程](./DOM.md) - 帮助扩展开发者追踪被注入网页中的内容变化 +- - [DomProxy](../../api-documents/kit.domproxy.md) 用于持续追踪网页变化而不丢失引用和副作用 +- - [LiveSelector](../../api-documents/kit.liveselector.md) 用于持续选择网页中的元素。 +- - `abstract` [Watcher](../../api-documents/kit.watcher.md) +- - - [MutationObserverWatcher](../../api-documents/kit.mutationobserverwatcher.md) 通过 `MutationObserver` 追踪网页中的变化 +- - - [IntervalWatcher](../../api-documents/kit.intervalwatcher.md) 通过 `setInterval` 追踪网页中的变化 +- - - [EventWatcher](../../api-documents/kit.eventwatcher.md) 通过事件回调追踪网页中的变化 +- - [ValueRef](../../api-documents/kit.valueref.md) 通过 `setter` 订阅值的变化 + +- Extension - 一些扩展开发会使用到的实用工具 +- - [AsyncCall](../../api-documents/kit.asynccall.md) 跨上下文的远程过程调用工具 +- - [Context](../../api-documents/kit.context.md) 在扩展开发中检测当前的上下文 +- - [MessageCenter](../../api-documents/kit.messagecenter.md) 在插件开发的不同上下文中通信 +- - [AutomatedTabTask](../../api-documents/kit.automatedtabtask.md) 打开一个新的标签页,自动执行某些任务,然后回传结果 ## 安装 @@ -17,6 +29,8 @@ 使用模块加载器时,使用 `@holoflows/kit` 导入;使用 umd 时,使用 `window.HoloflowsKit`。 +如果在 Chrome 中使用,需要加载 WebExtension 的 Polyfill (`webextension-polyfill`)。 + ### ECMAScript 版本 因为本库重度依赖 [Proxy](https://mdn.io/Proxy),所以支持 ES5 没有意义。 diff --git a/package.json b/package.json index d7954e1..152834b 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,26 @@ { "name": "@holoflows/kit", - "version": "0.2.0", + "version": "0.3.0", "module": "./es/index.js", "main": "./dist/out.js", "typings": "./es/", + "homepage": "https://github.com/project-holoflows/holoflows-kit", + "bugs": { + "url": "https://github.com/project-holoflows/holoflows-kit/issues" + }, + "readme": "https://github.com/project-holoflows/holoflows-kit/blob/master/README.md", "dependencies": { - "@types/chrome": "^0.0.81", "@types/lodash-es": "^4.1.4", "@types/node": "10.12.23", + "concurrent-lock": "^1.0.7", "events": "^3.0.0", "immer": "^2.1.3", "lodash-es": "^4.1.2", - "reflect-metadata": "^0.1.13" + "memorize-decorator": "^0.2.2", + "reflect-metadata": "^0.1.13", + "web-ext-types": "^3.1.0" }, + "sideEffects": false, "scripts": { "start": "npm-run-all --parallel start-tsc start-rollup", "start-tsc": "tsc --watch", @@ -21,9 +29,14 @@ "build-tsc": "tsc", "build-rollup": "rollup -c", "clean": "rimraf ./es ./dist", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "apigen": "api-extractor run --local --verbose", + "mdgen": "api-documenter markdown -i temp -o api-documents", + "gendoc": "npm-run-all --serial build-tsc apigen mdgen" }, "devDependencies": { + "@microsoft/api-documenter": "^7.1.6", + "@microsoft/api-extractor": "^7.1.4", "npm-run-all": "^4.1.5", "rimraf": "^2.6.3", "rollup": "^1.1.2", @@ -33,5 +46,8 @@ "rollup-plugin-typescript2": "^0.19.2", "typescript": "^3.4.1" }, + "peerDependencies": { + "webextension-polyfill": "^0.4.0" + }, "license": "AGPL-3.0-or-later" } diff --git a/src/DOM/LiveSelector.ts b/src/DOM/LiveSelector.ts index 0984ad5..c6cfd20 100644 --- a/src/DOM/LiveSelector.ts +++ b/src/DOM/LiveSelector.ts @@ -1,57 +1,143 @@ -type RecordType = { type: T; param: F } /** * Define all possible recordable operations. */ interface SelectorChainType { - querySelector: RecordType<'querySelector', string> - querySelectorAll: RecordType<'querySelectorAll', string> - filter: RecordType<'filter', (element: any, index: number, array: any[]) => boolean> - map: RecordType<'map', (element: any) => any> - concat: RecordType<'concat', LiveSelector> - reverse: RecordType<'reverse', undefined> - slice: RecordType<'slice', [number | undefined, number | undefined]> - sort: RecordType<'sort', (a: any, b: any) => number> - flat: RecordType<'flat', undefined> - nth: RecordType<'nth', number> - replace: RecordType<'replace', (array: any[]) => any[]> + getElementsByClassName: string + getElementsByTagName: string + querySelector: string + closest: string | number + querySelectorAll: string + filter: (element: any, index: number, array: any[]) => boolean + map: (element: any) => any + concat: LiveSelector + reverse: undefined + slice: [number | undefined, number | undefined] + sort: ((a: any, b: any) => number) | undefined + flat: undefined + nth: number + replace: (array: any[]) => any[] } -type Keys = SelectorChainType[keyof SelectorChainType]['type'] -type Params = { [key in keyof SelectorChainType]: SelectorChainType[key]['param'] } +type MapOf< + Original, + Type = { + [T in keyof Original]: { + type: T + param: Original[T] + } + }, + EachType = Type[keyof Type] +> = EachType +type SelectorChainTypeItem = MapOf + /** - * Create a live selector that can continuously select the element you want + * Create a live selector that can continuously select the element you want. + * + * @remarks + * Call {@link LiveSelector.evaluateOnce | #evaluateOnce} to evaluate the element. Falsy value will be ignored. * - * call `#evaluateOnce` to evaluate the element. Falsy will be ignored. + * @param T - Type of Element that LiveSelector contains */ export class LiveSelector { - private generateMethod = (type: Key) => (param: Params[Key]): LiveSelector => { + /** + * Record a method call into {@link LiveSelector.selectorChain} + */ + private appendSelectorChain = (type: Key) => ( + param: SelectorChainType[Key], + ): LiveSelector => { this.selectorChain.push({ type: type as any, param: param as any }) return this as LiveSelector } - private readonly selectorChain: (SelectorChainType[keyof SelectorChainType])[] = [] + /** + * Records of previous calls on LiveSelector + */ + private readonly selectorChain: SelectorChainTypeItem[] = [] + /** + * Clone this LiveSelector and return a new LiveSelector. + * @returns a new LiveSelector with same action + * @example + * ```ts + * ls.clone() + * ``` + */ + clone() { + const ls = new LiveSelector() + ls.selectorChain.push(...this.selectorChain) + return ls + } //#region Add elements /** * Select the first element that is a descendant of node that matches selectors. * - * @example ```ts - * ls.querySelector('div#root')``` + * @param selector - Selector + * + * @example + * ```ts + * ls.querySelector('div#root') + * ``` */ - querySelector(selectors: K): LiveSelector - querySelector(selectors: K): LiveSelector - querySelector(selectors: string): LiveSelector - querySelector(selectors: string): LiveSelector { - return this.generateMethod('querySelector')(selectors) + querySelector(selector: K): LiveSelector + querySelector(selector: K): LiveSelector + querySelector(selector: string): LiveSelector + querySelector(selector: string): LiveSelector { + return this.appendSelectorChain('querySelector')(selector) } /** * Select all element descendants of node that match selectors. * - * @example ```ts - * ls.querySelector('div > div')``` + * @param selector - Selector + * @example + * ```ts + * ls.querySelector('div > div') + * ``` + */ + querySelectorAll(selector: K): LiveSelector + querySelectorAll(selector: K): LiveSelector + querySelectorAll(selector: string): LiveSelector + querySelectorAll(selector: string): LiveSelector { + return this.appendSelectorChain('querySelectorAll')(selector) + } + /** + * Select all element base on the current result. + * @param className - Class name + * @example + * Equal to ls.querySelectorAll('.a .b') + * ```ts + * ls.getElementsByClassName('a').getElementsByClassName('b') + * ``` + */ + getElementsByClassName(className: string): LiveSelector + getElementsByClassName(className: string): LiveSelector { + return this.appendSelectorChain('getElementsByClassName')(className) + } + /** + * Select all element base on the current result. + * @param tag - Tag name + * @example + * Equal to ls.querySelectorAll('a b') + * ```ts + * ls.getElementsByTagName('a').getElementsByTagName('b') + * ``` */ - querySelectorAll(selectors: K): LiveSelector - querySelectorAll(selectors: K): LiveSelector - querySelectorAll(selectors: string): LiveSelector - querySelectorAll(selectors: string): LiveSelector { - return this.generateMethod('querySelectorAll')(selectors) + getElementsByTagName(tag: K): LiveSelector + getElementsByTagName(tag: K): LiveSelector + getElementsByTagName(tag: string): LiveSelector + getElementsByTagName(tag: string): LiveSelector { + return this.appendSelectorChain('getElementsByTagName')(tag) + } + /** + * Reversely select element in the parent + * @example + * ```ts + * ls.closest('div') + * ls.closest(2) // parentElement.parentElement + * ``` + */ + closest(parentOfNth: number): LiveSelector + closest(selectors: K): LiveSelector + closest(selectors: K): LiveSelector + closest(selectors: string): LiveSelector + closest(selectors: string | number): LiveSelector { + return this.appendSelectorChain('closest')(selectors) } //#endregion @@ -59,105 +145,191 @@ export class LiveSelector { /** * Select the elements of a LiveSelector that meet the condition specified in a callback function. * - * @example ```ts - * ls.filter(x => x.innerText.match('hello'))``` + * @param f - The filter method + * + * @example + * ```ts + * ls.filter(x => x.innerText.match('hello')) + * ``` */ - filter: ((f: (value: T, index: number, array: T[]) => value is S) => LiveSelector) & - ((f: (value: T, index: number, array: T[]) => any) => LiveSelector>) = this.generateMethod( - 'filter', - ) + filter(f: (value: T, index: number, array: T[]) => any): LiveSelector> + filter(f: (value: T, index: number, array: T[]) => value is S): LiveSelector { + return this.appendSelectorChain('filter')(f) + } /** * Calls a defined callback function on each element of a LiveSelector, and continues with the results. * - * @example ```ts - * ls.map(x => x.parentElement)``` + * @param callbackfn - Map function + * @example + * ```ts + * ls.map(x => x.parentElement) + * ``` */ - map: (callbackfn: (element: T) => NextType) => LiveSelector> = this.generateMethod( - 'map', - ) + map(callbackfn: (element: T) => NextType): LiveSelector> { + return this.appendSelectorChain('map')(callbackfn) + } /** * Combines two LiveSelector. - * @param item Additional LiveSelector to combine. + * @param newEle - Additional LiveSelector to combine. + * @param NextType - Next type generic for LiveSelector * - * @example ```ts - * ls.concat(new LiveSelector().querySelector('#root'))``` + * @example + * ```ts + * ls.concat(new LiveSelector().querySelector('#root')) + * ``` */ - concat: (newEle: LiveSelector) => LiveSelector = this.generateMethod('concat') + concat(newEle: LiveSelector): LiveSelector { + return this.appendSelectorChain('concat')(newEle) + } /** * Reverses the elements in an Array. * - * @example ```ts - * ls.reverse()``` + * @example + * ```ts + * ls.reverse() + * ``` */ reverse(): LiveSelector { - return this.generateMethod('reverse')(undefined) + return this.appendSelectorChain('reverse')(undefined) } /** * Returns a section of an array. - * @param start The beginning of the specified portion of the array. - * @param end The end of the specified portion of the array. + * @param start - The beginning of the specified portion of the array. + * @param end - The end of the specified portion of the array. * - * @example ```ts - * ls.slice(2, 4)``` + * @example + * ```ts + * ls.slice(2, 4) + * ``` */ - slice: (start?: number, end?: number) => LiveSelector = (a, b) => this.generateMethod('slice')([a, b]) + slice(start?: number, end?: number): LiveSelector { + return this.appendSelectorChain('slice')([start, end]) + } /** * Sorts an array. - * @param compareFn The name of the function used to determine the order of the elements. If omitted, the elements are sorted in ascending, ASCII character order. + * @param compareFn - The name of the function used to determine the order of the elements. If omitted, the elements are sorted in ascending, ASCII character order. * - * @example ```ts - * ls.sort((a, b) => a.innerText.length - b.innerText.length)``` + * @example + * ```ts + * ls.sort((a, b) => a.innerText.length - b.innerText.length) + * ``` */ - sort: (compareFn?: (a: T, b: T) => number) => LiveSelector = this.generateMethod('sort') + sort(compareFn?: (a: T, b: T) => number): LiveSelector { + return this.appendSelectorChain('sort')(compareFn) + } /** * Flat T[][] to T[] * - * @example ```ts - * ls.flat()``` + * @example + * ```ts + * ls.flat() + * ``` */ flat(): LiveSelector ? U : never> { - return this.generateMethod('flat')(undefined) + return this.appendSelectorChain('flat')(undefined) } /** * Select only nth element * - * @example ```ts - * ls.nth(-1)``` + * @param n - Select only nth element, allow negative number. + * @example + * ```ts + * ls.nth(-1) + * ``` */ nth(n: number): LiveSelector { - return this.generateMethod('nth')(n) + return this.appendSelectorChain('nth')(n) } /** * Replace the whole array. * - * @example ```typescript - * liveselector.replace(x => lodash.dropRight(x, 2))``` + * @example + * ```ts + * ls.replace(x => lodash.dropRight(x, 2)) + * ``` * - * @param f returns new array. + * @param f - returns new array. */ - replace: (f: (arr: T[]) => NextType[]) => LiveSelector = this.generateMethod('replace') + replace(f: (arr: T[]) => NextType[]): LiveSelector { + return this.appendSelectorChain('replace')(f) + } //#endregion //#region Build /** * Evaluate selector expression */ - evaluateOnce() { - let arr: T[] = [] + evaluateOnce(): T[] { + let arr: (T | Element)[] = [] + function isElementArray(x: any[]): x is Element[] { + // Do a simple check + return x[0] instanceof Element + } + function nonNull(x: T | null | undefined): x is T { + return x !== null && x !== undefined + } + let previouslyNulled = false for (const op of this.selectorChain) { switch (op.type) { - case 'querySelector': - const e = document.querySelector(op.param) - e && arr.push(e) + case 'querySelector': { + if (!previouslyNulled) { + if (arr.length === 0) { + const e = document.querySelector(op.param) + if (e) arr.push(e) + else previouslyNulled = true + } else if (isElementArray(arr)) { + arr = arr.map(e => e.querySelector(op.param)).filter(nonNull) + if (arr.length === 0) previouslyNulled = true + } else throw new TypeError('Call querySelector on non-Element item!') + } break - case 'querySelectorAll': - arr.push(...document.querySelectorAll(op.param)) + } + case 'getElementsByTagName': + case 'getElementsByClassName': + case 'querySelectorAll': { + if (!previouslyNulled) { + type F = (x: string) => NodeListOf | HTMLCollectionOf + if (arr.length === 0) { + const e = (document[op.type] as F)(op.param) + arr.push(...e) + if (e.length === 0) previouslyNulled = true + } else if (isElementArray(arr)) { + let newArr: Element[] = [] + for (const e of arr) { + newArr = newArr.concat(Array.from((e[op.type] as F)(op.param))) + } + arr = newArr.filter(nonNull) + if (arr.length === 0) previouslyNulled = true + } else throw new TypeError(`Call ${op.type} on non-Element item!`) + } + break + } + case 'closest': + if (arr.length === 0) { + break + } else if (isElementArray(arr)) { + const newArr: Element[] = arr + const selector = op.param + function findParent(node: Element, y: number): Element | null { + if (y < 0) throw new TypeError('Cannot use `.closet` with a negative number') + if (y === 0) return node + if (!node.parentElement) return null + return findParent(node.parentElement, y - 1) + } + if (typeof selector === 'number') { + arr = newArr.map(e => findParent(e, selector)).filter(nonNull) + } else { + arr = newArr.map(x => x.closest(selector)).filter(nonNull) + } + } else { + throw new TypeError('Cannot use `.closet on non-Element`') + } break case 'filter': - arr = arr.filter(op.param).filter(x => x !== null) + arr = arr.filter(op.param).filter(nonNull) break case 'map': - arr = arr.map(op.param).filter(x => x !== null) + arr = arr.map(op.param).filter(nonNull) break case 'concat': arr = arr.concat(op.param.evaluateOnce()) @@ -165,22 +337,21 @@ export class LiveSelector { case 'reverse': arr = arr.reverse() break - case 'slice': - arr = arr.slice(op.param[0], op.param[1]) + case 'slice': { + const [start, end] = op.param + arr = arr.slice(start, end) break + } case 'sort': arr = arr.sort(op.param) break - case 'nth': + case 'nth': { const x = op.param >= 0 ? op.param : arr.length - op.param arr = [arr[x]] break + } case 'flat': - if (Array.isArray(arr[0])) { - const newArr: any[] = [] - arr.forEach(x => newArr.push(...(x as any))) - arr = newArr - } + arr = ([] as typeof arr).concat(...arr) break case 'replace': arr = op.param(arr) diff --git a/src/DOM/Proxy.ts b/src/DOM/Proxy.ts index 1746d9a..e6d5ad1 100644 --- a/src/DOM/Proxy.ts +++ b/src/DOM/Proxy.ts @@ -1,6 +1,18 @@ +/** + * Options for DomProxy + */ +export interface DomProxyOptions { + /** Create the `before` node of the DomProxy */ createBefore(): Before + /** Create the `after` node of the DomProxy */ createAfter(): After + /** ShadowRootInit for creating the shadow of `before` */ beforeShadowRootInit: ShadowRootInit + /** ShadowRootInit for creating the shadow of `after` */ afterShadowRootInit: ShadowRootInit +} + /** * DomProxy provide an interface that be stable even dom is changed. * + * @remarks + * * DomProxy provide 3 nodes. `before`, `current` and `after`. * `current` is a fake dom node powered by Proxy, * it will forward all your operations to the `realCurrent`. @@ -15,16 +27,33 @@ * * *move*: move effect to new `realCurrent` * - * - style (forward, no-undo, move) + * - style (forward, undo, move) * - addEventListener (forward, undo, move) * - appendChild (forward, undo, move) */ -export const DomProxy = function() { +export function DomProxy< + ProxiedElement extends Element = HTMLElement, + Before extends Element = HTMLSpanElement, + After extends Element = HTMLSpanElement +>(options: Partial> = {}): DomProxy { + // Options + const { createAfter, createBefore, afterShadowRootInit, beforeShadowRootInit } = { + ...({ + createAfter: () => document.createElement('span'), + createBefore: () => document.createElement('span'), + afterShadowRootInit: { mode: 'open' }, + beforeShadowRootInit: { mode: 'open' }, + } as DomProxyOptions), + ...options, + } as DomProxyOptions + // let isDestroyed = false - - let virtualBefore: HTMLSpanElement | null = null + // Nodes + let virtualBefore: Before | null = null + let virtualBeforeShadow: ShadowRoot | null = null let current: Element | null = document.createElement('div') - let virtualAfter: HTMLSpanElement | null = null + let virtualAfter: After | null = null + let virtualAfterShadow: ShadowRoot | null = null /** All changes applied on the `proxy` */ let changes: (ActionTypes[keyof ActionTypes])[] = [] /** Read Traps */ @@ -42,13 +71,16 @@ export const DomProxy = function() { return new Proxy(current_[key], { apply: (target, thisArg, args) => { changes.push({ type: 'callMethods', op: { name: key, param: args, thisArg } }) - current_[key](...args) + return current_[key](...args) }, }) else if (key === 'style') return new Proxy((current as HTMLElement).style, { set: (t, styleKey, styleValue, r) => { - changes.push({ type: 'modifyStyle', op: { name: styleKey, value: styleValue } }) + changes.push({ + type: 'modifyStyle', + op: { name: styleKey, value: styleValue, originalValue: current_.style[styleKey] }, + }) current_.style[styleKey] = styleValue return true }, @@ -122,12 +154,28 @@ export const DomProxy = function() { const modifyTrapsWrite = modifyTraps(true) const modifyTrapsNotWrite = modifyTraps(false) const proxy = Proxy.revocable({}, { ...readonlyTraps, ...modifyTrapsWrite }) + function hasStyle(e: Element): e is HTMLElement { + return !!(e as any).style + } /** Call before realCurrent change */ - function undoEffects() { + function undoEffects(nextCurrent?: Element | null) { for (const change of changes) { - if (change.type !== 'callMethods') continue - if (change.op.name !== 'addEventListener') continue - current && current.removeEventListener(...(change.op.param as [any, any, any])) + if (change.type === 'callMethods') { + const attr: keyof HTMLElement = change.op.name as any + if (attr === 'addEventListener') { + current && current.removeEventListener(...(change.op.param as [any, any, any])) + } else if (attr === 'appendChild') { + if (!nextCurrent) { + const node = (change.op.thisArg as Parameters)[0] + current && node && current.removeChild(node) + } + } + } else if (change.type === 'modifyStyle') { + const { name, value, originalValue } = change.op + if (current && hasStyle(current)) { + current.style[name as any] = originalValue + } + } } } /** Call after realCurrent change */ @@ -152,45 +200,83 @@ export const DomProxy = function() { } } } + // MutationObserver + const noop: MutationCallback = () => {} + let observerCallback = noop + let mutationObserverInit: MutationObserverInit | undefined = undefined + let observer: MutationObserver | null = null + function reObserve(reinit: boolean) { + observer && observer.disconnect() + if (observerCallback === noop || !current) return + if (reinit || !observer) observer = new MutationObserver(observerCallback) + observer.observe(current, mutationObserverInit) + } return { - /** - * A `span` element that always located at the before of `realCurrent` - */ - get before(): HTMLSpanElement { + observer: { + set callback(v) { + if (v === undefined) v = noop + observerCallback = v + reObserve(true) + }, + get callback() { + return observerCallback + }, + get init() { + return mutationObserverInit + }, + set init(v) { + mutationObserverInit = v + reObserve(false) + }, + get observer() { + return observer + }, + }, + get weakBefore() { + if (isDestroyed) return null + return virtualBefore + }, + get before() { if (isDestroyed) throw new TypeError('Try to access `before` node after VirtualNode is destroyed') if (!virtualBefore) { - virtualBefore = document.createElement('span') + virtualBefore = createBefore() current && current.before(virtualBefore) } return virtualBefore }, - /** - * A proxy that always point to `realCurrent`, - * and if `realCurrent` changes, all action will be forwarded to new `realCurrent` - */ - get current(): HTMLSuperSet { + get beforeShadow(): ShadowRoot { + if (!virtualBeforeShadow) virtualBeforeShadow = this.before.attachShadow(beforeShadowRootInit) + return virtualBeforeShadow + }, + get current(): ProxiedElement { if (isDestroyed) throw new TypeError('Try to access `current` node after VirtualNode is destroyed') - return proxy.proxy as HTMLSuperSet + return proxy.proxy + }, + get weakAfter() { + if (isDestroyed) return null + return virtualAfter }, - /** - * A `span` element that always located at the after of `current` - */ - get after(): HTMLSpanElement { + get after(): After { if (isDestroyed) throw new TypeError('Try to access `after` node after VirtualNode is destroyed') if (!virtualAfter) { - virtualAfter = document.createElement('span') + virtualAfter = createAfter() current && current.after(virtualAfter) } return virtualAfter }, - get realCurrent() { + get afterShadow(): ShadowRoot { + if (!virtualAfterShadow) virtualAfterShadow = this.after.attachShadow(afterShadowRootInit) + return virtualAfterShadow + }, + get realCurrent(): ProxiedElement | null { if (isDestroyed) return null return current as any }, - set realCurrent(node: Element | null | undefined) { + set realCurrent(node: ProxiedElement | null) { if (isDestroyed) throw new TypeError('You can not set current for a destroyed proxy') if (node === current) return - undoEffects() + undoEffects(node) + reObserve(false) if (node === null || node === undefined) { current = document.createElement('div') if (virtualBefore) virtualBefore.remove() @@ -203,8 +289,11 @@ export const DomProxy = function() { } }, destroy() { + observer && observer.disconnect() isDestroyed = true proxy.revoke() + virtualBeforeShadow = null + virtualAfterShadow = null if (virtualBefore) virtualBefore.remove() if (virtualAfter) virtualAfter.remove() virtualBefore = null @@ -213,92 +302,48 @@ export const DomProxy = function() { }, } } -export type DomProxy = ReturnType -//#region HTMLSuperSet -type HTMLSuperSet = HTMLElement & - HTMLAnchorElement & - HTMLAppletElement & - HTMLAreaElement & - HTMLAudioElement & - HTMLBaseElement & - HTMLBaseFontElement & - HTMLQuoteElement & - HTMLBodyElement & - HTMLBRElement & - HTMLButtonElement & - HTMLCanvasElement & - HTMLTableCaptionElement & - HTMLTableColElement & - HTMLTableColElement & - HTMLDataElement & - HTMLDataListElement & - HTMLModElement & - HTMLDetailsElement & - HTMLDialogElement & - HTMLDirectoryElement & - HTMLDivElement & - HTMLDListElement & - HTMLEmbedElement & - HTMLFieldSetElement & - HTMLFontElement & - HTMLFormElement & - HTMLFrameElement & - HTMLFrameSetElement & - HTMLHeadingElement & - HTMLHeadingElement & - HTMLHeadingElement & - HTMLHeadingElement & - HTMLHeadingElement & - HTMLHeadingElement & - HTMLHeadElement & - HTMLHRElement & - HTMLHtmlElement & - HTMLIFrameElement & - HTMLImageElement & - HTMLInputElement & - HTMLModElement & - HTMLLabelElement & - HTMLLegendElement & - HTMLLIElement & - HTMLLinkElement & - HTMLMapElement & - HTMLMarqueeElement & - HTMLMenuElement & - HTMLMetaElement & - HTMLMeterElement & - HTMLObjectElement & - HTMLOListElement & - HTMLOptGroupElement & - HTMLOptionElement & - HTMLOutputElement & - HTMLParagraphElement & - HTMLParamElement & - HTMLPictureElement & - HTMLPreElement & - HTMLProgressElement & - HTMLQuoteElement & - HTMLScriptElement & - HTMLSelectElement & - HTMLSlotElement & - HTMLSourceElement & - HTMLSpanElement & - HTMLStyleElement & - HTMLTableElement & - HTMLTableSectionElement & - HTMLTableDataCellElement & - HTMLTemplateElement & - HTMLTextAreaElement & - HTMLTableSectionElement & - HTMLTableHeaderCellElement & - HTMLTableSectionElement & - HTMLTimeElement & - HTMLTitleElement & - HTMLTableRowElement & - HTMLTrackElement & - HTMLUListElement & - HTMLVideoElement & - HTMLElement -//#endregion +/** + * A DomProxy object + */ +export interface DomProxy< + ProxiedElement extends Element = HTMLElement, + Before extends Element = HTMLSpanElement, + After extends Element = HTMLSpanElement +> { + /** Destroy the DomProxy */ + destroy(): void + /** Returns the `before` element without implicitly create it. */ + readonly weakBefore: Before | null + /** Returns the `before` element, if it doesn't exist, create it implicitly. */ + readonly before: Before + /** Returns the `ShadowRoot` of the `before` element. */ + readonly beforeShadow: ShadowRoot + /** + * A proxy that always point to `realCurrent`, + * and if `realCurrent` changes, all action will be forwarded to new `realCurrent` + */ + readonly current: ProxiedElement + /** Returns the `after` element without implicitly create it. */ + readonly weakAfter: After | null + /** Returns the `after` element, if it doesn't exist, create it implicitly. */ + readonly after: After + /** Returns the `ShadowRoot` of the `after` element. */ + readonly afterShadow: ShadowRoot + /** + * The real current of the `current` + */ + realCurrent: ProxiedElement | null + /** + * Observer for the current node. + * You need to set callback and init to activate it. + */ + readonly observer: { + readonly observer: MutationObserver | null + callback: MutationCallback | undefined + init: MutationObserverInit | undefined + } +} + type Keys = string | number | symbol type ActionRecord = { type: T; op: F } interface ActionTypes { @@ -314,5 +359,5 @@ interface ActionTypes { isExtensible: ActionRecord<'isExtensible', undefined> getPrototypeOf: ActionRecord<'getPrototypeOf', undefined> callMethods: ActionRecord<'callMethods', { name: Keys; param: any[]; thisArg: any }> - modifyStyle: ActionRecord<'modifyStyle', { name: Keys; value: string }> + modifyStyle: ActionRecord<'modifyStyle', { name: Keys; value: string; originalValue: string }> } diff --git a/src/DOM/Watcher.ts b/src/DOM/Watcher.ts index 793e8b8..6e75f15 100644 --- a/src/DOM/Watcher.ts +++ b/src/DOM/Watcher.ts @@ -9,97 +9,138 @@ * - Interval watcher (based on time interval) * - Event watcher (based on addEventListener) */ -import { DomProxy } from './Proxy' +import { DomProxy, DomProxyOptions } from './Proxy' import { EventEmitter } from 'events' import { LiveSelector } from './LiveSelector' -// import { differenceWith, intersectionWith, uniqWith } from 'lodash-es' import differenceWith from 'lodash-es/differenceWith' import intersectionWith from 'lodash-es/intersectionWith' import uniqWith from 'lodash-es/uniqWith' -//#region Interface for Watcher -type RequireNode = T extends Element ? V : never -interface SingleNodeWatcher { - /** The virtual node always point to the first result of the LiveSelector */ - firstVirtualNode: RequireNode +type RequireElement = T extends Element ? V : never +type ElementLikeT = T extends Element ? T : never +interface Deadline { + didTimeout: boolean + timeRemaining(): number } -type useWatchCallback = +/** + * Return value of useNodeForeach + */ +type useNodeForeachReturns = | void | ((oldNode: T) => void) | { onRemove?: (old: T) => void onTargetChanged?: (oldNode: T, newNode: T) => void - onNodeMutation?: (node: T) => void + onNodeMutation?: (node: T, mutations: MutationRecord[]) => void } -/** Watcher for multiple node */ -interface MultipleNodeWatcher { - /** - * To help identify same nodes in different iteration, - * you need to implement a map function that map `node` => `key` - * - * If the key is changed, the same node will call through `forEachRemove` then `forEach` - * - * *Param map*: `node` => `key`, defaults to `node => node` - * - * *Param comparer*: compare between two keys, defaults to `===` - */ - assignKeys: ( - assigner: (node: T, index: number, arr: T[]) => Q, - comparer?: (a: Q, b: Q) => boolean, - ) => Watcher - useNodeForeach: RequireNode< - T, - (fn: (virtualNode: DomProxy, key: unknown, realNode: T) => useWatchCallback) => Watcher - > - /** - * Get virtual node by key. - * Virtual node will be unavailable if it is deleted - */ - getVirtualNodeByKey: RequireNode DomProxy> -} -//#endregion -type EventFn = (fn: CustomEvent & { data: T }) => void +type EventCallback = (fn: Event & { data: T }) => void /** * Use LiveSelector to watch dom change * - * @abstract You need to implement `startWatch` and `stopWatch` + * You need to implement `startWatch` */ -export abstract class Watcher extends EventEmitter implements SingleNodeWatcher, MultipleNodeWatcher { - protected readonly nodeWatcher: MutationWatcherHelper = new MutationWatcherHelper(this) +export abstract class Watcher< + T, + DomProxyBefore extends Element = HTMLSpanElement, + DomProxyAfter extends Element = HTMLSpanElement +> { + /** Event emitter */ + protected readonly eventEmitter = new EventEmitter() constructor(protected liveSelector: LiveSelector) { - super() - this.nodeWatcher.callback = (key, node) => { - for (const [invariantKey, callbacks] of this.lastCallbackMap.entries()) { - if (this.keyComparer(key, invariantKey)) { - if (typeof callbacks === 'object' && callbacks.onNodeMutation) { - callbacks.onNodeMutation(node as any) - } - } - } + if ('requestIdleCallback' in window) { + this.requestIdleCallback = (...args: any) => (window as any)['requestIdleCallback'](...args) + } + } + //#region DomProxy options + protected domProxyOption: Partial> = {} + /** + * Set option for DomProxy + * @param option - DomProxy options + */ + setDomProxyOption(option: Partial>): this { + this.domProxyOption = option + return this + } + //#endregion + //#region Watch once + /** + * Run the Watcher once. Once it emit data, stop watching. + * @param fn - Map function transform T to Result + * @param options - Options for watcher + * @param starter - How to start the watcher + */ + once(): Promise + once(fn: (data: T) => PromiseLike | Result): Promise + once( + fn: (data: T) => PromiseLike | Result = data => (data as any) as Result, + options: Partial<{ + minimalResultsRequired: number + }> = {}, + starter: (this: this, self: this) => void = watcher => watcher.startWatch(), + ): Promise { + const { minimalResultsRequired } = { + ...({ + minimalResultsRequired: 1, + } as Required), + ...options, } + const r = this.liveSelector.evaluateOnce() + if (r.length >= minimalResultsRequired) return Promise.resolve(Promise.all(r.map(fn))) + return new Promise((resolve, reject) => { + starter.bind(this)(this) + const f: EventCallback = e => { + if (e.data.length < minimalResultsRequired) return + this.stopWatch() + Promise.all(e.data.map(fn)).then(resolve, reject) + this.removeListener('onChangeFull', f) + } + this.addListener('onChangeFull', f) + }) } + //#endregion abstract startWatch(...args: any[]): this - abstract stopWatch(...args: any[]): void + stopWatch(...args: any[]): void { + this.watching = false + } //#region Watcher /** Is the watcher running */ + protected requestIdleCallback(fn: (t: Deadline) => void, timeout?: { timeout: number }) { + const start = Date.now() + return setTimeout(() => { + fn({ + didTimeout: false, + timeRemaining: function() { + return Math.max(0, 50 - (Date.now() - start)) + }, + }) + }, 1) + } protected watching = false /** Found key list of last watch */ protected lastKeyList: unknown[] = [] /** Found Node list of last watch */ protected lastNodeList: T[] = [] /** Saved callback map of last watch */ - protected lastCallbackMap = new Map>() + protected lastCallbackMap = new Map>() /** Saved virtual node of last watch */ - protected lastVirtualNodesMap = new Map() + protected lastVirtualNodesMap = new Map, DomProxyBefore, DomProxyAfter>>() /** Find node from the given list by key */ protected findNodeFromListByKey = (list: T[], keys: unknown[]) => (key: unknown) => { const i = keys.findIndex(x => this.keyComparer(x, key)) if (i === -1) return null return list[i] } + protected _omitWarningForRepeatedKeys = false + /** + * If you're expecting repeating keys, call this function, this will omit the warning. + */ + omitWarningForRepeatedKeys() { + this._omitWarningForRepeatedKeys = true + return this + } /** Should be called every watch */ - protected watcherCallback = () => { + protected watcherCallback = (deadline?: Deadline) => { if (!this.watching) return const thisNodes = this.liveSelector.evaluateOnce() @@ -107,16 +148,23 @@ export abstract class Watcher extends EventEmitter implements SingleNodeWatch //#region Warn about repeated keys { - const uniq = uniqWith(thisKeyList, this.keyComparer) - if (uniq.length < thisKeyList.length) { - console.warn('There are repeated keys in your watcher. [uniqKeys, allKeys] = ', uniq, thisKeyList) + if (!this._omitWarningForRepeatedKeys) { + const uniq = uniqWith(thisKeyList, this.keyComparer) + if (uniq.length < thisKeyList.length) { + console.warn( + 'There are repeated keys in your watcher. [uniqKeys, allKeys] = ', + uniq, + thisKeyList, + ', to omit this warning, call watcher.omitWarningForRepeatedKeys', + ) + } } } //#endregion // New maps for the next generation - const nextCallbackMap = new Map>() - const nextVirtualNodesMap = new Map() + const nextCallbackMap = new Map>() + const nextVirtualNodesMap = new Map, DomProxyBefore, DomProxyAfter>>() //#region Key is gone // Do: Delete node @@ -127,14 +175,19 @@ export abstract class Watcher extends EventEmitter implements SingleNodeWatch const virtualNode = this.lastVirtualNodesMap.get(oldKey) const callbacks = this.lastCallbackMap.get(oldKey) const node = findFromLast(oldKey)! - if (node instanceof Node) this.nodeWatcher.removeNode(oldKey) - if (callbacks) { - if (typeof callbacks === 'function') virtualNode && callbacks(virtualNode.realCurrent as any) - else if (callbacks.onRemove) { - callbacks.onRemove(node) - } - } - if (virtualNode) virtualNode.destroy() + // Delete node don't need a short timeout. + this.requestIdleCallback( + () => { + if (callbacks) { + if (typeof callbacks === 'function') virtualNode && callbacks(virtualNode.realCurrent!) + else if (callbacks.onRemove) { + callbacks.onRemove(node) + } + } + if (virtualNode) virtualNode.destroy() + }, + { timeout: 2000 }, + ) } } //#endregion @@ -148,12 +201,19 @@ export abstract class Watcher extends EventEmitter implements SingleNodeWatch if (!this.useNodeForeachFn) break const node = findFromNew(newKey) if (node instanceof Element) { - this.nodeWatcher.addNode(newKey, node) - - const virtualNode = DomProxy() - virtualNode.realCurrent = node - + const virtualNode = DomProxy, DomProxyBefore, DomProxyAfter>(this.domProxyOption) + virtualNode.realCurrent = node as ElementLikeT + // This step must be sync. const callbacks = this.useNodeForeachFn(virtualNode, newKey, node) + if (callbacks && typeof callbacks !== 'function' && callbacks.onNodeMutation) { + virtualNode.observer.init = { + subtree: true, + childList: true, + characterData: true, + attributes: true, + } + virtualNode.observer.callback = m => callbacks.onNodeMutation!(node, m) + } nextCallbackMap.set(newKey, callbacks) nextVirtualNodesMap.set(newKey, virtualNode) } @@ -171,14 +231,12 @@ export abstract class Watcher extends EventEmitter implements SingleNodeWatch .filter(([a, b]) => a !== b) for (const [oldNode, newNode, oldKey, newKey] of changedNodes) { if (newNode instanceof Element) { - this.nodeWatcher.removeNode(oldKey) - this.nodeWatcher.addNode(newKey, newNode) - const virtualNode = this.lastVirtualNodesMap.get(oldKey)! - virtualNode.realCurrent = newNode + virtualNode.realCurrent = newNode as ElementLikeT const fn = this.lastCallbackMap.get(oldKey) if (fn && typeof fn !== 'function' && fn.onTargetChanged) { + // This should be ordered. So keep it sync now. fn.onTargetChanged(oldNode, newNode) } } @@ -188,7 +246,7 @@ export abstract class Watcher extends EventEmitter implements SingleNodeWatch // Key is the same, node is the same // Do: nothing - // Final: Copy the same keys + // #region Final: Copy the same keys for (const newKey of newSameKeys) { const oldKey = oldSameKeys.find(oldKey => this.keyComparer(newKey, oldKey)) nextCallbackMap.set(newKey, this.lastCallbackMap.get(oldKey)) @@ -199,6 +257,7 @@ export abstract class Watcher extends EventEmitter implements SingleNodeWatch this.lastKeyList = thisKeyList this.lastNodeList = thisNodes + this.eventEmitter this.emit('onChangeFull', thisNodes) this.emit( 'onChange', @@ -209,31 +268,50 @@ export abstract class Watcher extends EventEmitter implements SingleNodeWatch // For single node mode const first = thisNodes[0] - if (first instanceof HTMLElement || first === undefined || first === null) { - this.firstVirtualNode.realCurrent = first as any + if (first instanceof Element) { + this.firstVirtualNode.realCurrent = first as ElementLikeT + } else if (first === undefined || first === null) { + this.firstVirtualNode.realCurrent = null } + //#endregion } //#endregion //#region events - addListener(event: 'onChange', fn: EventFn<{ oldNode: T; newNode: T; oldKey: unknown; newKey: unknown }[]>): this - addListener(event: 'onChangeFull', fn: EventFn): this - addListener(event: 'onRemove' | 'onAdd', fn: EventFn<{ node: T; key: unknown }[]>): this + addListener( + event: 'onChange', + fn: EventCallback<{ oldNode: T; newNode: T; oldKey: unknown; newKey: unknown }[]>, + ): this + addListener(event: 'onChangeFull', fn: EventCallback): this + addListener(event: 'onRemove' | 'onAdd', fn: EventCallback<{ node: T; key: unknown }[]>): this addListener(event: string | symbol, fn: (...args: any[]) => void) { - super.addListener(event, fn) + this.eventEmitter.addListener(event, fn) return this } - emit(event: 'onChange', data: { oldNode: T; newNode: T; oldKey: unknown; newKey: unknown }[]): boolean - emit(event: 'onChangeFull', data: T[]): boolean - emit(event: 'onRemove' | 'onAdd', data: { node: T; key: unknown }[]): boolean - emit(event: string | symbol, data: any) { - return super.emit(event, { data }) + removeListener( + event: 'onChange', + fn: EventCallback<{ oldNode: T; newNode: T; oldKey: unknown; newKey: unknown }[]>, + ): this + removeListener(event: 'onChangeFull', fn: EventCallback): this + removeListener(event: 'onRemove' | 'onAdd', fn: EventCallback<{ node: T; key: unknown }[]>): this + removeListener(event: string | symbol, fn: (...args: any[]) => void) { + this.eventEmitter.removeListener(event, fn) + return this + } + protected emit(event: 'onChange', data: { oldNode: T; newNode: T; oldKey: unknown; newKey: unknown }[]): boolean + protected emit(event: 'onChangeFull', data: T[]): boolean + protected emit(event: 'onRemove' | 'onAdd', data: { node: T; key: unknown }[]): boolean + protected emit(event: string | symbol, data: any) { + return this.eventEmitter.emit(event, { data }) } //#endregion /** * This virtualNode always point to the first node in the LiveSelector */ - public readonly firstVirtualNode: RequireNode = DomProxy() as any + readonly firstVirtualNode = (DomProxy(this.domProxyOption) as any) as RequireElement< + T, + DomProxy, DomProxyBefore, DomProxyAfter> + > //#region For multiple nodes injection /** * Map `Node -> Key`, in case of you don't want the default behavior @@ -247,91 +325,70 @@ export abstract class Watcher extends EventEmitter implements SingleNodeWatch protected keyComparer(a: unknown, b: unknown) { return a === b } - public assignKeys: MultipleNodeWatcher['assignKeys'] = (( - ...args: Parameters['assignKeys']> - ) => { - this.mapNodeToKey = args[0] - if (args[1]) this.keyComparer = args[1] + /** + * To help identify same nodes in different iteration, + * you need to implement a map function that map `node` to `key` + * + * If the key is changed, the same node will call through `forEachRemove` then `forEach` + * + * @param assigner - map `node` to `key`, defaults to `node => node` + * @param comparer - compare between two keys, defaults to `===` + */ + assignKeys(assigner: (node: T, index: number, arr: T[]) => Q, comparer?: (a: Q, b: Q) => boolean) { + this.mapNodeToKey = assigner + if (comparer) this.keyComparer = comparer return this - }) as any + } /** Saved useNodeForeach */ - protected useNodeForeachFn: Parameters['useNodeForeach']>[0] | null = null + protected useNodeForeachFn: Parameters['useNodeForeach']>[0] | null = null /** - * Just like React hooks. + * Just like React hooks. Provide callbacks for each node changes. + * + * @param fn - You can return a set of functions that will be called on changes. + * + * @remarks + * + * Return value of `fn` * - * @param fn you can return a set of functions that will be called on changes. * - `void`: No-op + * * - `((oldNode: T) => void)`: it will be called when the node is removed. + * * - `{ onRemove?: (old: T) => void; onTargetChanged?: (oldNode: T, newNode: T) => void; onNodeMutation?: (node: T) => void }`, - * `onRemove` will be called when node is removed. - * `onTargetChanged` will be called when the node is still existing but target has changed. - * `onNodeMutation` will be called when the node is the same, but it inner content or attributes are modified. + * + * - - `onRemove` will be called when node is removed. + * + * - - `onTargetChanged` will be called when the node is still existing but target has changed. + * + * - - `onNodeMutation` will be called when the node is the same, but it inner content or attributes are modified. */ - public useNodeForeach: MultipleNodeWatcher['useNodeForeach'] = (( - ...args: Parameters['useNodeForeach']> - ) => { + useNodeForeach( + fn: RequireElement< + T, + ( + virtualNode: DomProxy, DomProxyBefore, DomProxyAfter>, + key: unknown, + realNode: T, + ) => useNodeForeachReturns + >, + ) { if (this.useNodeForeachFn) { console.warn("You can't chain useNodeForeach currently. The old one will be replaced.") } - this.useNodeForeachFn = args[0] + this.useNodeForeachFn = fn return this - }) as any - public getVirtualNodeByKey: MultipleNodeWatcher['getVirtualNodeByKey'] = ((key: unknown) => { - return this.lastVirtualNodesMap.get( - [...this.lastVirtualNodesMap.keys()].find(_ => this.keyComparer(_, key)), - ) as ReturnType['getVirtualNodeByKey']> - }) as any - //#endregion -} - -class MutationWatcherHelper { - constructor(private ref: Watcher) {} - /** Observer */ - private observer = new MutationObserver(this.onMutation.bind(this)) - /** Watching nodes */ - private nodesMap = new Map() - /** Limit onMutation computation by rAF */ - private rAFLock = false - private onMutation(mutations: MutationRecord[], observer: MutationObserver) { - requestAnimationFrame(() => { - if (this.rAFLock) return - this.rAFLock = true - for (const mutation of mutations) { - for (const [key, node] of this.nodesMap.entries()) { - let cNode: Node | null = mutation.target - compare: while (cNode) { - if (cNode === node) { - this.callback(key, node) - break compare - } - cNode = cNode.parentNode - } - } - } - this.rAFLock = false - }) } - private readonly options = { - attributes: true, - characterData: true, - childList: true, - subtree: true, - } - callback = (key: unknown, node: Node) => {} - addNode(key: unknown, node: Node) { - this.observer.observe(node, this.options) - this.nodesMap.set(key, node) - } - removeNode(key: unknown) { - // No need to call this.observer.disconnect() - // See: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe - // If you call observe() on a node that's already being observed by the same MutationObserver, - // all existing observers are automatically removed from all targets being observed before the new observer is activated. - // Access the protected `keyComparer` here - const foundKey = Array.from(this.nodesMap.keys()).find(k => (this.ref as any).keyComparer(k, key)) - this.nodesMap.delete(foundKey) - for (const node of this.nodesMap.values()) { - this.observer.observe(node, this.options) - } + + /** + * Get virtual node by key. + * Virtual node will be unavailable if it is deleted + * @param key - Key used to find DomProxy + */ + getVirtualNodeByKey(key: unknown) { + return ( + this.lastVirtualNodesMap.get([...this.lastVirtualNodesMap.keys()].find(_ => this.keyComparer(_, key))) || + null + ) } + //#endregion } diff --git a/src/DOM/Watchers/EventWatcher.ts b/src/DOM/Watchers/EventWatcher.ts index c86af18..3f5bf48 100644 --- a/src/DOM/Watchers/EventWatcher.ts +++ b/src/DOM/Watchers/EventWatcher.ts @@ -1,30 +1,38 @@ import { Watcher } from '../Watcher' /** - * To use EventWatcher, do this - * ```typescript - * const e = new EventWatcher(...) + * A Watcher based on event handlers. + * + * @example + * ```ts + * const e = new EventWatcher(ls) * document.addEventListener('event', e.eventListener) * ``` */ -export class EventWatcher extends Watcher { +export class EventWatcher< + T, + Before extends Element = HTMLSpanElement, + After extends Element = HTMLSpanElement +> extends Watcher { /** Limit computation by rAF */ - private rAFLock = false - private _callback = () => { - if (this.rAFLock) return - this.rAFLock = true - this.watcherCallback() - this.rAFLock = false - } - public eventListener() { - requestAnimationFrame(this._callback) + private rICLock = false + /** + * Use this function as event listener to invoke watcher. + */ + public eventListener = () => { + this.requestIdleCallback( + deadline => { + if (this.rICLock) return + this.rICLock = true + this.watcherCallback(deadline) + this.rICLock = false + }, + { timeout: 500 }, + ) } protected watching = true startWatch() { this.watching = true return this } - stopWatch() { - this.watching = false - } } diff --git a/src/DOM/Watchers/IntervalWatcher.ts b/src/DOM/Watchers/IntervalWatcher.ts index f39fae9..11d14e6 100644 --- a/src/DOM/Watchers/IntervalWatcher.ts +++ b/src/DOM/Watchers/IntervalWatcher.ts @@ -2,7 +2,11 @@ import { Watcher } from '../Watcher' /** * A watcher based on time interval. */ -export class IntervalWatcher extends Watcher { +export class IntervalWatcher< + T, + Before extends Element = HTMLSpanElement, + After extends Element = HTMLSpanElement +> extends Watcher { private timer: NodeJS.Timer | undefined /** Start to watch the LiveSelector at a interval(ms). */ startWatch(interval: number) { @@ -13,7 +17,7 @@ export class IntervalWatcher extends Watcher { return this } stopWatch() { - this.watching = false + super.stopWatch() if (this.timer) clearInterval(this.timer) } } diff --git a/src/DOM/Watchers/MutationObserverWatcher.ts b/src/DOM/Watchers/MutationObserverWatcher.ts index ff83084..6285b0b 100644 --- a/src/DOM/Watchers/MutationObserverWatcher.ts +++ b/src/DOM/Watchers/MutationObserverWatcher.ts @@ -3,29 +3,31 @@ import { LiveSelector } from '../LiveSelector' /** * A watcher based on MutationObserver */ -export class MutationObserverWatcher extends Watcher { +export class MutationObserverWatcher< + T, + Before extends Element = HTMLSpanElement, + After extends Element = HTMLSpanElement +> extends Watcher { constructor( protected liveSelector: LiveSelector, /** The element that won't change during the whole watching lifetime. This may improve performance. */ - private consistentWatchRoot: Element | Document = document.body, + private consistentWatchRoot: Node = document.body, ) { super(liveSelector) } /** Observe whole document change */ - private observer: MutationObserver = new MutationObserver(this.onMutation.bind(this)) + private observer: MutationObserver = new MutationObserver((mutations, observer) => { + this.requestIdleCallback(() => { + if (this.rAFLock) return + this.rAFLock = true + this.watcherCallback() + this.rAFLock = false + }) + }) /** Limit onMutation computation by rAF */ private rAFLock = false - private callback = () => { - if (this.rAFLock) return - this.rAFLock = true - this.watcherCallback() - this.rAFLock = false - } - private onMutation(mutations: MutationRecord[], observer: MutationObserver) { - requestAnimationFrame(this.callback) - } startWatch(options?: MutationObserverInit) { this.stopWatch() this.watching = true @@ -36,12 +38,17 @@ export class MutationObserverWatcher extends Watcher { subtree: true, ...options, } - this.observer.observe(this.consistentWatchRoot, option) - this.watcherCallback() + const watch = (root?: Node) => { + this.observer.observe(root || document.body, option) + this.watcherCallback() + } + if (document.readyState !== 'complete' && this.consistentWatchRoot === null) { + document.addEventListener('load', () => watch()) + } else watch(this.consistentWatchRoot) return this } stopWatch() { - this.watching = false + super.stopWatch() this.observer.disconnect() } } diff --git a/src/DOM/Watchers/ValueRef.ts b/src/DOM/Watchers/ValueRef.ts index 29b9984..3cd7f0d 100644 --- a/src/DOM/Watchers/ValueRef.ts +++ b/src/DOM/Watchers/ValueRef.ts @@ -1,4 +1,7 @@ type Fn = (newVal: T, oldVal: T) => void +/** + * A `ref` object with `addListener` + */ export class ValueRef { /** Get current value of a ValueRef */ get value() { @@ -21,7 +24,10 @@ export class ValueRef { constructor(private _value: T) {} /** * Add a listener. This will return a remover. - * Use it like: useEffect(() => ref.addListener(() => {...})) + * @example + * ```ts + * React.useEffect(() => ref.addListener(() => {...})) + * ``` */ addListener(fn: Fn) { this.watcher.set(fn, true) diff --git a/src/Extension/Async-Call.ts b/src/Extension/Async-Call.ts index 4c5517e..c617577 100644 --- a/src/Extension/Async-Call.ts +++ b/src/Extension/Async-Call.ts @@ -1,9 +1,4 @@ import { MessageCenter as HoloflowsMessageCenter } from './MessageCenter' -import 'reflect-metadata' // Load types from reflect-metadata -try { - // This is Optional! - require('reflect-metadata') -} catch (e) {} //#region Serialization /** @@ -14,7 +9,7 @@ export interface Serialization { deserialization(serialized: unknown): Promise } /** - * Do not do any serialization + * Serialization implementation that do nothing */ export const NoSerialization: Serialization = { async serialization(from) { @@ -24,6 +19,11 @@ export const NoSerialization: Serialization = { return serialized }, } +/** + * Serialization implementation by JSON.parse/stringify + * + * @param replacer - Replacer of JSON.parse/stringify + */ export const JSONSerialization = (replacer: Parameters[1] = undefined) => ({ async serialization(from) { @@ -34,17 +34,30 @@ export const JSONSerialization = (replacer: Parameters[1] = undef }, } as Serialization) //#endregion -type Default = Record Promise> -type GeneratorDefault = Record AsyncIterableIterator> - +/** + * Options of {@link AsyncCall} + * @alpha + */ +export interface AsyncCallExecutorOptions + extends Partial<{ + /** + * Allow this function to be memorized for `memorable` ms. + */ + memorable: number + }> {} +type Default = Record Promise) & AsyncCallExecutorOptions> +type GeneratorDefault = Record AsyncIterableIterator) & AsyncCallExecutorOptions> +/** + * Options for {@link AsyncCall} + */ export interface AsyncCallOptions { /** - * @param key + * @param key - * A key to prevent collision with other AsyncCalls. Can be anything, but need to be the same on the both side. */ key: string /** - * @param serializer + * @param serializer - * How to serialization and deserialization parameters and return values * * We offer some built-in serializer: @@ -53,8 +66,7 @@ export interface AsyncCallOptions { */ serializer: Serialization /** - * @param MessageCenter - * A class that can let you transfer messages between two sides + * @param MessageCenter - A class that can let you transfer messages between two sides */ MessageCenter: { new (): { @@ -63,66 +75,80 @@ export interface AsyncCallOptions { } } /** - * @param dontThrowOnNotImplemented + * @param dontThrowOnNotImplemented - * If this side receive messages that we didn't implemented, throw an error */ dontThrowOnNotImplemented: boolean /** - * @param writeToConsole - * Write all calls to console. + * @param writeToConsole - Write all calls to console. */ writeToConsole: boolean } /** * Async call between different context. * - * High level abstraction of MessageCenter. + * @remarks + * Async call is a high level abstraction of MessageCenter. + * + * # Shared code * - * > Shared code * - How to stringify/parse parameters/returns should be shared, defaults to NoSerialization. + * * - `key` should be shared. * - * > One side + * # One side + * * - Should provide some functions then export its type (for example, `BackgroundCalls`) + * * - `const call = AsyncCall(backgroundCalls)` + * * - Then you can `call` any method on `ForegroundCalls` * - * > Other side + * # Other side + * * - Should provide some functions then export its type (for example, `ForegroundCalls`) + * * - `const call = AsyncCall(foregroundCalls)` + * * - Then you can `call` any method on `BackgroundCalls` * * Note: Two sides can implement the same function * - * @example ```typescript - // Mono repo - // UI part - const UI = { - async dialog(text: string) { - alert(text) - }, - } - export type UI = typeof UI - const callsClient = AsyncCall(UI) - callsClient.sendMail('hello world', 'what') - - // On server - const Server = { - async sendMail(text: string, to: string) { - return true - }, - } - export type Server = typeof Server - const calls = AsyncCall(Server) - calls.dialog('hello') - ``` - * @param implementation Implementation of this side. - * @param options Define your own serializer, MessageCenter or other options. + * @example + * For example, here is a mono repo. + * + * Code for UI part: + * ```ts + * const UI = { + * async dialog(text: string) { + * alert(text) + * }, + * } + * export type UI = typeof UI + * const callsClient = AsyncCall(UI) + * callsClient.sendMail('hello world', 'what') + * ``` + * + * Code for server part + * ```ts + * const Server = { + * async sendMail(text: string, to: string) { + * return true + * } + * } + * export type Server = typeof Server + * const calls = AsyncCall(Server) + * calls.dialog('hello') + * ``` + * + * @param implementation - Implementation of this side. + * @param options - Define your own serializer, MessageCenter or other options. + * */ -export const AsyncCall = ( +export function AsyncCall( implementation: Default, options: Partial = {}, -): OtherSideImplementedFunctions => { +): OtherSideImplementedFunctions { const { writeToConsole, serializer, dontThrowOnNotImplemented, MessageCenter, key } = { MessageCenter: HoloflowsMessageCenter, dontThrowOnNotImplemented: true, @@ -137,7 +163,6 @@ export const AsyncCall = ( type PromiseParam = Parameters<(ConstructorParameters)[0]> const map = new Map() message.on(CALL, async (_data: unknown) => { - let metadataOnRequest = getMetadata(_data) const data: Request = await serializer.deserialization(_data) try { const executor = implementation[data.method as keyof typeof implementation] @@ -149,13 +174,7 @@ export const AsyncCall = ( } } const args: any[] = data.args - if (data.metadata) { - // Read metadata on args - data.metadata.forEach((meta, index) => applyMetadata(args[index], meta)) - } - let promise: Promise - if (metadataOnRequest) promise = executor.apply(applyMetadata({}, metadataOnRequest), args) - else promise = executor(...args) + const promise = executor(...args) if (writeToConsole) console.log( `${key}.%c${data.method}%c(${args.map(() => '%o').join(', ')}%c)\n%o %c@${data.callId}`, @@ -171,10 +190,7 @@ export const AsyncCall = ( method: data.method, return: result, callId: data.callId, - // Store metadata on result - metadata: getMetadata(result), } - if (response.metadata === null) delete response.metadata message.send(RESPONSE, await serializer.serialization(response)) } catch (err) { if (writeToConsole) console.error(`${err.message} %c@${data.callId}\n%c${err.stack}`, 'color: gray', '') @@ -188,30 +204,17 @@ export const AsyncCall = ( } }) message.on(RESPONSE, async (_data: unknown) => { - const metadataOnResponse = getMetadata(_data) const data: Response = await serializer.deserialization(_data) const [resolve, reject] = map.get(data.callId) || (([null, null] as any) as PromiseParam) if (!resolve) return // drop this response map.delete(data.callId) - const apply = (obj: any) => - applyMetadata( - obj, - defineMetadata( - // Restore metadata on return value - applyMetadata({}, data.metadata || null), - 'async-call-response', - applyMetadata({}, metadataOnResponse), - ), - ) if (data.error) { const err = new Error(data.error.message) err.stack = data.error.stack - apply(err) reject(err) if (writeToConsole) console.error(`${data.error.message} %c@${data.callId}\n%c${data.error.stack}`, 'color: gray', '') } else { - apply(data.return) resolve(data.return) } }) @@ -219,37 +222,12 @@ export const AsyncCall = ( method: string args: any[] callId: string - metadata?: (Record | null)[] } interface Response { return: any callId: string method: string error?: { message: string; stack: string } - metadata?: Record | null - } - function isObject(it: any): it is object { - return typeof it === 'object' ? it !== null : typeof it === 'function' - } - function getMetadata(obj: any): Record | null { - if (!isObject(obj)) return null - if ('getOwnMetadataKeys' in Reflect === false) return null - return Reflect.getOwnMetadataKeys(obj) - .map(key => [key, Reflect.getOwnMetadata(key, obj)]) - .reduce((prev, curr) => ({ ...prev, [curr[0]]: curr[1] }), {}) - } - function applyMetadata(obj: T, metadata: Record | null): T { - if (!isObject(obj)) return obj - if (metadata === null) return obj - if ('defineMetadata' in Reflect === false) return obj - Object.entries(metadata).forEach(([key, value]) => Reflect.defineMetadata(key, value, obj)) - return obj - } - function defineMetadata(obj: T, key: string, data: any): T { - if (!isObject(obj)) return obj - if ('defineMetadata' in Reflect === false) return obj - Reflect.defineMetadata(key, data, obj) - return obj } return new Proxy( {}, @@ -261,11 +239,7 @@ export const AsyncCall = ( const id = Math.random() .toString(36) .slice(2) - // Store metadata on args - const metadata: Request['metadata'] = args.map(getMetadata) - const req: Request = { method: method, args: args, callId: id, metadata } - const metadataUsed = args.some(x => x !== null) - if (!metadataUsed) delete req.metadata + const req: Request = { method: method, args: args, callId: id } serializer.serialization(req).then(data => { message.send(CALL, data) map.set(id, [resolve, reject]) diff --git a/src/Extension/AutomatedTabTask.ts b/src/Extension/AutomatedTabTask.ts index a5c8575..f82d921 100644 --- a/src/Extension/AutomatedTabTask.ts +++ b/src/Extension/AutomatedTabTask.ts @@ -1,14 +1,76 @@ -import { sleep, timeout } from '../util/sleep' +import { sleep, timeout as timeoutFn } from '../util/sleep' import { AsyncCall } from './Async-Call' import { GetContext } from './Context' +import Lock from 'concurrent-lock' +import { memorize } from 'memorize-decorator' +interface AutomatedTabTaskSharedOptions { + /** + * If the task is memorable. + * - true: Memorize by url and all options + * - false: Does not use Memory + * @defaultValue false + */ + memorable: boolean + /** + * Task timeout. + * @defaultValue 30000 + */ + timeout: number + /** + * Should the new tab pinned? + * @defaultValue true + * + * !TODO: make it false on Vavaldi. + */ + pinned: boolean + /** + * Should the new tab to be closed automatically? + * @defaultValue true + */ + autoClose: boolean + /** + * Should the new tab to be active? + * @defaultValue false + */ + active: boolean +} /** - * Based on AsyncCall. Open a new page in the background, execute some task, then close it automatically. - * - * Usage: + * Define-time options for {@link AutomatedTabTask} + */ +export interface AutomatedTabTaskDefineTimeOptions extends AutomatedTabTaskSharedOptions { + /** + * At most run `concurrent` tasks. + * Prevents open too many tabs at the same time. + * @defaultValue 3 + */ + concurrent: number + /** + * A unique key + * @defaultValue `a extension specific url.` + */ + key: string + /** + * TTL for memorize + * @defaultValue 30 * 60 * 1000 + */ + memorizeTTL: number +} +/** + * Runtime options for {@link AutomatedTabTask} + */ +export interface AutomatedTabTaskRuntimeOptions extends AutomatedTabTaskSharedOptions { + /** + * This task is important, need to start now without queue. + */ + important: boolean +} +/** + * Open a new page in the background, execute some task, then close it automatically. * - * > In content script: (You must run this in the page you wanted to run task in!) + * @example * + * In content script: (You must run this in the page you wanted to run task in!) * ```ts * export const task = AutomatedTabTask({ * async taskA() { @@ -17,86 +79,167 @@ import { GetContext } from './Context' * }) * ``` * - * > In background script: + * In background script: * + * Open https://example.com/ then run taskA() on that page, which will return 'Done!' * ```ts * import { task } from '...' * task('https://example.com/').taskA() - * // Open https://example.com/ then run taskA() on that page, which will return 'Done!' * ``` - * @param taskImplements All tasks that background page can call. - * @param AsyncCallKey A unique key, defaults to a extension specific url. + * + * @param taskImplements - All tasks that background page can call. + * @param options - Options */ export function AutomatedTabTask Promise>>( taskImplements: T, - AsyncCallKey = chrome.runtime.getURL('@holoflows/kit:AutomatedTabTask'), + options: Partial = {}, ) { + const { + timeout: defaultTimeout, + key: AsyncCallKey, + concurrent, + memorable: defaultMemorable, + memorizeTTL, + autoClose: defaultAutoClose, + pinned: defaultPinned, + active: defaultActive, + } = { + ...({ + timeout: 30 * 1000, + key: (context => { + switch (context) { + case 'background': + case 'content': + case 'options': + return browser.runtime.getURL('@holoflows/kit:AutomatedTabTask') + case 'debugging': + return 'debug' + case 'unknown': + default: + throw new TypeError('Unknown running context') + } + })(GetContext()), + concurrent: 3, + memorizeTTL: 30 * 60 * 1000, + memorable: false, + autoClose: true, + pinned: true, + active: false, + } as AutomatedTabTaskDefineTimeOptions), + ...options, + } const REGISTER = AsyncCallKey + ':ping' const getTaskNameByTabId = (task: string, tabId: number) => `${task}:${tabId}` if (GetContext() === 'content') { // If run in content script // Register this tab - chrome.runtime.sendMessage(REGISTER, (sender: chrome.runtime.MessageSender) => { - const tabId = sender.tab!.id! - if (!tabId) return - // Transform `methodA` to `methodA - 233` (if tabId = 233) - const tasksWithId: any = {} - for (const [taskName, value] of Object.entries(taskImplements)) { - tasksWithId[getTaskNameByTabId(taskName, tabId)] = value - } - // Register AsyncCall - AsyncCall(tasksWithId, { key: AsyncCallKey }) - }) + browser.runtime.sendMessage({ type: REGISTER }).then( + (sender: browser.runtime.MessageSender) => { + const tabId = sender.tab!.id! + if (!tabId) return + // Transform `methodA` to `methodA:233` (if tabId = 233) + const tasksWithId: any = {} + for (const [taskName, value] of Object.entries(taskImplements)) { + tasksWithId[getTaskNameByTabId(taskName, tabId)] = value + } + // Register AsyncCall + AsyncCall(tasksWithId, { key: AsyncCallKey }) + }, + () => {}, + ) return null - } else if (GetContext() === 'background') { + } else if (GetContext() === 'background' || GetContext() === 'options') { type tabId = number /** If `tab` is ready */ const readyMap: Record = {} // Listen to tab REGISTER event - chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message === REGISTER) { - // response its tab id - sendResponse(sender) + browser.runtime.onMessage.addListener(((message, sender) => { + if ((message as any).type === REGISTER) { readyMap[sender.tab!.id!] = true + // response its tab id + return Promise.resolve(sender) } - }) + return undefined + }) as browser.runtime.onMessageVoid) // Register a empty AsyncCall for runtime-generated call const asyncCall = AsyncCall({}, { key: AsyncCallKey }) + const lock = new Lock(concurrent) + async function runTask( + url: string, + taskName: string, + timeout: number, + important: boolean, + pinned: boolean, + autoClose: boolean, + active: boolean, + args: any[], + ) { + const withoutLock = important || !autoClose || active + if (!withoutLock) await lock.lock(timeout) + // Create a new tab + const tab = await browser.tabs.create({ active, pinned, url }) + const tabId = tab.id! + // Wait for the tab register + while (readyMap[tabId] !== true) await sleep(50) + + // Run the async call + const task: Promise = asyncCall[getTaskNameByTabId(taskName, tabId)](...args) + try { + // ! DO NOT Remove `await`, or finally block will run before the promise resolved + return await timeoutFn(task, timeout) + } finally { + if (!withoutLock) lock.unlock() + autoClose && browser.tabs.remove(tabId) + delete readyMap[tabId] + } + } + const memoRunTask = memorize(runTask, { ttl: memorizeTTL }) return ( /** URL you want to execute the task ok */ url: string, - /** When will the task timed out */ timeoutTime = 30 * 1000, - ) => + options: Partial = {}, + ) => { + const { memorable, timeout, important, autoClose, pinned, active } = { + ...({ + memorable: defaultMemorable, + important: false, + timeout: defaultTimeout, + autoClose: defaultAutoClose, + pinned: defaultPinned, + active: defaultActive, + } as AutomatedTabTaskRuntimeOptions), + ...options, + } + function runner(_: unknown, taskName: string | number | symbol) { + return (...args: any[]) => { + if (typeof taskName !== 'string') throw new TypeError('Key must be a string') + return (memorable ? memoRunTask : runTask)( + url, + taskName, + timeout, + important, + pinned, + autoClose, + active, + args, + ) + } + } + return new Proxy({}, { get: runner }) as T + } + } else if (GetContext() === 'debugging') { + return (...args1: any[]) => new Proxy( {}, { - get(_, taskName) { - return (...args: any[]) => - new Promise((resolve, reject) => { - if (typeof taskName !== 'string') return reject('Key must be a string') - // Create a new tab - chrome.tabs.create( - { - active: false, - pinned: true, - url: url, - }, - async tab => { - const tabId = tab.id! - // Wait for the tab register - while (readyMap[tabId] !== true) await sleep(50) - // Run the async call - const task: Promise = asyncCall[getTaskNameByTabId(taskName, tabId)]( - ...args, - ) - timeout(task, timeoutTime) - .then(resolve, reject) - .finally(() => { - chrome.tabs.remove(tabId) - delete readyMap[tabId] - }) - }, - ) - }) + get(_, key) { + return async (...args2: any) => { + console.log( + `AutomatedTabTask.${AsyncCallKey}.${String(key)} called with `, + ...args1, + ...args2, + ) + await sleep(2000) + } }, }, ) as T diff --git a/src/Extension/Context.ts b/src/Extension/Context.ts index 692545b..7281291 100644 --- a/src/Extension/Context.ts +++ b/src/Extension/Context.ts @@ -1,22 +1,47 @@ -export type Contexts = 'background' | 'content' | 'unknown' +/** + * All context that possible in when developing a WebExtension + */ +export type Contexts = 'background' | 'content' | 'webpage' | 'unknown' | 'options' | 'debugging' /** * Get current running context. + * + * @remarks * - background: background script * - content: content script + * - webpage: a normal webpage * - unknown: unknown context */ export function GetContext(): Contexts { if (typeof location === 'undefined') return 'unknown' - if (location.protocol === 'chrome-extension:') return 'background' - return 'content' + if (typeof browser !== 'undefined') { + if (location.protocol.match('-extension')) { + if ( + browser.extension && + browser.extension.getBackgroundPage && + browser.extension.getBackgroundPage().location.href === location.href + ) + return 'background' + return 'options' + } + if (browser.runtime && browser.runtime.getManifest) return 'content' + } + // What about rollup? + if (location.hostname === 'localhost') return 'debugging' + return 'webpage' } /** - * Make sure this file only run in (for Typescript user: but you can still export types) wanted context - * @param context Wanted context or contexts - * @param name name to throw + * Make sure this file only run in wanted context + * @param context - Wanted context or contexts + * @param name - name to throw */ -export function OnlyRunInContext(context: Contexts | Contexts[], name: string) { +export function OnlyRunInContext(context: Contexts | Contexts[], name: string): void +export function OnlyRunInContext(context: Contexts | Contexts[], throws: false): boolean +export function OnlyRunInContext(context: Contexts | Contexts[], name: string | false) { const ctx = GetContext() - if (Array.isArray(context) ? context.indexOf(ctx) === -1 : context !== ctx) - throw new TypeError(`${name} run in the wrong context. (Wanted ${context}, actually ${ctx})`) + if (Array.isArray(context) ? context.indexOf(ctx) === -1 : context !== ctx) { + if (typeof name === 'string') + throw new TypeError(`${name} run in the wrong context. (Wanted ${context}, actually ${ctx})`) + else return false + } + return true } diff --git a/src/Extension/MessageCenter.ts b/src/Extension/MessageCenter.ts index 810679b..e042524 100644 --- a/src/Extension/MessageCenter.ts +++ b/src/Extension/MessageCenter.ts @@ -7,16 +7,16 @@ type Key = string | number | symbol const MessageCenterEvent = 'Holoflows-Kit MessageCenter' const newMessage = (key: InternalMessageType['key'], data: InternalMessageType['data']) => new CustomEvent(MessageCenterEvent, { detail: { data, key } }) -/** Send and receive messages in different contexts. */ +const noop = () => {} +/** + * Send and receive messages in different contexts. + */ export class MessageCenter { private listeners: Array<{ key: Key; handler: (data: any) => void }> = [] - // private id: any private listener = (request: InternalMessageType | Event) => { - let key: Key, data: any, instanceKey: string - if (request instanceof Event) ({ key, data, instanceKey } = (request as CustomEvent).detail) - else ({ key, data, instanceKey } = request) + let { key, data, instanceKey } = (request as CustomEvent).detail || request // Message is not for us - if (this.instanceKey !== instanceKey) return + if (this.instanceKey !== (instanceKey || '')) return if (this.writeToConsole) { console.log( `%cReceive%c %c${key.toString()}`, @@ -28,23 +28,25 @@ export class MessageCenter { } this.listeners.filter(it => it.key === key).forEach(it => it.handler(data)) } + /** + * @param instanceKey - Use this instanceKey to distinguish your messages and others. + * This option cannot make your message safe! + */ constructor(private instanceKey = '') { - if (chrome && chrome.runtime) { + if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.onMessage) { // Fired when a message is sent from either an extension process (by runtime.sendMessage) // or a content script (by tabs.sendMessage). - if (chrome.runtime.onMessage) { - chrome.runtime.onMessage.addListener(this.listener) - } - // Fired when a message is sent from another extension/app (by runtime.sendMessage). - // Cannot be used in a content script. - if (chrome.runtime.onMessageExternal) { - chrome.runtime.onMessageExternal.addListener(this.listener) - } + browser.runtime.onMessage.addListener(this.listener) } - if (document && document.addEventListener) { + if (typeof document !== 'undefined' && document.addEventListener) { document.addEventListener(MessageCenterEvent, this.listener) } } + /** + * Listen to an event + * @param event - Name of the event + * @param handler - Handler of the event + */ public on(event: Key, handler: (data: ITypedMessages[Key]) => void): void { this.listeners.push({ handler: data => handler(data), @@ -52,8 +54,17 @@ export class MessageCenter { }) } - /** Send message to local or other instance of extension */ - public send(key: Key, data: ITypedMessages[Key]): void { + /** + * Send message to local or other instance of extension + * @param key - Key of the message + * @param data - Data of the message + * @param alsoSendToDocument - ! Send message to document. This may leaks secret! Only open in localhost! + */ + public send( + key: Key, + data: ITypedMessages[Key], + alsoSendToDocument = location.hostname === 'localhost', + ): void { if (this.writeToConsole) { console.log( `%cSend%c %c${key.toString()}`, @@ -64,28 +75,22 @@ export class MessageCenter { ) } const msg: InternalMessageType = { data, key, instanceKey: this.instanceKey || '' } - if (chrome && chrome.runtime) { - if (chrome.runtime.sendMessage) { - try { - chrome.runtime.sendMessage(msg) - } catch (error) { - // if (this.id) { - // chrome.runtime.sendMessage(this.id, msg) - // } - } + if (typeof browser !== 'undefined') { + if (browser.runtime && browser.runtime.sendMessage) { + browser.runtime.sendMessage(msg).catch(noop) + } + if (browser.tabs) { + // Send message to Content Script + browser.tabs.query({ discarded: false }).then(tabs => { + for (const tab of tabs) { + if (tab.id) browser.tabs.sendMessage(tab.id, msg).catch(noop) + } + }) } } - if (document && document.dispatchEvent) { + if (alsoSendToDocument && typeof document !== 'undefined' && document.dispatchEvent) { document.dispatchEvent(newMessage(key, data)) } - // Send message to Content Script - if (chrome && chrome.tabs) { - chrome.tabs.query({ discarded: false }, tabs => { - for (const tab of tabs) { - if (tab.id) chrome.tabs.sendMessage(tab.id, msg) - } - }) - } } writeToConsole = false } diff --git a/src/index.ts b/src/index.ts index 5d4626d..1fae5a3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,7 @@ +/** + * A toolkit for browser extension developing. + * + * @packageDocumentation + */ export * from './DOM' export * from './Extension' diff --git a/src/types/concurrent-lock.d.ts b/src/types/concurrent-lock.d.ts new file mode 100644 index 0000000..db1000c --- /dev/null +++ b/src/types/concurrent-lock.d.ts @@ -0,0 +1,8 @@ +declare module 'concurrent-lock' { + export default class Lock { + constructor(limit: number) + isLocked(): boolean + lock(timeout: number): Promise + unlock(): void + } +} diff --git a/src/util/sleep.ts b/src/util/sleep.ts index 8833b55..7420db2 100644 --- a/src/util/sleep.ts +++ b/src/util/sleep.ts @@ -1,15 +1,19 @@ /** * Return a promise that resolved after `time` ms. * If `time` is `Infinity`, it will never resolve. - * @param time Time to sleep. In `ms`. + * @param time - Time to sleep. In `ms`. + * + * @internal */ export const sleep = (time: number) => new Promise(resolve => (Number.isFinite(time) ? setTimeout(resolve, time) : void 0)) /** * Accept a promise and then set a timeout on it. After `time` ms, it will reject. - * @param promise The promise that you want to set time limit on. - * @param time Time before timeout. In `ms`. - * @param rejectReason When reject, show a reason. Defaults to `"timeout"` + * @param promise - The promise that you want to set time limit on. + * @param time - Time before timeout. In `ms`. + * @param rejectReason - When reject, show a reason. Defaults to `"timeout"` + * + * @internal */ export const timeout = (promise: Promise, time: number, rejectReason?: string) => Number.isFinite(time) diff --git a/tsconfig.json b/tsconfig.json index 863a6ef..8e5f97d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,8 @@ "outDir": "./es/", "incremental": true, "emitDecoratorMetadata": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "typeRoots": ["node_modules/@types", "node_modules/web-ext-types", "src/types"] }, "include": ["./src/**/*"] } diff --git a/yarn.lock b/yarn.lock index 30b13b9..8fbccf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,29 +2,99 @@ # yarn lockfile v1 -"@types/chrome@^0.0.81": - version "0.0.81" - resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.81.tgz#4fbf34c370a60235be4f97f32429e701fb644d10" - integrity sha512-NKkQGMJSppFwUwMbEbYcq8/p/ACVHmQCUpWaqJVdHn81tU3by+YXnxw86KFa0tUSLamu35wtzFaMyY4TFaMceQ== - dependencies: - "@types/filesystem" "*" +"@babel/runtime@^7.3.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.4.tgz#dc2e34982eb236803aa27a07fea6857af1b9171d" + integrity sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg== + dependencies: + regenerator-runtime "^0.13.2" + +"@microsoft/api-documenter@^7.1.6": + version "7.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/api-documenter/-/api-documenter-7.1.6.tgz#3145b809d1195be3d28468d18ac82c1057a89feb" + integrity sha512-TzChFwx5orx7NnDFqR+siEOYTZCeVyu6hqMPPHF4u+MY5OHZv5Dm/uIAiJoN06alDC5/SRDYgvV6nQXHciYoTw== + dependencies: + "@microsoft/api-extractor-model" "7.1.0" + "@microsoft/node-core-library" "3.13.0" + "@microsoft/ts-command-line" "4.2.4" + "@microsoft/tsdoc" "0.12.9" + colors "~1.2.1" + js-yaml "~3.13.1" + +"@microsoft/api-extractor-model@7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.1.0.tgz#57e9805ba0f2322dd12945bb0588eeda522519cd" + integrity sha512-DvaJ1fEpwega9TVMR4xR0jeNV/9JHNMxN/t8TuBftZHSLZzTczh8HyqyFxKo2IwDDCoZy5FmXZsq/vo5JQvRMQ== + dependencies: + "@microsoft/node-core-library" "3.13.0" + "@microsoft/tsdoc" "0.12.9" + "@types/node" "8.5.8" + +"@microsoft/api-extractor@^7.1.4": + version "7.1.4" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.1.4.tgz#297acd3a021e66737a28b26638838618ef6446fc" + integrity sha512-hmvdgPhCthK8tb+sSGT/3rYcTq6OmzzOcMDGNcWooyMGSfllBKgArr+JJ0VBQnUjNAKFpVmXaQQ7zJoGCqCIPQ== + dependencies: + "@microsoft/api-extractor-model" "7.1.0" + "@microsoft/node-core-library" "3.13.0" + "@microsoft/ts-command-line" "4.2.4" + "@microsoft/tsdoc" "0.12.9" + colors "~1.2.1" + lodash "~4.17.5" + resolve "1.8.1" + source-map "~0.6.1" + typescript "~3.4.3" + +"@microsoft/node-core-library@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@microsoft/node-core-library/-/node-core-library-3.13.0.tgz#ba24e16182149dc817bf52a886d22aced5cd8070" + integrity sha512-mnsL/1ikVWHl8sPNssavaAgtUaIM3hkQ8zeySuApU5dNmsMPzovJPfx9m5JGiMvs1v5QNAIVeiS9jnWwe/7anw== + dependencies: + "@types/fs-extra" "5.0.4" + "@types/jju" "~1.4.0" + "@types/node" "8.5.8" + "@types/z-schema" "3.16.31" + colors "~1.2.1" + fs-extra "~7.0.1" + jju "~1.4.0" + z-schema "~3.18.3" + +"@microsoft/ts-command-line@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@microsoft/ts-command-line/-/ts-command-line-4.2.4.tgz#42fbd5dac0eb530b338867ec62e86a463d8280d6" + integrity sha512-aRKhg+Tpxx4PSRYzFmv+bMsoMFi2joE6tONlTHB2MER1mivS0qb8uJh1SvrjzeshD6sASlaQxgADRIdHyXBCWw== + dependencies: + "@types/argparse" "1.0.33" + "@types/node" "8.5.8" + argparse "~1.0.9" + colors "~1.2.1" + +"@microsoft/tsdoc@0.12.9": + version "0.12.9" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.12.9.tgz#f92538bebf649b1b9d00bdd34a9c9971aef17d01" + integrity sha512-sDhulvuVk65eMppYOE6fr6mMI6RUqs53KUg9deYzNCBpP+2FhR0OFB5innEfdtSedk0LK+1Ppu6MxkfiNjS/Cw== + +"@types/argparse@1.0.33": + version "1.0.33" + resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.33.tgz#2728669427cdd74a99e53c9f457ca2866a37c52d" + integrity sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ== "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/filesystem@*": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.29.tgz#ee3748eb5be140dcf980c3bd35f11aec5f7a3748" - integrity sha512-85/1KfRedmfPGsbK8YzeaQUyV1FQAvMPMTuWFQ5EkLd2w7szhNO96bk3Rh/SKmOfd9co2rCLf0Voy4o7ECBOvw== +"@types/fs-extra@5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.4.tgz#b971134d162cc0497d221adde3dbb67502225599" + integrity sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g== dependencies: - "@types/filewriter" "*" + "@types/node" "*" -"@types/filewriter@*": - version "0.0.28" - resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.28.tgz#c054e8af4d9dd75db4e63abc76f885168714d4b3" - integrity sha1-wFTor02d11205jq8dviFFocU1LM= +"@types/jju@~1.4.0": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@types/jju/-/jju-1.4.1.tgz#0a39f5f8e84fec46150a7b9ca985c3f89ad98e9f" + integrity sha512-LFt+YA7Lv2IZROMwokZKiPNORAV5N3huMs3IKnzlE430HWhWYZ8b+78HiwJXJJP1V2IEjinyJURuRJfGoaFSIA== "@types/lodash-es@^4.1.4": version "4.17.3" @@ -38,16 +108,31 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.123.tgz#39be5d211478c8dd3bdae98ee75bb7efe4abfe4d" integrity sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q== +"@types/node@*": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.0.tgz#d11813b9c0ff8aaca29f04cbc12817f4c7d656e5" + integrity sha512-Jrb/x3HT4PTJp6a4avhmJCDEVrPdqLfl3e8GGMbpkGGdwAV5UGlIs4vVEfsHHfylZVOKZWpOqmqFH8CbfOZ6kg== + "@types/node@10.12.23": version "10.12.23" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.23.tgz#308a3acdc5d1c842aeadc50b867d99c46cfae868" integrity sha512-EKhb5NveQ3NlW5EV7B0VRtDKwUfVey8LuJRl9pp5iW0se87/ZqLjG0PMf2MCzPXAJYWZN5Ltg7pHIAf9/Dm1tQ== +"@types/node@8.5.8": + version "8.5.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.8.tgz#92509422653f10e9c0ac18d87e0610b39f9821c7" + integrity sha512-8KmlRxwbKZfjUHFIt3q8TF5S2B+/E5BaAoo/3mgc5h6FJzqxXkCK/VMetO+IRDtwtU6HUvovHMBn+XRj7SV9Qg== + "@types/node@^11.11.6": version "11.12.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.2.tgz#d7f302e74b10e9801d52852137f652d9ee235da8" integrity sha512-c82MtnqWB/CqqK7/zit74Ob8H1dBdV7bK+BcErwtXbe0+nUGkgzq5NTDmRW/pAv2lFtmeNmW95b0zK2hxpeklg== +"@types/z-schema@3.16.31": + version "3.16.31" + resolved "https://registry.yarnpkg.com/@types/z-schema/-/z-schema-3.16.31.tgz#2eb1d00a5e4ec3fa58c76afde12e182b66dc5c1c" + integrity sha1-LrHQCl5Ow/pYx2r94S4YK2bcXBw= + acorn@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" @@ -60,6 +145,13 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +argparse@^1.0.7, argparse@~1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" @@ -227,6 +319,16 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +colors@~1.2.1: + version "1.2.5" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" + integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== + +commander@^2.7.1: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== + component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -237,6 +339,13 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +concurrent-lock@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/concurrent-lock/-/concurrent-lock-1.0.7.tgz#ceda4cec9adcbe3062f91e7401c33f1c9a1489d5" + integrity sha512-WhivxLzkjaqpd3Bs+8UCq8cDznvx4vX5tzW17Zbk1c8nyuALt3h7EK5qpc3BSLFZADpCmMJieqUoZ6iOSTYIPA== + dependencies: + "@babel/runtime" "^7.3.4" + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" @@ -327,6 +436,11 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + estree-walker@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39" @@ -450,7 +564,7 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fs-extra@7.0.1: +fs-extra@7.0.1, fs-extra@~7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== @@ -761,6 +875,19 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +jju@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" + integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= + +js-yaml@~3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -817,6 +944,21 @@ lodash-es@^4.1.2: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0" integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q== +lodash.get@^4.0.0: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.isequal@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash@~4.17.5: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + magic-string@^0.25.2: version "0.25.2" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.2.tgz#139c3a729515ec55e96e69e82a11fe890a293ad9" @@ -841,6 +983,13 @@ math-random@^1.0.1: resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== +memorize-decorator@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/memorize-decorator/-/memorize-decorator-0.2.2.tgz#f3dcb14a2584981b486454fa3f1bd59c17586d70" + integrity sha512-gjMxF1Dw7alhiVbxTQiMcYtMCGJ8cGVdUPCEek88H4WvRGShozxObMFm1BMWctXXZpP7F7eIUJQkTa/jLPZeFQ== + dependencies: + multikey-map "^0.2.0" + memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" @@ -891,6 +1040,11 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" +mixed-map@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/mixed-map/-/mixed-map-0.1.1.tgz#f3407171d13ed784ae3c1c6bb4f2c1233b4529e6" + integrity sha512-k1bGC1J3EgXbvKM4qWUVygoF5BxZrL9pKprEXEL5l8p7lePA01IllCvpMnpEs5odne3JFc+efayDEPdX2OIxwg== + mixin-deep@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" @@ -904,6 +1058,13 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +multikey-map@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/multikey-map/-/multikey-map-0.2.0.tgz#3c2e7171e43c7facfa0910b81c4eb4c960650729" + integrity sha512-0DkizPHr9u3oBL73vHJoFPChMvBfXQtZy2qsMfkam7qnqyOQn4C7D9lmNNQv+WmRZz2nNACi70sdncV/eVqL1A== + dependencies: + mixed-map "^0.1.0" + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -1089,6 +1250,11 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== +regenerator-runtime@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" + integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA== + regex-cache@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" @@ -1317,6 +1483,11 @@ source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + sourcemap-codec@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f" @@ -1355,6 +1526,11 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -1419,6 +1595,11 @@ typescript@^3.4.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6" integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q== +typescript@~3.4.3: + version "3.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" + integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== + union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" @@ -1460,6 +1641,16 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-8.2.0.tgz#3c1237290e37092355344fef78c231249dab77b9" + integrity sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA== + +web-ext-types@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/web-ext-types/-/web-ext-types-3.1.0.tgz#32aab56a3d0f4a3d499d43b4838b3356102d11eb" + integrity sha512-HKVibk040vuhpbOljcIYYYC8GP9w9REbHpquI3im/aoZqoDIRq9DnsHl4Zsg+4Fg3SBnWsnvlIr1rnspV4TdXQ== + which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -1471,3 +1662,14 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +z-schema@~3.18.3: + version "3.18.4" + resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-3.18.4.tgz#ea8132b279533ee60be2485a02f7e3e42541a9a2" + integrity sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw== + dependencies: + lodash.get "^4.0.0" + lodash.isequal "^4.0.0" + validator "^8.0.0" + optionalDependencies: + commander "^2.7.1"