diff --git a/.babelrc b/.babelrc index b2f5cd96..3cb084c9 100644 --- a/.babelrc +++ b/.babelrc @@ -10,5 +10,10 @@ "babel-plugin-add-module-exports", "babel-plugin-class-display-name", "@babel/plugin-transform-runtime" - ] + ], + "env": { + "test": { + "plugins": [ "istanbul" ] + } + } } diff --git a/.eslintrc b/.eslintrc index 77d22840..bb64ed7d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,7 +7,15 @@ * Temporary suppress some errors. We need to fix them partially in next patches */ "import/no-duplicates": ["warn"], - "@typescript-eslint/triple-slash-reference": ["off"] + "@typescript-eslint/triple-slash-reference": ["off"], + "jsdoc/no-undefined-types": ["warn", {"definedTypes": [ + "ConstructorOptions", + "API", + "BlockToolConstructable", + "EditorConfig", + "Tool", + "ToolSettings" + ]}] }, "settings": { "jsdoc": { diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml new file mode 100644 index 00000000..8201686a --- /dev/null +++ b/.github/workflows/cypress.yml @@ -0,0 +1,33 @@ +name: Tests +on: [pull_request] +jobs: + firefox: + runs-on: ubuntu-latest + container: + image: cypress/browsers:node14.16.0-chrome89-ff86 + options: --user 1001 + steps: + - uses: actions/checkout@v2 + - run: yarn pull_tools + - uses: cypress-io/github-action@v2 + with: + browser: firefox + build: yarn build + chrome: + runs-on: ubuntu-16.04 + steps: + - uses: actions/checkout@v2 + - run: yarn pull_tools + - uses: cypress-io/github-action@v2 + with: + browser: chrome + build: yarn build + edge: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - run: yarn pull_tools + - uses: cypress-io/github-action@v2 + with: + browser: edge + build: yarn build diff --git a/.github/workflows/publish-package-to-npm.yml b/.github/workflows/publish-package-to-npm.yml index 008c90f7..69364cdf 100644 --- a/.github/workflows/publish-package-to-npm.yml +++ b/.github/workflows/publish-package-to-npm.yml @@ -15,6 +15,10 @@ jobs: # Pull submodules submodules: 'recursive' + - name: Get package info + id: package + uses: codex-team/action-nodejs-package-info@v1 + # Setup node environment - uses: actions/setup-node@v1 with: @@ -28,8 +32,14 @@ jobs: - name: Build output files run: yarn build - - name: Publish the package - run: yarn publish --access=public ${{ github.event.release.prerelease && '--tag=next' || '--tag=latest' }} + - name: Publish the package with a NEXT tag + run: yarn publish --access=public --tag=next + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Add LATEST tag for the published package if this is not a prerelease version + if: github.event.release.prerelease != true + run: npm dist-tag add ${{ steps.package.outputs.name }}@${{ steps.package.outputs.version }} latest env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 52c29337..db93fc47 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ test/cypress/screenshots test/cypress/videos dist/ + +coverage/ +.nyc_output/ diff --git a/.gitmodules b/.gitmodules index 8e40c5e6..7dc8f02c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,8 +16,8 @@ [submodule "example/tools/simple-image"] path = example/tools/simple-image url = https://github.com/editor-js/simple-image -[submodule "src/components/tools/paragraph"] - path = src/components/tools/paragraph +[submodule "src/tools/paragraph"] + path = src/tools/paragraph url = https://github.com/editor-js/paragraph [submodule "example/tools/marker"] path = example/tools/marker @@ -49,3 +49,6 @@ [submodule "example/tools/underline"] path = example/tools/underline url = https://github.com/editor-js/underline +[submodule "example/tools/nested-list"] + path = example/tools/nested-list + url = https://github.com/editor-js/nested-list diff --git a/README.md b/README.md index a3e24b55..d4530ecc 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,13 @@ If you like Editor.js you can support project improvements and development of ne ### Sponsors -Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/editorjs#sponsor)] +Support us by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/editorjs/contribute/sir-8679/checkout)] + + + + + - ### Backers diff --git a/cypress.json b/cypress.json index 732aea68..2bed0b88 100644 --- a/cypress.json +++ b/cypress.json @@ -1,9 +1,11 @@ { "env": { + "NODE_ENV": "test" }, "fixturesFolder": "test/cypress/fixtures", "integrationFolder": "test/cypress/tests", "screenshotsFolder": "test/cypress/screenshots", "videosFolder": "test/cypress/videos", - "supportFile": "test/cypress/support/index.ts" + "supportFile": "test/cypress/support/index.ts", + "pluginsFile": "test/cypress/plugins/index.ts" } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7ee68258..fa519bb6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,16 +1,58 @@ # Changelog -### 2.19.4 +### 2.23.0 - `Improvements` - Vertical caret moving with UP or DOWN key [#857](https://github.com/codex-team/editor.js/issues/857). +### 2.22.2 + +- `Improvement` — Inline Toolbar might be used for any contenteditable element inside Editor.js zone +- `Improvement` *Tunes API* - Tunes now can provide sanitize configuration +- `Fix` *Tunes API* - Tune config now passed to constructor under `config` property +- `Fix` *Types* - Add common type for internal and external Tools configuration +- `Fix` — Block's destroy method is called on block deletion +- `Fix` - Fix jump to the button of editor zone on CBS + +### 2.22.1 + +- `Fix` — I18n for internal Block Tunes [#1661](https://github.com/codex-team/editor.js/issues/1661) + +### 2.22.0 + +- `New` - `onChange` callback now receive Block API object of affected block +- `New` - API method `blocks.update(id, data)` added. + +### 2.21.0 + +- `New` - Blocks now have unique ids [#873](https://github.com/codex-team/editor.js/issues/873) + +### 2.20.2 + +- `Fix` — Append default Tunes if user tunes are provided for Block Tool [#1640](https://github.com/codex-team/editor.js/issues/1640) +- `Fix` - Prevent the leak of codex-tooltip when Editor.js is destroyed [#1475](https://github.com/codex-team/editor.js/issues/1475). +- `Refactoring` - Notifier module now is a util. + +### 2.20.1 + +- `Fix` - Create a new block when clicked at the bottom [#1588](https://github.com/codex-team/editor.js/issues/1588). +- `Fix` — Fix sanitisation problem with Inline Tools [#1631](https://github.com/codex-team/editor.js/issues/1631) +- `Fix` — Fix copy in FireFox [1625](https://github.com/codex-team/editor.js/issues/1625) +- `Refactoring` - The Sanitizer module is util now. +- `Refactoring` - Tooltip module is util now. +- `Refactoring` — Refactoring based on LGTM [#1577](https://github.com/codex-team/editor.js/issues/1577). +- `Refactoring` — Refactoring based on ESLint [#1636](https://github.com/codex-team/editor.js/issues/1636). + +### 2.20.0 + +- `New` — [Block Tunes API](block-tunes.md) added + ### 2.19.3 - `Fix` — Ignore error raised by Shortcut module ### 2.19.2 -- `New` - `toolbar.toggleBlockSettings()` API method added [#1442](https://github.com/codex-team/editor.js/issues/1421). +- `New` - `toolbar.toggleBlockSettings()` API method added [#1442](https://github.com/codex-team/editor.js/issues/1421). - `Improvements` - A generic type for Tool config added [#1516](https://github.com/codex-team/editor.js/issues/1516) - `Improvements` - Remove unused `force` option in `Caret.navigateNext()` and `Caret.navigatePrevious()` [#857](https://github.com/codex-team/editor.js/issues/857#issuecomment-770363438). - `Improvements` - Remove bundles from the repo [#1541](https://github.com/codex-team/editor.js/pull/1541). diff --git a/docs/api.md b/docs/api.md index 82a11c6c..ab0c9e3b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -77,6 +77,8 @@ use 'move' instead) `insert(type?: string, data?: BlockToolData, config?: ToolConfig, index?: number, needToFocus?: boolean)` - insert new Block with passed parameters +`update(id: string, data: BlockToolData)` - updates data for the block with passed id + #### SanitizerAPI `clean(taintString, config)` - method uses HTMLJanitor to clean taint string. diff --git a/docs/block-tunes.md b/docs/block-tunes.md new file mode 100644 index 00000000..6544bb95 --- /dev/null +++ b/docs/block-tunes.md @@ -0,0 +1,185 @@ +# Block Tunes + +Similar with [Tools](tools.md) represented Blocks, you can create Block Tunes and connect it to particular Tool or for all Tools. + +Block Tunes allows you to set any additional options to Blocks. For example, with corresponded Block Tunes you can mark Block as «spoiler», give it an anchor, set a background, and so on. + +## Base structure + +Tune's class should have the `isTune` property (static getter) set to `true`. + +Block Tune must implement the `render()` method which returns an HTML Element that will be appended to the Block Settings panel. + +- `render()` — create a button + +Also, you can provide optional methods + +- `wrap()` — wraps Block content with own HTML elements +- `save()` — save Tunes state on Editor's save + +At the constructor of Tune's class exemplar you will receive an object with following parameters: + +| Parameter | Description | +| --------- | ----------- | +| api | Editor's [API](api.md) obejct | +| config | Configuration of Block Tool Tune is connected to (might be useful in some cases) | +| block | [Block API](api.md#block-api) methods for block Tune is connected to | +| data | Saved Tune data | + +--- + +### render(): HTMLElement + +Method that returns button to append to the block settings area + +#### Parameters + +Method does not accept any parameters + +#### Return value + +type | description | +-- | -- | +`HTMLElement` | element that will be added to the block settings area | + +--- + +### wrap(blockContent: HTMLElement): HTMLElement + +Method that accepts Block's content and wrap it with your own layout. +Might be useful if you want to modify Block appearance. + +```javascript +class Tune { + wrap(blockContent) { + const myWrapper = document.createElement('div'); + + myWrapper.append(blockContent); + + return myWrapper; + } +} +``` + +#### Parameters + +name | type | description | +-- |-- | -- | +blockContent | HTMLElement | Block's content (might be wrapped by other Tunes) | + +#### Return value + +| type | description | +| -- | -- | +| HTMLElement | Your element that wraps block content | + +--- + +### save() + +Method should return Tune's state you want to save to Editor's output + +#### Parameters + +No parameters + +#### Return value + +type | description | +-- | -- | +`any` | any data you want to save | + +--- + +### static prepare() + +If you need to prepare some data for Tune (eg. load external script, create HTML nodes in the document, etc) you can use the static `prepare()` method. + +It accepts tunes config passed on Editor's initialization as an argument: + + +```javascript +class Tune { + static prepare(config) { + loadScript(); + insertNodes(); + ... + } +} +``` + +#### Parameters + +type | description | +-- | -- | +`object` | your Tune configuration | + + +#### Return value + +No return value + +--- + +### static reset() + +On Editor destroy you can use an opposite method `reset` to clean up all prepared data: + +```javascript +class Tune { + static reset() { + cleanUpScripts(); + deleteNodes(); + ... + } +} +``` + +#### Parameters + +No parameters + +#### Return value + +No return value + +--- + +### static get sanitize() + +If your Tune inserts any HTML markup into Block's content you need to provide sanitize configuration, so your HTML is not trimmed on save. + +Please see more information at [sanitizer page](sanitizer.md). + + +```javascript +class Tune { + static get sanitize() { + return { + sup: true + } + } +} +``` + +## Format + +Tunes data is saved to `tunes` property of output object: + +``` +{ + blocks: [ + { + type: 'paragraph', + data: { + text: 'This is paragraph with Tune' + }, + tunes: { + 'my-tune-name': {}, + favorite: true, + anchor: 'might be string' + } + } + ] +} +``` diff --git a/docs/installation.md b/docs/installation.md index 6660ce80..29565665 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -193,7 +193,7 @@ var editor = new EditorJS({ /** * onChange callback */ - onChange: () => {console.log('Now I know that Editor\'s content changed!')} + onChange: (editorAPI, affectedBlockAPI) => {console.log('Now I know that Editor\'s content changed!')} }); ``` diff --git a/docs/tools.md b/docs/tools.md index 6f782ecc..85108cf8 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -59,6 +59,7 @@ Options that Tool can specify. All settings should be passed as static propertie | `toolbox` | _Object_ | `undefined` | Pass here `icon` and `title` to display this `Tool` in the Editor's `Toolbox`
`icon` - HTML string with icon for Toolbox
`title` - optional title to display in Toolbox | | `enableLineBreaks` | _Boolean_ | `false` | With this option, Editor.js won't handle Enter keydowns. Can be helpful for Tools like `` where line breaks should be handled by default behaviour. | | `isInline` | _Boolean_ | `false` | Describes Tool as a [Tool for the Inline Toolbar](tools-inline.md) | +| `isTune` | _Boolean_ | `false` | Describes Tool as a [Block Tune](block-tunes.md) | | `sanitize` | _Object_ | `undefined` | Config for automatic sanitizing of saved data. See [Sanitize](#sanitize) section. | | `conversionConfig` | _Object_ | `undefined` | Config allows Tool to specify how it can be converted into/from another Tool. See [Conversion config](#conversion-config) section. | diff --git a/example/example-dev.html b/example/example-dev.html index 84553b66..4700e8e9 100644 --- a/example/example-dev.html +++ b/example/example-dev.html @@ -68,7 +68,8 @@ - + + @@ -131,7 +132,7 @@ image: SimpleImage, list: { - class: List, + class: NestedList, inlineToolbar: true, shortcut: 'CMD+SHIFT+L' }, @@ -195,6 +196,7 @@ data: { blocks: [ { + id: "zcKCF1S7X8", type: "header", data: { text: "Editor.js", @@ -202,13 +204,15 @@ } }, { - type : 'paragraph', - data : { - text : 'Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration.' + "id": "b6ji-DvaKb", + "type": "paragraph", + "data": { + "text": "Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration." } }, { type: "header", + id: "7ItVl5biRo", data: { text: "Key features", level: 3 @@ -216,17 +220,28 @@ }, { type : 'list', + id: "SSBSguGvP7", data : { items : [ - 'It is a block-styled editor', - 'It returns clean data output in JSON', - 'Designed to be extendable and pluggable with a simple API', + { + content: 'It is a block-styled editor', + items: [] + }, + { + content: 'It returns clean data output in JSON', + items: [] + }, + { + content: 'Designed to be extendable and pluggable with a simple API', + items: [] + } ], style: 'unordered' } }, { type: "header", + id: "QZFox1m_ul", data: { text: "What does it mean «block-styled editor»", level: 3 @@ -234,18 +249,21 @@ }, { type : 'paragraph', + id: "bwnFX5LoX7", data : { text : 'Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor\'s Core.' } }, { type : 'paragraph', + id: "mTrPOHAQTe", data : { text : `There are dozens of ready-to-use Blocks and the simple API for creation any Block you need. For example, you can implement Blocks for Tweets, Instagram posts, surveys and polls, CTA-buttons and even games.` } }, { type: "header", + id: "1sYMhUrznu", data: { text: "What does it mean clean data output", level: 3 @@ -253,34 +271,40 @@ }, { type : 'paragraph', + id: "jpd7WEXrJG", data : { text : 'Classic WYSIWYG-editors produce raw HTML-markup with both content data and content appearance. On the contrary, Editor.js outputs JSON object with data of each Block. You can see an example below' } }, { type : 'paragraph', + id: "0lOGNUKxqt", data : { text : `Given data can be used as you want: render with HTML for Web clients, render natively for mobile apps, create markup for Facebook Instant Articles or Google AMP, generate an audio version and so on.` } }, { type : 'paragraph', + id: "WvX7kBjp0I", data : { text : 'Clean data is useful to sanitize, validate and process on the backend.' } }, { type : 'delimiter', + id: "H9LWKQ3NYd", data : {} }, { type : 'paragraph', + id: "h298akk2Ad", data : { text : 'We have been working on this project more than three years. Several large media projects help us to test and debug the Editor, to make its core more stable. At the same time we significantly improved the API. Now, it can be used to create any plugin for any task. Hope you enjoy. 😏' } }, { type: 'image', + id: "9802bjaAA2", data: { url: 'assets/codex2x.png', caption: '', @@ -294,8 +318,8 @@ onReady: function(){ saveButton.click(); }, - onChange: function() { - console.log('something changed'); + onChange: function(api, block) { + console.log('something changed', block); }, }); diff --git a/example/example.html b/example/example.html index 7de5baeb..abc6a5f6 100644 --- a/example/example.html +++ b/example/example.html @@ -281,8 +281,8 @@ onReady: function(){ saveButton.click(); }, - onChange: function() { - console.log('something changed'); + onChange: function(api, block) { + console.log('something changed', block); } }); diff --git a/example/tools/nested-list b/example/tools/nested-list new file mode 160000 index 00000000..e5edb603 --- /dev/null +++ b/example/tools/nested-list @@ -0,0 +1 @@ +Subproject commit e5edb6038a715075381cd9aa663872b2325abe1c diff --git a/example/tools/table b/example/tools/table index 5c1a73a8..0bcc8d4d 160000 --- a/example/tools/table +++ b/example/tools/table @@ -1 +1 @@ -Subproject commit 5c1a73a8022c18ac1c15ee8d0134caae029bfbe9 +Subproject commit 0bcc8d4da2bfb15e7bc3c50eb15a1ec16eb77f96 diff --git a/package.json b/package.json index 19aff379..6c13eb80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.19.3", + "version": "2.22.2", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editor.js", "types": "./types/index.d.ts", @@ -24,7 +24,7 @@ "svg": "svg-sprite-generate -d src/assets/ -o dist/sprite.svg", "pull_tools": "git submodule update --init --recursive", "checkout_tools": "git submodule foreach git pull origin master", - "test:e2e": "cypress run" + "test:e2e": "yarn build && cypress run" }, "author": "CodeX", "license": "Apache-2.0", @@ -37,21 +37,28 @@ "@babel/plugin-transform-runtime": "^7.9.0", "@babel/polyfill": "^7.8.7", "@babel/preset-env": "^7.9.5", + "@babel/preset-typescript": "^7.13.0", "@babel/register": "^7.9.0", "@babel/runtime": "^7.9.2", "@codexteam/shortcuts": "^1.1.1", + "@cypress/code-coverage": "^3.9.2", + "@cypress/webpack-preprocessor": "^5.6.0", + "@types/node": "^14.14.35", "@types/webpack": "^4.41.12", "@types/webpack-env": "^1.15.2", "babel-loader": "^8.1.0", "babel-plugin-add-module-exports": "^1.0.0", "babel-plugin-class-display-name": "^2.1.0", + "babel-plugin-istanbul": "^6.0.0", "core-js": "3.6.5", "css-loader": "^3.5.3", "cssnano": "^4.1.10", - "cypress": "^5.5.0", + "cypress": "^6.8.0", + "cypress-intellij-reporter": "^0.0.6", "eslint": "^6.8.0", "eslint-config-codex": "^1.3.3", "eslint-loader": "^4.0.2", + "eslint-plugin-chai-friendly": "^0.6.0", "eslint-plugin-cypress": "^2.11.2", "extract-text-webpack-plugin": "^3.0.2", "html-janitor": "^2.0.4", @@ -80,6 +87,7 @@ }, "dependencies": { "codex-notifier": "^1.1.2", - "codex-tooltip": "^1.0.1" + "codex-tooltip": "^1.0.2", + "nanoid": "^3.1.22" } } diff --git a/src/components/__module.ts b/src/components/__module.ts index 877ecd88..f18de55f 100644 --- a/src/components/__module.ts +++ b/src/components/__module.ts @@ -93,8 +93,7 @@ export default class Module { /** * @class * - * @param {EditorConfig} config - Editor's config - * @param {EventsDispatcher} eventsDispatcher - Editor's event dispatcher + * @param {ModuleConfig} - Module config */ constructor({ config, eventsDispatcher }: ModuleConfig) { if (new.target === Module) { diff --git a/src/components/block-tunes/block-tune-delete.ts b/src/components/block-tunes/block-tune-delete.ts index 87a8cf85..a9b4c08a 100644 --- a/src/components/block-tunes/block-tune-delete.ts +++ b/src/components/block-tunes/block-tune-delete.ts @@ -11,6 +11,11 @@ import $ from '../dom'; * */ export default class DeleteTune implements BlockTune { + /** + * Set Tool is Tune + */ + public static readonly isTune = true; + /** * Property that contains Editor.js API methods * @@ -70,7 +75,9 @@ export default class DeleteTune implements BlockTune { /** * Enable tooltip module */ - this.api.tooltip.onHover(this.nodes.button, this.api.i18n.t('Delete')); + this.api.tooltip.onHover(this.nodes.button, this.api.i18n.t('Delete'), { + hidingDelay: 300, + }); return this.nodes.button; } diff --git a/src/components/block-tunes/block-tune-move-down.ts b/src/components/block-tunes/block-tune-move-down.ts index f095f6db..3f5f4867 100644 --- a/src/components/block-tunes/block-tune-move-down.ts +++ b/src/components/block-tunes/block-tune-move-down.ts @@ -12,6 +12,11 @@ import { API, BlockTune } from '../../../types'; * */ export default class MoveDownTune implements BlockTune { + /** + * Set Tool is Tune + */ + public static readonly isTune = true; + /** * Property that contains Editor.js API methods * @@ -58,7 +63,9 @@ export default class MoveDownTune implements BlockTune { /** * Enable tooltip module on button */ - this.api.tooltip.onHover(moveDownButton, this.api.i18n.t('Move down')); + this.api.tooltip.onHover(moveDownButton, this.api.i18n.t('Move down'), { + hidingDelay: 300, + }); return moveDownButton; } diff --git a/src/components/block-tunes/block-tune-move-up.ts b/src/components/block-tunes/block-tune-move-up.ts index 0005af91..940d3da4 100644 --- a/src/components/block-tunes/block-tune-move-up.ts +++ b/src/components/block-tunes/block-tune-move-up.ts @@ -11,6 +11,11 @@ import { API, BlockTune } from '../../../types'; * */ export default class MoveUpTune implements BlockTune { + /** + * Set Tool is Tune + */ + public static readonly isTune = true; + /** * Property that contains Editor.js API methods * @@ -57,7 +62,9 @@ export default class MoveUpTune implements BlockTune { /** * Enable tooltip module on button */ - this.api.tooltip.onHover(moveUpButton, this.api.i18n.t('Move up')); + this.api.tooltip.onHover(moveUpButton, this.api.i18n.t('Move up'), { + hidingDelay: 300, + }); return moveUpButton; } diff --git a/src/components/block/api.ts b/src/components/block/api.ts index 34e23d89..556323ee 100644 --- a/src/components/block/api.ts +++ b/src/components/block/api.ts @@ -14,6 +14,14 @@ function BlockAPI( block: Block ): void { const blockAPI: BlockAPIInterface = { + /** + * Block id + * + * @returns {string} + */ + get id(): string { + return block.id; + }, /** * Tool name * diff --git a/src/components/block/index.ts b/src/components/block/index.ts index fef14126..85771882 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -1,13 +1,10 @@ import { BlockAPI as BlockAPIInterface, - BlockTool, - BlockToolConstructable, + BlockTool as IBlockTool, BlockToolData, - BlockTune, - BlockTuneConstructable, + BlockTune as IBlockTune, SanitizerConfig, - ToolConfig, - ToolSettings + ToolConfig } from '../../../types'; import { SavedData } from '../../../types/data-formats'; @@ -15,22 +12,22 @@ import $ from '../dom'; import * as _ from '../utils'; import ApiModules from '../modules/api'; import BlockAPI from './api'; -import { ToolType } from '../modules/tools'; - -/** Import default tunes */ -import MoveUpTune from '../block-tunes/block-tune-move-up'; -import DeleteTune from '../block-tunes/block-tune-delete'; -import MoveDownTune from '../block-tunes/block-tune-move-down'; import SelectionUtils from '../selection'; +import BlockTool from '../tools/block'; + +import BlockTune from '../tools/tune'; +import { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; +import ToolsCollection from '../tools/collection'; +import EventsDispatcher from '../utils/events'; /** * Interface describes Block class constructor argument */ interface BlockConstructorOptions { /** - * Tool's name + * Block's id. Should be passed for existed block, and omitted for a new one. */ - name: string; + id?: string; /** * Initial Block data @@ -38,14 +35,9 @@ interface BlockConstructorOptions { data: BlockToolData; /** - * Tool's class or constructor function + * Tool object */ - Tool: BlockToolConstructable; - - /** - * Tool settings from initial config - */ - settings: ToolSettings; + tool: BlockTool; /** * Editor's API methods @@ -56,6 +48,11 @@ interface BlockConstructorOptions { * This flag indicates that the Block should be constructed in the read-only mode. */ readOnly: boolean; + + /** + * Tunes data for current Block + */ + tunesData: {[name: string]: BlockTuneData}; } /** @@ -83,6 +80,11 @@ export enum BlockToolAPI { ON_PASTE = 'onPaste', } +/** + * Names of events supported by Block class + */ +type BlockEvents = 'didMutated'; + /** * @classdesc Abstract Block class that contains Block information, Tool name and Tool class instance * @@ -90,7 +92,7 @@ export enum BlockToolAPI { * @property {HTMLElement} holder - Div element that wraps block content with Tool's content. Has `ce-block` CSS class * @property {HTMLElement} pluginsContent - HTML content that returns by Tool's render function */ -export default class Block { +export default class Block extends EventsDispatcher { /** * CSS classes for the Block * @@ -107,35 +109,35 @@ export default class Block { }; } + /** + * Block unique identifier + */ + public id: string; + /** * Block Tool`s name */ - public name: string; + public readonly name: string; /** * Instance of the Tool Block represents */ - public tool: BlockTool; - - /** - * Class blueprint of the ool Block represents - */ - public class: BlockToolConstructable; + public readonly tool: BlockTool; /** * User Tool configuration */ - public settings: ToolConfig; + public readonly settings: ToolConfig; /** * Wrapper for Block`s content */ - public holder: HTMLDivElement; + public readonly holder: HTMLDivElement; /** * Tunes used by Tool */ - public tunes: BlockTune[]; + public readonly tunes: ToolsCollection; /** * Tool's user configuration @@ -149,6 +151,27 @@ export default class Block { */ private cachedInputs: HTMLElement[] = []; + /** + * Tool class instance + */ + private readonly toolInstance: IBlockTool; + + /** + * User provided Block Tunes instances + */ + private readonly tunesInstances: Map = new Map(); + + /** + * Editor provided Block Tunes instances + */ + private readonly defaultTunesInstances: Map = new Map(); + + /** + * If there is saved data for Tune which is not available at the moment, + * we will store it here and provide back on save so data is not lost + */ + private unavailableTunesData: {[name: string]: BlockTuneData} = {}; + /** * Editor`s API module */ @@ -178,7 +201,19 @@ export default class Block { /** * Is fired when DOM mutation has been happened */ - private didMutated = _.debounce((): void => { + private didMutated = _.debounce((mutations: MutationRecord[]): void => { + const shouldFireUpdate = !mutations.some(({ addedNodes = [], removedNodes }) => { + return [...Array.from(addedNodes), ...Array.from(removedNodes)] + .some(node => $.isElement(node) && (node as HTMLElement).dataset.mutationFree === 'true'); + }); + + /** + * In case some mutation free elements are added or removed, do not trigger didMutated event + */ + if (!shouldFireUpdate) { + return; + } + /** * Drop cache */ @@ -190,6 +225,8 @@ export default class Block { this.updateCurrentInput(); this.call(BlockToolAPI.UPDATED); + + this.emit('didMutated', this); }, this.modificationDebounceTimer); /** @@ -199,43 +236,42 @@ export default class Block { /** * @param {object} options - block constructor options - * @param {string} options.name - Tool name that passed on initialization + * @param {string} [options.id] - block's id. Will be generated if omitted. * @param {BlockToolData} options.data - Tool's initial data - * @param {BlockToolConstructable} options.Tool — Tool's class - * @param {ToolSettings} options.settings - default tool's config + * @param {BlockToolConstructable} options.tool — block's tool * @param options.api - Editor API module for pass it to the Block Tunes * @param {boolean} options.readOnly - Read-Only flag */ constructor({ - name, + id = _.generateBlockId(), data, - Tool, - settings, + tool, api, readOnly, + tunesData, }: BlockConstructorOptions) { - this.name = name; - this.class = Tool; - this.settings = settings; - this.config = settings.config || {}; + super(); + + this.name = tool.name; + this.id = id; + this.settings = tool.settings; + this.config = tool.settings.config || {}; this.api = api; this.blockAPI = new BlockAPI(this); this.mutationObserver = new MutationObserver(this.didMutated); - this.tool = new Tool({ - data, - config: this.config, - api: this.api.getMethodsForTool(name, ToolType.Block), - block: this.blockAPI, - readOnly, - }); + this.tool = tool; + this.toolInstance = tool.create(data, this.blockAPI, readOnly); - this.holder = this.compose(); /** * @type {BlockTune[]} */ - this.tunes = this.makeTunes(); + this.tunes = tool.tunes; + + this.composeTunes(tunesData); + + this.holder = this.compose(); } /** @@ -349,7 +385,7 @@ export default class Block { * @returns {object} */ public get sanitize(): SanitizerConfig { - return this.tool.sanitize; + return this.tool.sanitizeConfig; } /** @@ -359,7 +395,7 @@ export default class Block { * @returns {boolean} */ public get mergeable(): boolean { - return _.isFunction(this.tool.merge); + return _.isFunction(this.toolInstance.merge); } /** @@ -424,8 +460,12 @@ export default class Block { public set selected(state: boolean) { if (state) { this.holder.classList.add(Block.CSS.selected); + + SelectionUtils.addFakeCursor(this.holder); } else { this.holder.classList.remove(Block.CSS.selected); + + SelectionUtils.removeFakeCursor(this.holder); } } @@ -502,7 +542,7 @@ export default class Block { /** * call Tool's method with the instance context */ - if (this.tool[methodName] && this.tool[methodName] instanceof Function) { + if (_.isFunction(this.toolInstance[methodName])) { if (methodName === BlockToolAPI.APPEND_CALLBACK) { _.log( '`appendCallback` hook is deprecated and will be removed in the next major release. ' + @@ -513,7 +553,7 @@ export default class Block { try { // eslint-disable-next-line no-useless-call - this.tool[methodName].call(this.tool, params); + this.toolInstance[methodName].call(this.toolInstance, params); } catch (e) { _.log(`Error during '${methodName}' call: ${e.message}`, 'error'); } @@ -526,7 +566,7 @@ export default class Block { * @param {BlockToolData} data - data to merge */ public async mergeWith(data: BlockToolData): Promise { - await this.tool.merge(data); + await this.toolInstance.merge(data); } /** @@ -536,7 +576,22 @@ export default class Block { * @returns {object} */ public async save(): Promise { - const extractedBlock = await this.tool.save(this.pluginsContent as HTMLElement); + const extractedBlock = await this.toolInstance.save(this.pluginsContent as HTMLElement); + const tunesData: {[name: string]: BlockTuneData} = this.unavailableTunesData; + + [ + ...this.tunesInstances.entries(), + ...this.defaultTunesInstances.entries(), + ] + .forEach(([name, tune]) => { + if (_.isFunction(tune.save)) { + try { + tunesData[name] = tune.save(); + } catch (e) { + _.log(`Tune ${tune.constructor.name} save method throws an Error %o`, 'warn', e); + } + } + }); /** * Measuring execution time @@ -550,8 +605,10 @@ export default class Block { measuringEnd = window.performance.now(); return { + id: this.id, tool: this.name, data: finishedExtraction, + tunes: tunesData, time: measuringEnd - measuringStart, }; }) @@ -572,57 +629,30 @@ export default class Block { public async validate(data: BlockToolData): Promise { let isValid = true; - if (this.tool.validate instanceof Function) { - isValid = await this.tool.validate(data); + if (this.toolInstance.validate instanceof Function) { + isValid = await this.toolInstance.validate(data); } return isValid; } - /** - * Make an array with default settings - * Each block has default tune instance that have states - * - * @returns {BlockTune[]} - */ - public makeTunes(): BlockTune[] { - const tunesList = [ - { - name: 'moveUp', - Tune: MoveUpTune, - }, - { - name: 'delete', - Tune: DeleteTune, - }, - { - name: 'moveDown', - Tune: MoveDownTune, - }, - ]; - - // Pluck tunes list and return tune instances with passed Editor API and settings - return tunesList.map(({ name, Tune }: {name: string; Tune: BlockTuneConstructable}) => { - return new Tune({ - api: this.api.getMethodsForTool(name, ToolType.Tune), - settings: this.config, - }); - }); - } - /** * Enumerates initialized tunes and returns fragment that can be appended to the toolbars area * - * @returns {DocumentFragment} + * @returns {DocumentFragment[]} */ - public renderTunes(): DocumentFragment { + public renderTunes(): [DocumentFragment, DocumentFragment] { const tunesElement = document.createDocumentFragment(); + const defaultTunesElement = document.createDocumentFragment(); - this.tunes.forEach((tune) => { + this.tunesInstances.forEach((tune) => { $.append(tunesElement, tune.render()); }); + this.defaultTunesInstances.forEach((tune) => { + $.append(defaultTunesElement, tune.render()); + }); - return tunesElement; + return [tunesElement, defaultTunesElement]; } /** @@ -672,6 +702,26 @@ export default class Block { this.removeInputEvents(); } + /** + * Call Tool instance destroy method + */ + public destroy(): void { + super.destroy(); + + if (_.isFunction(this.toolInstance.destroy)) { + this.toolInstance.destroy(); + } + } + + /** + * Call Tool instance renderSettings method + */ + public renderSettings(): HTMLElement | undefined { + if (_.isFunction(this.toolInstance.renderSettings)) { + return this.toolInstance.renderSettings(); + } + } + /** * Make default Block wrappers and put Tool`s content there * @@ -680,14 +730,60 @@ export default class Block { private compose(): HTMLDivElement { const wrapper = $.make('div', Block.CSS.wrapper) as HTMLDivElement, contentNode = $.make('div', Block.CSS.content), - pluginsContent = this.tool.render(); + pluginsContent = this.toolInstance.render(); contentNode.appendChild(pluginsContent); - wrapper.appendChild(contentNode); + + /** + * Block Tunes might wrap Block's content node to provide any UI changes + * + * + * + * + * + * + */ + let wrappedContentNode: HTMLElement = contentNode; + + [...this.tunesInstances.values(), ...this.defaultTunesInstances.values()] + .forEach((tune) => { + if (_.isFunction(tune.wrap)) { + try { + wrappedContentNode = tune.wrap(wrappedContentNode); + } catch (e) { + _.log(`Tune ${tune.constructor.name} wrap method throws an Error %o`, 'warn', e); + } + } + }); + + wrapper.appendChild(wrappedContentNode); return wrapper; } + /** + * Instantiate Block Tunes + * + * @param tunesData - current Block tunes data + * @private + */ + private composeTunes(tunesData: {[name: string]: BlockTuneData}): void { + Array.from(this.tunes.values()).forEach((tune) => { + const collection = tune.isInternal ? this.defaultTunesInstances : this.tunesInstances; + + collection.set(tune.name, tune.create(tunesData[tune.name], this.blockAPI)); + }); + + /** + * Check if there is some data for not available tunes + */ + Object.entries(tunesData).forEach(([name, data]) => { + if (!this.tunesInstances.has(name)) { + this.unavailableTunesData[name] = data; + } + }); + } + /** * Is fired when text input or contentEditable is focused */ @@ -709,6 +805,13 @@ export default class Block { private addInputEvents(): void { this.inputs.forEach(input => { input.addEventListener('focus', this.handleFocus); + + /** + * If input is native input add oninput listener to observe changes + */ + if ($.isNativeInput(input)) { + input.addEventListener('input', this.didMutated); + } }); } @@ -718,6 +821,10 @@ export default class Block { private removeInputEvents(): void { this.inputs.forEach(input => { input.removeEventListener('focus', this.handleFocus); + + if ($.isNativeInput(input)) { + input.removeEventListener('input', this.didMutated); + } }); } } diff --git a/src/components/dom.ts b/src/components/dom.ts index b9d694ac..7e45cd6e 100644 --- a/src/components/dom.ts +++ b/src/components/dom.ts @@ -202,7 +202,7 @@ export default class Dom { public static get allInputsSelector(): string { const allowedInputTypes = ['text', 'password', 'email', 'number', 'search', 'tel', 'url']; - return '[contenteditable], textarea, input:not([type]), ' + + return '[contenteditable=true], textarea, input:not([type]), ' + allowedInputTypes.map((type) => `input[type="${type}"]`).join(', '); } diff --git a/src/components/inline-tools/inline-tool-link.ts b/src/components/inline-tools/inline-tool-link.ts index b55120bb..6c5db6d5 100644 --- a/src/components/inline-tools/inline-tool-link.ts +++ b/src/components/inline-tools/inline-tool-link.ts @@ -2,7 +2,7 @@ import SelectionUtils from '../selection'; import $ from '../dom'; import * as _ from '../utils'; -import { API, InlineTool, SanitizerConfig } from '../../../types'; +import { InlineTool, SanitizerConfig } from '../../../types'; import { Notifier, Toolbar, I18n } from '../../../types/api'; /** diff --git a/src/components/modules/api/blocks.ts b/src/components/modules/api/blocks.ts index a65db8b0..d5a64ad9 100644 --- a/src/components/modules/api/blocks.ts +++ b/src/components/modules/api/blocks.ts @@ -23,11 +23,13 @@ export default class BlocksAPI extends Module { swap: (fromIndex: number, toIndex: number): void => this.swap(fromIndex, toIndex), move: (toIndex: number, fromIndex?: number): void => this.move(toIndex, fromIndex), getBlockByIndex: (index: number): BlockAPIInterface | void => this.getBlockByIndex(index), + getById: (id: string): BlockAPIInterface | null => this.getById(id), getCurrentBlockIndex: (): number => this.getCurrentBlockIndex(), getBlocksCount: (): number => this.getBlocksCount(), stretchBlock: (index: number, status = true): void => this.stretchBlock(index, status), insertNewBlock: (): void => this.insertNewBlock(), insert: this.insert, + update: this.update, }; } @@ -66,6 +68,23 @@ export default class BlocksAPI extends Module { return new BlockAPI(block); } + /** + * Returns BlockAPI object by Block id + * + * @param id - id of block to get + */ + public getById(id: string): BlockAPIInterface | null { + const block = this.Editor.BlockManager.getBlockById(id); + + if (block === undefined) { + _.logLabeled('There is no block with id `' + id + '`', 'warn'); + + return null; + } + + return new BlockAPI(block); + } + /** * Call Block Manager method that swap Blocks * @@ -229,4 +248,32 @@ export default class BlocksAPI extends Module { 'Use blocks.insert() instead.', 'warn'); this.insert(); } + + /** + * Updates block data by id + * + * @param id - id of the block to update + * @param data - the new data + */ + public update = (id: string, data: BlockToolData): void => { + const { BlockManager } = this.Editor; + const block = BlockManager.getBlockById(id); + + if (!block) { + _.log('blocks.update(): Block with passed id was not found', 'warn'); + + return; + } + + const blockIndex = BlockManager.getBlockIndex(block); + + BlockManager.insert({ + id: block.id, + tool: block.name, + data, + index: blockIndex, + replace: true, + tunes: block.tunes, + }); + } } diff --git a/src/components/modules/api/i18n.ts b/src/components/modules/api/i18n.ts index 6f702c60..9ecb9ceb 100644 --- a/src/components/modules/api/i18n.ts +++ b/src/components/modules/api/i18n.ts @@ -1,8 +1,8 @@ import { I18n } from '../../../../types/api'; import I18nInternal from '../../i18n'; -import { ToolType } from '../tools'; import { logLabeled } from '../../utils'; import Module from '../../__module'; +import { ToolClass } from '../../tools/collection'; /** * Provides methods for working with i18n @@ -11,17 +11,14 @@ export default class I18nAPI extends Module { /** * Return namespace section for tool or block tune * - * @param toolName - name of tool. Used to provide dictionary only for this tool - * @param toolType - 'block' for Block Tool, 'inline' for Inline Tool, 'tune' for Block Tunes + * @param tool - tool object */ - private static getNamespace(toolName: string, toolType: ToolType): string { - switch (toolType) { - case ToolType.Block: - case ToolType.Inline: - return `tools.${toolName}`; - case ToolType.Tune: - return `blockTunes.${toolName}`; + private static getNamespace(tool: ToolClass): string { + if (tool.isTune()) { + return `blockTunes.${tool.name}`; } + + return `tools.${tool.name}`; } /** @@ -40,15 +37,14 @@ export default class I18nAPI extends Module { /** * Return I18n API methods with tool namespaced dictionary * - * @param toolName - name of tool. Used to provide dictionary only for this tool - * @param toolType - 'block' for Block Tool, 'inline' for Inline Tool, 'tune' for Block Tunes + * @param tool - Tool object */ - public getMethodsForTool(toolName: string, toolType: ToolType): I18n { + public getMethodsForTool(tool: ToolClass): I18n { return Object.assign( this.methods, { t: (dictKey: string): string => { - return I18nInternal.t(I18nAPI.getNamespace(toolName, toolType), dictKey); + return I18nInternal.t(I18nAPI.getNamespace(tool), dictKey); }, }); } diff --git a/src/components/modules/api/index.ts b/src/components/modules/api/index.ts index c765e5a6..db875771 100644 --- a/src/components/modules/api/index.ts +++ b/src/components/modules/api/index.ts @@ -7,7 +7,7 @@ */ import Module from '../../__module'; import { API as APIInterfaces } from '../../../../types'; -import { ToolType } from '../tools'; +import { ToolClass } from '../../tools/collection'; /** * @class API @@ -38,16 +38,13 @@ export default class API extends Module { /** * Returns Editor.js Core API methods for passed tool * - * @param toolName - how user name tool. It can be used in some API logic, - * for example in i18n to provide namespaced dictionary - * - * @param toolType - 'block' for Block Tool, 'inline' for Inline Tool, 'tune' for Block Tunes + * @param tool - tool object */ - public getMethodsForTool(toolName: string, toolType = ToolType.Block): APIInterfaces { + public getMethodsForTool(tool: ToolClass): APIInterfaces { return Object.assign( this.methods, { - i18n: this.Editor.I18nAPI.getMethodsForTool(toolName, toolType), + i18n: this.Editor.I18nAPI.getMethodsForTool(tool), } ) as APIInterfaces; } diff --git a/src/components/modules/api/notifier.ts b/src/components/modules/api/notifier.ts index ce89596a..dae16ba8 100644 --- a/src/components/modules/api/notifier.ts +++ b/src/components/modules/api/notifier.ts @@ -1,15 +1,38 @@ -import { Notifier } from '../../../../types/api'; +import EventsDispatcher from '../../utils/events'; +import { Notifier as INotifier } from '../../../../types/api'; +import Notifier from '../../utils/notifier'; import { ConfirmNotifierOptions, NotifierOptions, PromptNotifierOptions } from 'codex-notifier'; import Module from '../../__module'; +import { ModuleConfig } from '../../../types-internal/module-config'; /** * */ export default class NotifierAPI extends Module { + /** + * Notifier utility Instance + */ + private notifier: Notifier; + + /** + * @class + * @param {object} moduleConfiguration - Module Configuration + * @param {EditorConfig} moduleConfiguration.config - Editor's config + * @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher + */ + constructor({ config, eventsDispatcher }: ModuleConfig) { + super({ + config, + eventsDispatcher, + }); + + this.notifier = new Notifier(); + } + /** * Available methods */ - public get methods(): Notifier { + public get methods(): INotifier { return { show: (options: NotifierOptions | ConfirmNotifierOptions | PromptNotifierOptions): void => this.show(options), }; @@ -21,6 +44,6 @@ export default class NotifierAPI extends Module { * @param {NotifierOptions} options - message option */ public show(options: NotifierOptions | ConfirmNotifierOptions | PromptNotifierOptions): void { - return this.Editor.Notifier.show(options); + return this.notifier.show(options); } } diff --git a/src/components/modules/api/readonly.ts b/src/components/modules/api/readonly.ts index 7b268c83..19a8c2d9 100644 --- a/src/components/modules/api/readonly.ts +++ b/src/components/modules/api/readonly.ts @@ -12,6 +12,7 @@ export default class ReadOnlyAPI extends Module { public get methods(): ReadOnly { return { toggle: (state): Promise => this.toggle(state), + isEnabled: this.isEnabled, }; } @@ -25,4 +26,11 @@ export default class ReadOnlyAPI extends Module { public toggle(state?: boolean): Promise { return this.Editor.ReadOnly.toggle(state); } + + /** + * Returns current read-only state + */ + public get isEnabled(): boolean { + return this.Editor.ReadOnly.isEnabled; + } } diff --git a/src/components/modules/api/sanitizer.ts b/src/components/modules/api/sanitizer.ts index 01ab2be2..79c91656 100644 --- a/src/components/modules/api/sanitizer.ts +++ b/src/components/modules/api/sanitizer.ts @@ -1,6 +1,7 @@ -import { Sanitizer } from '../../../../types/api'; +import { Sanitizer as ISanitizer } from '../../../../types/api'; import { SanitizerConfig } from '../../../../types/configs'; import Module from '../../__module'; +import { clean } from '../../utils/sanitizer'; /** * @class SanitizerAPI @@ -10,9 +11,9 @@ export default class SanitizerAPI extends Module { /** * Available methods * - * @returns {Sanitizer} + * @returns {SanitizerConfig} */ - public get methods(): Sanitizer { + public get methods(): ISanitizer { return { clean: (taintString, config): string => this.clean(taintString, config), }; @@ -27,6 +28,6 @@ export default class SanitizerAPI extends Module { * @returns {string} */ public clean(taintString: string, config: SanitizerConfig): string { - return this.Editor.Sanitizer.clean(taintString, config); + return clean(taintString, config); } } diff --git a/src/components/modules/api/tooltip.ts b/src/components/modules/api/tooltip.ts index 4094f4e8..30bb6585 100644 --- a/src/components/modules/api/tooltip.ts +++ b/src/components/modules/api/tooltip.ts @@ -1,16 +1,45 @@ -import { Tooltip } from '../../../../types/api'; +import { Tooltip as ITooltip } from '../../../../types/api'; import { TooltipContent, TooltipOptions } from 'codex-tooltip'; import Module from '../../__module'; - +import { ModuleConfig } from '../../../types-internal/module-config'; +import Tooltip from '../../utils/tooltip'; +import EventsDispatcher from '../../utils/events'; +import { EditorConfig } from '../../../../types'; /** * @class TooltipAPI * @classdesc Tooltip API */ export default class TooltipAPI extends Module { + /** + * Tooltip utility Instance + */ + private tooltip: Tooltip; + /** + * @class + * @param {object} moduleConfiguration - Module Configuration + * @param {EditorConfig} moduleConfiguration.config - Editor's config + * @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher + */ + constructor({ config, eventsDispatcher }: ModuleConfig) { + super({ + config, + eventsDispatcher, + }); + + this.tooltip = new Tooltip(); + } + + /** + * Destroy Module + */ + public destroy(): void { + this.tooltip.destroy(); + } + /** * Available methods */ - public get methods(): Tooltip { + public get methods(): ITooltip { return { show: (element: HTMLElement, content: TooltipContent, @@ -32,14 +61,14 @@ export default class TooltipAPI extends Module { * @param {TooltipOptions} options - tooltip options */ public show(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void { - this.Editor.Tooltip.show(element, content, options); + this.tooltip.show(element, content, options); } /** * Method hides tooltip on HTML page */ public hide(): void { - this.Editor.Tooltip.hide(); + this.tooltip.hide(); } /** @@ -50,6 +79,6 @@ export default class TooltipAPI extends Module { * @param {TooltipOptions} options - tooltip options */ public onHover(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void { - this.Editor.Tooltip.onHover(element, content, options); + this.tooltip.onHover(element, content, options); } } diff --git a/src/components/modules/blockEvents.ts b/src/components/modules/blockEvents.ts index a2fbef77..9173aca3 100644 --- a/src/components/modules/blockEvents.ts +++ b/src/components/modules/blockEvents.ts @@ -118,14 +118,14 @@ export default class BlockEvents extends Module { */ this.Editor.BlockSelection.clearSelection(event); - const { BlockManager, Tools, InlineToolbar, ConversionToolbar } = this.Editor; + const { BlockManager, InlineToolbar, ConversionToolbar } = this.Editor; const currentBlock = BlockManager.currentBlock; if (!currentBlock) { return; } - const canOpenToolbox = Tools.isDefault(currentBlock.tool) && currentBlock.isEmpty; + const canOpenToolbox = currentBlock.tool.isDefault && currentBlock.isEmpty; const conversionToolbarOpened = !currentBlock.isEmpty && ConversionToolbar.opened; const inlineToolbarOpened = !currentBlock.isEmpty && !SelectionUtils.isCollapsed && InlineToolbar.opened; @@ -167,7 +167,7 @@ export default class BlockEvents extends Module { * * @param {ClipboardEvent} event - clipboard event */ - public handleCommandC(event: ClipboardEvent): Promise { + public handleCommandC(event: ClipboardEvent): void { const { BlockSelection } = this.Editor; if (!BlockSelection.anyBlockSelected) { @@ -175,7 +175,7 @@ export default class BlockEvents extends Module { } // Copy Selected Blocks - return BlockSelection.copySelectedBlocks(event); + BlockSelection.copySelectedBlocks(event); } /** @@ -183,21 +183,26 @@ export default class BlockEvents extends Module { * * @param {ClipboardEvent} event - clipboard event */ - public async handleCommandX(event: ClipboardEvent): Promise { + public handleCommandX(event: ClipboardEvent): void { const { BlockSelection, BlockManager, Caret } = this.Editor; if (!BlockSelection.anyBlockSelected) { return; } - await BlockSelection.copySelectedBlocks(event); + BlockSelection.copySelectedBlocks(event).then(() => { + const selectionPositionIndex = BlockManager.removeSelectedBlocks(); - const selectionPositionIndex = BlockManager.removeSelectedBlocks(); + /** + * Insert default block in place of removed ones + */ + const insertedBlock = BlockManager.insertDefaultBlockAtIndex(selectionPositionIndex, true); - Caret.setToBlock(BlockManager.insertDefaultBlockAtIndex(selectionPositionIndex, true), Caret.positions.START); + Caret.setToBlock(insertedBlock, Caret.positions.START); - /** Clear selection */ - BlockSelection.clearSelection(event); + /** Clear selection */ + BlockSelection.clearSelection(event); + }); } /** @@ -206,15 +211,14 @@ export default class BlockEvents extends Module { * @param {KeyboardEvent} event - keydown */ private enter(event: KeyboardEvent): void { - const { BlockManager, Tools, UI } = this.Editor; + const { BlockManager, UI } = this.Editor; const currentBlock = BlockManager.currentBlock; - const tool = Tools.available[currentBlock.name]; /** * Don't handle Enter keydowns when Tool sets enableLineBreaks to true. * Uses for Tools like where line breaks should be handled by default behaviour. */ - if (tool && tool[Tools.INTERNAL_SETTINGS.IS_ENABLED_LINE_BREAKS]) { + if (currentBlock.tool.isLineBreaksEnabled) { return; } @@ -253,7 +257,7 @@ export default class BlockEvents extends Module { /** * If new Block is empty */ - if (this.Editor.Tools.isDefault(newCurrent.tool) && newCurrent.isEmpty) { + if (newCurrent.tool.isDefault && newCurrent.isEmpty) { /** * Show Toolbar */ @@ -276,7 +280,7 @@ export default class BlockEvents extends Module { private backspace(event: KeyboardEvent): void { const { BlockManager, BlockSelection, Caret } = this.Editor; const currentBlock = BlockManager.currentBlock; - const tool = this.Editor.Tools.available[currentBlock.name]; + const tool = currentBlock.tool; /** * Check if Block should be removed by current Backspace keydown @@ -314,7 +318,7 @@ export default class BlockEvents extends Module { * * But if caret is at start of the block, we allow to remove it by backspaces */ - if (tool && tool[this.Editor.Tools.INTERNAL_SETTINGS.IS_ENABLED_LINE_BREAKS] && !Caret.isAtStart) { + if (tool.isLineBreaksEnabled && !Caret.isAtStart) { return; } diff --git a/src/components/modules/blockManager.ts b/src/components/modules/blockManager.ts index 77c1447f..41e18865 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -11,7 +11,9 @@ import Module from '../__module'; import $ from '../dom'; import * as _ from '../utils'; import Blocks from '../blocks'; -import { BlockToolConstructable, BlockToolData, PasteEvent } from '../../../types'; +import { BlockToolData, PasteEvent } from '../../../types'; +import { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; +import BlockAPI from '../block/api'; /** * @typedef {BlockManager} BlockManager @@ -215,21 +217,26 @@ export default class BlockManager extends Module { * * @param {object} options - block creation options * @param {string} options.tool - tools passed in editor config {@link EditorConfig#tools} + * @param {string} [options.id] - unique id for this block * @param {BlockToolData} [options.data] - constructor params * * @returns {Block} */ - public composeBlock({ tool, data = {} }: {tool: string; data?: BlockToolData}): Block { + public composeBlock({ + tool: name, + data = {}, + id = undefined, + tunes: tunesData = {}, + }: {tool: string; id?: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}}): Block { const readOnly = this.Editor.ReadOnly.isEnabled; - const settings = this.Editor.Tools.getToolSettings(tool); - const Tool = this.Editor.Tools.available[tool] as BlockToolConstructable; + const tool = this.Editor.Tools.blockTools.get(name); const block = new Block({ - name: tool, + id, data, - Tool, - settings, + tool, api: this.Editor.API, readOnly, + tunesData, }); if (!readOnly) { @@ -243,26 +250,31 @@ export default class BlockManager extends Module { * Insert new block into _blocks * * @param {object} options - insert options - * @param {string} options.tool - plugin name, by default method inserts the default block type - * @param {object} options.data - plugin data - * @param {number} options.index - index where to insert new Block - * @param {boolean} options.needToFocus - flag shows if needed to update current Block index - * @param {boolean} options.replace - flag shows if block by passed index should be replaced with inserted one + * @param {string} [options.id] - block's unique id + * @param {string} [options.tool] - plugin name, by default method inserts the default block type + * @param {object} [options.data] - plugin data + * @param {number} [options.index] - index where to insert new Block + * @param {boolean} [options.needToFocus] - flag shows if needed to update current Block index + * @param {boolean} [options.replace] - flag shows if block by passed index should be replaced with inserted one * * @returns {Block} */ public insert({ + id = undefined, tool = this.config.defaultBlock, data = {}, index, needToFocus = true, replace = false, + tunes = {}, }: { + id?: string; tool?: string; data?: BlockToolData; index?: number; needToFocus?: boolean; replace?: boolean; + tunes?: {[name: string]: BlockTuneData}; } = {}): Block { let newIndex = index; @@ -271,12 +283,19 @@ export default class BlockManager extends Module { } const block = this.composeBlock({ + id, tool, data, + tunes, }); this._blocks.insert(newIndex, block, replace); + /** + * Force call of didMutated event on Block insertion + */ + this.blockDidMutated(block); + if (needToFocus) { this.currentBlockIndex = newIndex; } else if (newIndex <= this.currentBlockIndex) { @@ -348,6 +367,11 @@ export default class BlockManager extends Module { this._blocks[index] = block; + /** + * Force call of didMutated event on Block insertion + */ + this.blockDidMutated(block); + if (needToFocus) { this.currentBlockIndex = index; } else if (index <= this.currentBlockIndex) { @@ -413,8 +437,16 @@ export default class BlockManager extends Module { throw new Error('Can\'t find a Block to remove'); } + const blockToRemove = this._blocks[index]; + + blockToRemove.destroy(); this._blocks.remove(index); + /** + * Force call of didMutated event on Block removal + */ + this.blockDidMutated(blockToRemove); + if (this.currentBlockIndex >= index) { this.currentBlockIndex--; } @@ -508,6 +540,26 @@ export default class BlockManager extends Module { return this._blocks[index]; } + /** + * Returns an index for passed Block + * + * @param block - block to find index + */ + public getBlockIndex(block: Block): number { + return this._blocks.indexOf(block); + } + + /** + * Returns the Block by passed id + * + * @param id - id of block to get + * + * @returns {Block} + */ + public getBlockById(id): Block | undefined { + return this._blocks.array.find(block => block.id === id); + } + /** * Get Block instance by html element * @@ -665,6 +717,11 @@ export default class BlockManager extends Module { /** Now actual block moved so that current block index changed */ this.currentBlockIndex = toIndex; + + /** + * Force call of didMutated event on Block movement + */ + this.blockDidMutated(this.currentBlock); } /** @@ -703,9 +760,7 @@ export default class BlockManager extends Module { */ public async destroy(): Promise { await Promise.all(this.blocks.map((block) => { - if (_.isFunction(block.tool.destroy)) { - return block.tool.destroy(); - } + return block.destroy(); })); } @@ -732,6 +787,8 @@ export default class BlockManager extends Module { this.readOnlyMutableListeners.on(block.holder, 'dragleave', (event: DragEvent) => { BlockEvents.dragLeave(event); }); + + block.on('didMutated', (affectedBlock: Block) => this.blockDidMutated(affectedBlock)); } /** @@ -767,4 +824,15 @@ export default class BlockManager extends Module { private validateIndex(index: number): boolean { return !(index < 0 || index >= this._blocks.length); } + + /** + * Block mutation callback + * + * @param block - mutated block + */ + private blockDidMutated(block: Block): Block { + this.Editor.ModificationsObserver.onChange(new BlockAPI(block)); + + return block; + } } diff --git a/src/components/modules/blockSelection.ts b/src/components/modules/blockSelection.ts index 1c05d239..682b7aeb 100644 --- a/src/components/modules/blockSelection.ts +++ b/src/components/modules/blockSelection.ts @@ -13,6 +13,7 @@ import Shortcuts from '../utils/shortcuts'; import SelectionUtils from '../selection'; import { SanitizerConfig } from '../../../types/configs'; +import { clean } from '../utils/sanitizer'; /** * @@ -285,7 +286,7 @@ export default class BlockSelection extends Module { * * @returns {Promise} */ - public async copySelectedBlocks(e: ClipboardEvent): Promise { + public copySelectedBlocks(e: ClipboardEvent): Promise { /** * Prevent default copy */ @@ -297,22 +298,29 @@ export default class BlockSelection extends Module { /** * Make

tag that holds clean HTML */ - const cleanHTML = this.Editor.Sanitizer.clean(block.holder.innerHTML, this.sanitizerConfig); + const cleanHTML = clean(block.holder.innerHTML, this.sanitizerConfig); const fragment = $.make('p'); fragment.innerHTML = cleanHTML; fakeClipboard.appendChild(fragment); }); - const savedData = await Promise.all(this.selectedBlocks.map((block) => block.save())); - const textPlain = Array.from(fakeClipboard.childNodes).map((node) => node.textContent) .join('\n\n'); const textHTML = fakeClipboard.innerHTML; e.clipboardData.setData('text/plain', textPlain); e.clipboardData.setData('text/html', textHTML); - e.clipboardData.setData(this.Editor.Paste.MIME_TYPE, JSON.stringify(savedData)); + + return Promise + .all(this.selectedBlocks.map((block) => block.save())) + .then(savedData => { + try { + e.clipboardData.setData(this.Editor.Paste.MIME_TYPE, JSON.stringify(savedData)); + } catch (err) { + // In Firefox we can't set data in async function + } + }); } /** diff --git a/src/components/modules/caret.ts b/src/components/modules/caret.ts index 6cfa778d..f98f1e19 100644 --- a/src/components/modules/caret.ts +++ b/src/components/modules/caret.ts @@ -370,7 +370,7 @@ export default class Caret extends Module { * If last block is empty and it is an defaultBlock, set to that. * Otherwise, append new empty block and set to that */ - if (this.Editor.Tools.isDefault(lastBlock.tool) && lastBlock.isEmpty) { + if (lastBlock.tool.isDefault && lastBlock.isEmpty) { this.setToBlock(lastBlock); } else { const newBlock = this.Editor.BlockManager.insertAtEnd(); @@ -562,7 +562,7 @@ export default class Caret extends Module { * Detect an next line position from the caret position */ private detectNextLinePosition(): Position | false { - const { BlockManager, Tools } = this.Editor; + const { BlockManager } = this.Editor; const nextInput = BlockManager.currentBlock.nextInput; let nextBlock = BlockManager.nextContentfulBlock; @@ -573,7 +573,7 @@ export default class Caret extends Module { * This code allows to exit from the last non-initial tool: * https://github.com/codex-team/editor.js/issues/1103 */ - if (Tools.isDefault(BlockManager.currentBlock.tool)) { + if (BlockManager.currentBlock.tool.isDefault) { return false; } diff --git a/src/components/modules/modificationsObserver.ts b/src/components/modules/modificationsObserver.ts index 30f5797c..c1fc5123 100644 --- a/src/components/modules/modificationsObserver.ts +++ b/src/components/modules/modificationsObserver.ts @@ -1,195 +1,40 @@ -/** - * @module ModificationsObserver - * - * Handles any mutations - * and gives opportunity to handle outside - */ - import Module from '../__module'; +import { BlockAPI } from '../../../types'; import * as _ from '../utils'; -import Block from '../block'; /** - * + * Single entry point for Block mutation events */ export default class ModificationsObserver extends Module { /** - * Debounce Timer - * - * @type {number} - */ - public static readonly DebounceTimer = 450; - - /** - * MutationObserver instance - */ - private observer: MutationObserver; - - /** - * Allows to temporary disable mutations handling + * Flag shows onChange event is disabled */ private disabled = false; /** - * Used to prevent several mutation callback execution - * - * @type {Function} - */ - private mutationDebouncer = _.debounce(() => { - this.updateNativeInputs(); - - if (_.isFunction(this.config.onChange)) { - this.config.onChange(this.Editor.API.methods); - } - }, ModificationsObserver.DebounceTimer); - - /** - * Array of native inputs in Blocks. - * Changes in native inputs are not handled by modification observer, so we need to set change event listeners on them - */ - private nativeInputs: HTMLElement[] = []; - - /** - * Clear timeout and set null to mutationDebouncer property - */ - public destroy(): void { - this.mutationDebouncer = null; - if (this.observer) { - this.observer.disconnect(); - } - this.observer = null; - this.nativeInputs.forEach((input) => this.listeners.off(input, 'input', this.mutationDebouncer)); - this.mutationDebouncer = null; - } - - /** - * Set read-only state - * - * @param {boolean} readOnlyEnabled - read only flag value - */ - public toggleReadOnly(readOnlyEnabled: boolean): void { - if (readOnlyEnabled) { - this.disableModule(); - } else { - this.enableModule(); - } - } - - /** - * Allows to disable observer, - * for example when Editor wants to stealthy mutate DOM - */ - public disable(): void { - this.disabled = true; - } - - /** - * Enables mutation handling - * Should be called after .disable() + * Enables onChange event */ public enable(): void { this.disabled = false; } /** - * setObserver - * - * sets 'DOMSubtreeModified' listener on Editor's UI.nodes.redactor - * so that User can handle outside from API + * Disables onChange event */ - private setObserver(): void { - const { UI } = this.Editor; - const observerOptions = { - childList: true, - attributes: true, - subtree: true, - characterData: true, - characterDataOldValue: true, - }; - - this.observer = new MutationObserver((mutationList, observer) => { - this.mutationHandler(mutationList, observer); - }); - this.observer.observe(UI.nodes.redactor, observerOptions); + public disable(): void { + this.disabled = true; } /** - * MutationObserver events handler + * Call onChange event passed to Editor.js configuration * - * @param {MutationRecord[]} mutationList - list of mutations - * @param {MutationObserver} observer - observer instance + * @param block - changed Block */ - private mutationHandler(mutationList: MutationRecord[], observer: MutationObserver): void { - /** - * Skip mutations in stealth mode - */ - if (this.disabled) { + public onChange(block: BlockAPI): void { + if (this.disabled || !_.isFunction(this.config.onChange)) { return; } - /** - * We divide two Mutation types: - * 1) mutations that concerns client changes: settings changes, symbol added, deletion, insertions and so on - * 2) functional changes. On each client actions we set functional identifiers to interact with user - */ - let contentMutated = false; - - mutationList.forEach((mutation) => { - switch (mutation.type) { - case 'childList': - case 'characterData': - contentMutated = true; - break; - case 'attributes': - /** - * Changes on Element.ce-block usually is functional - */ - if (!(mutation.target as Element).classList.contains(Block.CSS.wrapper)) { - contentMutated = true; - } - break; - } - }); - - /** call once */ - if (contentMutated) { - this.mutationDebouncer(); - } - } - - /** - * Gets native inputs and set oninput event handler - */ - private updateNativeInputs(): void { - if (this.nativeInputs) { - this.nativeInputs.forEach((input) => { - this.listeners.off(input, 'input'); - }); - } - - this.nativeInputs = Array.from(this.Editor.UI.nodes.redactor.querySelectorAll('textarea, input, select')); - - this.nativeInputs.forEach((input) => this.listeners.on(input, 'input', this.mutationDebouncer)); - } - - /** - * Sets observer and enables it - */ - private enableModule(): void { - /** - * wait till Browser render Editor's Blocks - */ - window.setTimeout(() => { - this.setObserver(); - this.updateNativeInputs(); - this.enable(); - }, 1000); - } - - /** - * Disables observer - */ - private disableModule(): void { - this.disable(); + this.config.onChange(this.Editor.API.methods, block); } } diff --git a/src/components/modules/paste.ts b/src/components/modules/paste.ts index 884cbdba..178e3cc1 100644 --- a/src/components/modules/paste.ts +++ b/src/components/modules/paste.ts @@ -2,14 +2,14 @@ import Module from '../__module'; import $ from '../dom'; import * as _ from '../utils'; import { - BlockTool, - BlockToolConstructable, - PasteConfig, + BlockAPI, PasteEvent, PasteEventDetail } from '../../../types'; import Block from '../block'; import { SavedData } from '../../../types/data-formats'; +import { clean, sanitizeBlocks } from '../utils/sanitizer'; +import BlockTool from '../tools/block'; /** * Tag substitute object. @@ -18,9 +18,8 @@ interface TagSubstitute { /** * Name of related Tool * - * @type {string} */ - tool: string; + tool: BlockTool; } /** @@ -29,24 +28,18 @@ interface TagSubstitute { interface PatternSubstitute { /** * Pattern`s key - * - * @type {string} */ key: string; /** * Pattern regexp - * - * @type {RegExp} */ pattern: RegExp; /** * Name of related Tool - * - * @type {string} */ - tool: string; + tool: BlockTool; } /** @@ -166,8 +159,7 @@ export default class Paste extends Module { * @param {boolean} isDragNDrop - true if data transfer comes from drag'n'drop events */ public async processDataTransfer(dataTransfer: DataTransfer, isDragNDrop = false): Promise { - const { Sanitizer } = this.Editor; - + const { Tools } = this.Editor; const types = dataTransfer.types; /** @@ -211,9 +203,8 @@ export default class Paste extends Module { return result; }, {}); - const customConfig = Object.assign({}, toolsTags, Sanitizer.getAllInlineToolsConfig(), { br: {} }); - - const cleanData = Sanitizer.clean(htmlData, customConfig); + const customConfig = Object.assign({}, toolsTags, Tools.getAllInlineToolsSanitizeConfig(), { br: {} }); + const cleanData = clean(htmlData, customConfig); /** If there is no HTML or HTML string is equal to plain one, process it as plain text */ if (!cleanData.trim() || cleanData.trim() === plainData || !$.isHTMLString(cleanData)) { @@ -230,7 +221,7 @@ export default class Paste extends Module { * @param {boolean} isHTML - if passed string is HTML, this parameter should be true */ public async processText(data: string, isHTML = false): Promise { - const { Caret, BlockManager, Tools } = this.Editor; + const { Caret, BlockManager } = this.Editor; const dataToInsert = isHTML ? this.processHTML(data) : this.processPlain(data); if (!dataToInsert.length) { @@ -247,7 +238,7 @@ export default class Paste extends Module { return; } - const isCurrentBlockDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool); + const isCurrentBlockDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault; const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty; dataToInsert.map( @@ -279,23 +270,22 @@ export default class Paste extends Module { private processTools(): void { const tools = this.Editor.Tools.blockTools; - Object.entries(tools).forEach(this.processTool); + Array + .from(tools.values()) + .forEach(this.processTool); } /** * Process paste config for each tool + * + * @param tool - BlockTool object */ - private processTool = ([name, tool]: [string, BlockToolConstructable]): void => { + private processTool = (tool: BlockTool): void => { try { - const toolInstance = new this.Editor.Tools.blockTools[name]({ - api: this.Editor.API.getMethodsForTool(name), - config: {}, - data: {}, - readOnly: false, - }) as BlockTool; + const toolInstance = tool.create({}, {} as BlockAPI, false); if (tool.pasteConfig === false) { - this.exceptionList.push(name); + this.exceptionList.push(tool.name); return; } @@ -304,14 +294,12 @@ export default class Paste extends Module { return; } - const toolPasteConfig = tool.pasteConfig || {}; - - this.getTagsConfig(name, toolPasteConfig); - this.getFilesConfig(name, toolPasteConfig); - this.getPatternsConfig(name, toolPasteConfig); + this.getTagsConfig(tool); + this.getFilesConfig(tool); + this.getPatternsConfig(tool); } catch (e) { _.log( - `Paste handling for «${name}» Tool hasn't been set up because of the error`, + `Paste handling for «${tool.name}» Tool hasn't been set up because of the error`, 'warn', e ); @@ -321,17 +309,16 @@ export default class Paste extends Module { /** * Get tags to substitute by Tool * - * @param {string} name - Tool name - * @param {PasteConfig} toolPasteConfig - Tool onPaste configuration + * @param tool - BlockTool object */ - private getTagsConfig(name: string, toolPasteConfig: PasteConfig): void { - const tags = toolPasteConfig.tags || []; + private getTagsConfig(tool: BlockTool): void { + const tags = tool.pasteConfig.tags || []; tags.forEach((tag) => { if (Object.prototype.hasOwnProperty.call(this.toolsTags, tag)) { _.log( - `Paste handler for «${name}» Tool on «${tag}» tag is skipped ` + - `because it is already used by «${this.toolsTags[tag].tool}» Tool.`, + `Paste handler for «${tool.name}» Tool on «${tag}» tag is skipped ` + + `because it is already used by «${this.toolsTags[tag].tool.name}» Tool.`, 'warn' ); @@ -339,21 +326,20 @@ export default class Paste extends Module { } this.toolsTags[tag.toUpperCase()] = { - tool: name, + tool, }; }); - this.tagsByTool[name] = tags.map((t) => t.toUpperCase()); + this.tagsByTool[tool.name] = tags.map((t) => t.toUpperCase()); } /** * Get files` types and extensions to substitute by Tool * - * @param {string} name - Tool name - * @param {PasteConfig} toolPasteConfig - Tool onPaste configuration + * @param tool - BlockTool object */ - private getFilesConfig(name: string, toolPasteConfig: PasteConfig): void { - const { files = {} } = toolPasteConfig; + private getFilesConfig(tool: BlockTool): void { + const { files = {} } = tool.pasteConfig; let { extensions, mimeTypes } = files; if (!extensions && !mimeTypes) { @@ -361,19 +347,19 @@ export default class Paste extends Module { } if (extensions && !Array.isArray(extensions)) { - _.log(`«extensions» property of the onDrop config for «${name}» Tool should be an array`); + _.log(`«extensions» property of the onDrop config for «${tool.name}» Tool should be an array`); extensions = []; } if (mimeTypes && !Array.isArray(mimeTypes)) { - _.log(`«mimeTypes» property of the onDrop config for «${name}» Tool should be an array`); + _.log(`«mimeTypes» property of the onDrop config for «${tool.name}» Tool should be an array`); mimeTypes = []; } if (mimeTypes) { mimeTypes = mimeTypes.filter((type) => { if (!_.isValidMimeType(type)) { - _.log(`MIME type value «${type}» for the «${name}» Tool is not a valid MIME type`, 'warn'); + _.log(`MIME type value «${type}» for the «${tool.name}» Tool is not a valid MIME type`, 'warn'); return false; } @@ -382,7 +368,7 @@ export default class Paste extends Module { }); } - this.toolsFiles[name] = { + this.toolsFiles[tool.name] = { extensions: extensions || [], mimeTypes: mimeTypes || [], }; @@ -391,19 +377,18 @@ export default class Paste extends Module { /** * Get RegExp patterns to substitute by Tool * - * @param {string} name - Tool name - * @param {PasteConfig} toolPasteConfig - Tool onPaste configuration + * @param tool - BlockTool object */ - private getPatternsConfig(name: string, toolPasteConfig: PasteConfig): void { - if (!toolPasteConfig.patterns || _.isEmpty(toolPasteConfig.patterns)) { + private getPatternsConfig(tool: BlockTool): void { + if (!tool.pasteConfig.patterns || _.isEmpty(tool.pasteConfig.patterns)) { return; } - Object.entries(toolPasteConfig.patterns).forEach(([key, pattern]: [string, RegExp]) => { + Object.entries(tool.pasteConfig.patterns).forEach(([key, pattern]: [string, RegExp]) => { /** Still need to validate pattern as it provided by user */ if (!(pattern instanceof RegExp)) { _.log( - `Pattern ${pattern} for «${name}» Tool is skipped because it should be a Regexp instance.`, + `Pattern ${pattern} for «${tool.name}» Tool is skipped because it should be a Regexp instance.`, 'warn' ); } @@ -411,7 +396,7 @@ export default class Paste extends Module { this.toolsPatterns.push({ key, pattern, - tool: name, + tool, }); }); } @@ -462,9 +447,9 @@ export default class Paste extends Module { * @param {FileList} items - pasted or dropped items */ private async processFiles(items: FileList): Promise { - const { BlockManager, Tools } = this.Editor; + const { BlockManager } = this.Editor; - let dataToInsert: Array<{type: string; event: PasteEvent}>; + let dataToInsert: {type: string; event: PasteEvent}[]; dataToInsert = await Promise.all( Array @@ -473,7 +458,7 @@ export default class Paste extends Module { ); dataToInsert = dataToInsert.filter((data) => !!data); - const isCurrentBlockDefault = Tools.isDefault(BlockManager.currentBlock.tool); + const isCurrentBlockDefault = BlockManager.currentBlock.tool.isDefault; const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty; dataToInsert.forEach( @@ -529,8 +514,7 @@ export default class Paste extends Module { * @returns {PasteData[]} */ private processHTML(innerHTML: string): PasteData[] { - const { Tools, Sanitizer } = this.Editor; - const initialTool = this.config.defaultBlock; + const { Tools } = this.Editor; const wrapper = $.make('DIV'); wrapper.innerHTML = innerHTML; @@ -539,7 +523,7 @@ export default class Paste extends Module { return nodes .map((node) => { - let content, tool = initialTool, isBlock = false; + let content, tool = Tools.defaultTool, isBlock = false; switch (node.nodeType) { /** If node is a document fragment, use temp wrapper to get innerHTML */ @@ -559,16 +543,16 @@ export default class Paste extends Module { break; } - const { tags } = Tools.blockTools[tool].pasteConfig as PasteConfig; + const { tags } = tool.pasteConfig; const toolTags = tags.reduce((result, tag) => { result[tag.toLowerCase()] = {}; return result; }, {}); - const customConfig = Object.assign({}, toolTags, Sanitizer.getInlineToolsConfig(tool)); + const customConfig = Object.assign({}, toolTags, tool.baseSanitizeConfig); - content.innerHTML = Sanitizer.clean(content.innerHTML, customConfig); + content.innerHTML = clean(content.innerHTML, customConfig); const event = this.composePasteEvent('tag', { data: content, @@ -577,7 +561,7 @@ export default class Paste extends Module { return { content, isBlock, - tool, + tool: tool.name, event, }; }) @@ -627,7 +611,7 @@ export default class Paste extends Module { * @param {PasteData} dataToInsert - data of Block to inseret */ private async processSingleBlock(dataToInsert: PasteData): Promise { - const { Caret, BlockManager, Tools } = this.Editor; + const { Caret, BlockManager } = this.Editor; const { currentBlock } = BlockManager; /** @@ -638,7 +622,7 @@ export default class Paste extends Module { dataToInsert.tool !== currentBlock.name || !$.containsOnlyInlineElements(dataToInsert.content.innerHTML) ) { - this.insertBlock(dataToInsert, currentBlock && Tools.isDefault(currentBlock.tool) && currentBlock.isEmpty); + this.insertBlock(dataToInsert, currentBlock?.tool.isDefault && currentBlock.isEmpty); return; } @@ -655,17 +639,17 @@ export default class Paste extends Module { * @param {PasteData} dataToInsert - data of Block to insert */ private async processInlinePaste(dataToInsert: PasteData): Promise { - const { BlockManager, Caret, Sanitizer, Tools } = this.Editor; + const { BlockManager, Caret } = this.Editor; const { content } = dataToInsert; - const currentBlockIsDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool); + const currentBlockIsDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault; if (currentBlockIsDefault && content.textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) { const blockData = await this.processPattern(content.textContent); if (blockData) { const needToReplaceCurrentBlock = BlockManager.currentBlock && - Tools.isDefault(BlockManager.currentBlock.tool) && + BlockManager.currentBlock.tool.isDefault && BlockManager.currentBlock.isEmpty; const insertedBlock = BlockManager.paste(blockData.tool, blockData.event, needToReplaceCurrentBlock); @@ -678,12 +662,12 @@ export default class Paste extends Module { /** If there is no pattern substitute - insert string as it is */ if (BlockManager.currentBlock && BlockManager.currentBlock.currentInput) { - const currentToolSanitizeConfig = Sanitizer.getInlineToolsConfig(BlockManager.currentBlock.name); + const currentToolSanitizeConfig = BlockManager.currentBlock.tool.sanitizeConfig; document.execCommand( 'insertHTML', false, - Sanitizer.clean(content.innerHTML, currentToolSanitizeConfig) + clean(content.innerHTML, currentToolSanitizeConfig) ); } else { this.insertBlock(dataToInsert); @@ -719,7 +703,7 @@ export default class Paste extends Module { return { event, - tool: pattern.tool, + tool: pattern.tool.name, }; } @@ -755,15 +739,17 @@ export default class Paste extends Module { * * @returns {void} */ - private insertEditorJSData(blocks: Array>): void { - const { BlockManager, Caret, Sanitizer, Tools } = this.Editor; - const sanitizedBlocks = Sanitizer.sanitizeBlocks(blocks); + private insertEditorJSData(blocks: Pick[]): void { + const { BlockManager, Caret, Tools } = this.Editor; + const sanitizedBlocks = sanitizeBlocks(blocks, (name) => + Tools.blockTools.get(name).sanitizeConfig + ); sanitizedBlocks.forEach(({ tool, data }, i) => { let needToReplaceCurrentBlock = false; if (i === 0) { - const isCurrentBlockDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool); + const isCurrentBlockDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault; needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty; } @@ -792,8 +778,8 @@ export default class Paste extends Module { const element = node as HTMLElement; - const { tool = '' } = this.toolsTags[element.tagName] || {}; - const toolTags = this.tagsByTool[tool] || []; + const { tool } = this.toolsTags[element.tagName] || {}; + const toolTags = this.tagsByTool[tool?.name] || []; const isSubstitutable = tags.includes(element.tagName); const isBlockElement = $.blockElements.includes(element.tagName.toLowerCase()); diff --git a/src/components/modules/readonly.ts b/src/components/modules/readonly.ts index 544d1d7d..77a5fc8c 100644 --- a/src/components/modules/readonly.ts +++ b/src/components/modules/readonly.ts @@ -40,11 +40,13 @@ export default class ReadOnly extends Module { const { blockTools } = Tools; const toolsDontSupportReadOnly: string[] = []; - Object.entries(blockTools).forEach(([name, tool]) => { - if (!Tools.isReadOnlySupported(tool)) { - toolsDontSupportReadOnly.push(name); - } - }); + Array + .from(blockTools.entries()) + .forEach(([name, tool]) => { + if (!tool.isReadOnlySupported) { + toolsDontSupportReadOnly.push(name); + } + }); this.toolsDontSupportReadOnly = toolsDontSupportReadOnly; diff --git a/src/components/modules/rectangleSelection.ts b/src/components/modules/rectangleSelection.ts index 8b2ea151..70657897 100644 --- a/src/components/modules/rectangleSelection.ts +++ b/src/components/modules/rectangleSelection.ts @@ -211,7 +211,16 @@ export default class RectangleSelection extends Module { if (mouseEvent.button !== this.MAIN_MOUSE_BUTTON) { return; } - this.startSelection(mouseEvent.pageX, mouseEvent.pageY); + + /** + * Do not enable the Rectangle Selection when mouse dragging started some editable input + * Used to prevent Rectangle Selection on Block Tune wrappers' inputs that also can be inside the Block + */ + const startedFromContentEditable = (mouseEvent.target as Element).closest($.allInputsSelector) !== null; + + if (!startedFromContentEditable) { + this.startSelection(mouseEvent.pageX, mouseEvent.pageY); + } } /** diff --git a/src/components/modules/renderer.ts b/src/components/modules/renderer.ts index d94efb8b..9aad79ff 100644 --- a/src/components/modules/renderer.ts +++ b/src/components/modules/renderer.ts @@ -1,6 +1,7 @@ import Module from '../__module'; import * as _ from '../utils'; -import { BlockToolConstructable, OutputBlockData } from '../../../types'; +import { OutputBlockData } from '../../../types'; +import BlockTool from '../tools/block'; /** * Editor.js Renderer Module @@ -22,12 +23,14 @@ export default class Renderer extends Module { * * blocks: [ * { + * id : 'oDe-EVrGWA', * type : 'paragraph', * data : { * text : 'Hello from Codex!' * } * }, * { + * id : 'Ld5BJjJCHs', * type : 'paragraph', * data : { * text : 'Leave feedback if you like it!' @@ -45,8 +48,15 @@ export default class Renderer extends Module { public async render(blocks: OutputBlockData[]): Promise { const chainData = blocks.map((block) => ({ function: (): Promise => this.insertBlock(block) })); + /** + * Disable onChange callback on render to not to spam those events + */ + this.Editor.ModificationsObserver.disable(); + const sequence = await _.sequence(chainData as _.ChainData[]); + this.Editor.ModificationsObserver.enable(); + this.Editor.UI.checkEmptiness(); return sequence; @@ -63,14 +73,15 @@ export default class Renderer extends Module { */ public async insertBlock(item: OutputBlockData): Promise { const { Tools, BlockManager } = this.Editor; - const tool = item.type; - const data = item.data; + const { type: tool, data, tunes, id } = item; - if (tool in Tools.available) { + if (Tools.available.has(tool)) { try { BlockManager.insert({ + id, tool, data, + tunes, }); } catch (error) { _.log(`Block «${tool}» skipped because of plugins error`, 'warn', data); @@ -80,20 +91,21 @@ export default class Renderer extends Module { /** If Tool is unavailable, create stub Block for it */ const stubData = { savedData: { + id, type: tool, data, }, title: tool, }; - if (tool in Tools.unavailable) { - const toolToolboxSettings = (Tools.unavailable[tool] as BlockToolConstructable).toolbox; - const userToolboxSettings = Tools.getToolSettings(tool).toolbox; + if (Tools.unavailable.has(tool)) { + const toolboxSettings = (Tools.unavailable.get(tool) as BlockTool).toolbox; - stubData.title = toolToolboxSettings.title || (userToolboxSettings && userToolboxSettings.title) || stubData.title; + stubData.title = toolboxSettings?.title || stubData.title; } const stub = BlockManager.insert({ + id, tool: Tools.stubTool, data: stubData, }); diff --git a/src/components/modules/sanitizer.ts b/src/components/modules/sanitizer.ts deleted file mode 100644 index f3bc086f..00000000 --- a/src/components/modules/sanitizer.ts +++ /dev/null @@ -1,333 +0,0 @@ -/** - * CodeX Sanitizer - * - * @module Sanitizer - * Clears HTML from taint tags - * - * @version 2.0.0 - * - * @example - * Module can be used within two ways: - * 1) When you have an instance - * - this.Editor.Sanitizer.clean(yourTaintString); - * 2) As static method - * - EditorJS.Sanitizer.clean(yourTaintString, yourCustomConfiguration); - * - * {@link SanitizerConfig} - */ - -import Module from '../__module'; -import * as _ from '../utils'; - -/** - * @typedef {object} SanitizerConfig - * @property {object} tags - define tags restrictions - * - * @example - * - * tags : { - * p: true, - * a: { - * href: true, - * rel: "nofollow", - * target: "_blank" - * } - * } - */ - -import HTMLJanitor from 'html-janitor'; -import { BlockToolData, InlineToolConstructable, SanitizerConfig } from '../../../types'; -import { SavedData } from '../../../types/data-formats'; - -/** - * - */ -export default class Sanitizer extends Module { - /** - * Memoize tools config - */ - private configCache: {[toolName: string]: SanitizerConfig} = {}; - - /** - * Cached inline tools config - */ - private inlineToolsConfigCache: SanitizerConfig | null = null; - - /** - * Sanitize Blocks - * - * Enumerate blocks and clean data - * - * @param {Array<{tool, data: BlockToolData}>} blocksData - blocks' data to sanitize - */ - public sanitizeBlocks( - blocksData: Array> - ): Array> { - return blocksData.map((block) => { - const toolConfig = this.composeToolConfig(block.tool); - - if (_.isEmpty(toolConfig)) { - return block; - } - - block.data = this.deepSanitize(block.data, toolConfig) as BlockToolData; - - return block; - }); - } - - /** - * Method recursively reduces Block's data and cleans with passed rules - * - * @param {BlockToolData|object|*} dataToSanitize - taint string or object/array that contains taint string - * @param {SanitizerConfig} rules - object with sanitizer rules - */ - public deepSanitize(dataToSanitize: object | string, rules: SanitizerConfig): object | string { - /** - * BlockData It may contain 3 types: - * - Array - * - Object - * - Primitive - */ - if (Array.isArray(dataToSanitize)) { - /** - * Array: call sanitize for each item - */ - return this.cleanArray(dataToSanitize, rules); - } else if (_.isObject(dataToSanitize)) { - /** - * Objects: just clean object deeper. - */ - return this.cleanObject(dataToSanitize, rules); - } else { - /** - * Primitives (number|string|boolean): clean this item - * - * Clean only strings - */ - if (_.isString(dataToSanitize)) { - return this.cleanOneItem(dataToSanitize, rules); - } - - return dataToSanitize; - } - } - - /** - * Cleans string from unwanted tags - * Method allows to use default config - * - * @param {string} taintString - taint string - * @param {SanitizerConfig} customConfig - allowed tags - * - * @returns {string} clean HTML - */ - public clean(taintString: string, customConfig: SanitizerConfig = {} as SanitizerConfig): string { - const sanitizerConfig = { - tags: customConfig, - }; - - /** - * API client can use custom config to manage sanitize process - */ - const sanitizerInstance = this.createHTMLJanitorInstance(sanitizerConfig); - - return sanitizerInstance.clean(taintString); - } - - /** - * Merge with inline tool config - * - * @param {string} toolName - tool name - * - * @returns {SanitizerConfig} - */ - public composeToolConfig(toolName: string): SanitizerConfig { - /** - * If cache is empty, then compose tool config and put it to the cache object - */ - if (this.configCache[toolName]) { - return this.configCache[toolName]; - } - - const sanitizeGetter = this.Editor.Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG; - const toolClass = this.Editor.Tools.available[toolName]; - const baseConfig = this.getInlineToolsConfig(toolName); - - /** - * If Tools doesn't provide sanitizer config or it is empty - */ - if (!toolClass.sanitize || (toolClass[sanitizeGetter] && _.isEmpty(toolClass[sanitizeGetter]))) { - return baseConfig; - } - - const toolRules = toolClass.sanitize; - - const toolConfig = {} as SanitizerConfig; - - for (const fieldName in toolRules) { - if (Object.prototype.hasOwnProperty.call(toolRules, fieldName)) { - const rule = toolRules[fieldName]; - - if (_.isObject(rule)) { - toolConfig[fieldName] = Object.assign({}, baseConfig, rule); - } else { - toolConfig[fieldName] = rule; - } - } - } - this.configCache[toolName] = toolConfig; - - return toolConfig; - } - - /** - * Returns Sanitizer config - * When Tool's "inlineToolbar" value is True, get all sanitizer rules from all tools, - * otherwise get only enabled - * - * @param {string} name - Inline Tool name - */ - public getInlineToolsConfig(name: string): SanitizerConfig { - const { Tools } = this.Editor; - const toolsConfig = Tools.getToolSettings(name); - const enableInlineTools = toolsConfig.inlineToolbar || []; - - let config = {} as SanitizerConfig; - - if (_.isBoolean(enableInlineTools) && enableInlineTools) { - /** - * getting all tools sanitizer rule - */ - config = this.getAllInlineToolsConfig(); - } else { - /** - * getting only enabled - */ - (enableInlineTools as string[]).map((inlineToolName) => { - config = Object.assign( - config, - Tools.inline[inlineToolName][Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG] - ) as SanitizerConfig; - }); - } - - /** - * Allow linebreaks - */ - config['br'] = true; - config['wbr'] = true; - - return config; - } - - /** - * Return general config for all inline tools - */ - public getAllInlineToolsConfig(): SanitizerConfig { - const { Tools } = this.Editor; - - if (this.inlineToolsConfigCache) { - return this.inlineToolsConfigCache; - } - - const config: SanitizerConfig = {} as SanitizerConfig; - - Object.entries(Tools.inline) - .forEach(([, inlineTool]: [string, InlineToolConstructable]) => { - Object.assign(config, inlineTool[Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG]); - }); - - this.inlineToolsConfigCache = config; - - return this.inlineToolsConfigCache; - } - - /** - * Clean array - * - * @param {Array} array - [1, 2, {}, []] - * @param {SanitizerConfig} ruleForItem - sanitizer config for array - */ - private cleanArray(array: Array, ruleForItem: SanitizerConfig): Array { - return array.map((arrayItem) => this.deepSanitize(arrayItem, ruleForItem)); - } - - /** - * Clean object - * - * @param {object} object - {level: 0, text: 'adada', items: [1,2,3]}} - * @param {object} rules - { b: true } or true|false - * @returns {object} - */ - private cleanObject(object: object, rules: SanitizerConfig|{[field: string]: SanitizerConfig}): object { - const cleanData = {}; - - for (const fieldName in object) { - if (!Object.prototype.hasOwnProperty.call(object, fieldName)) { - continue; - } - - const currentIterationItem = object[fieldName]; - - /** - * Get object from config by field name - * - if it is a HTML Janitor rule, call with this rule - * - otherwise, call with parent's config - */ - const ruleForItem = this.isRule(rules[fieldName] as SanitizerConfig) ? rules[fieldName] : rules; - - cleanData[fieldName] = this.deepSanitize(currentIterationItem, ruleForItem as SanitizerConfig); - } - - return cleanData; - } - - /** - * Clean primitive value - * - * @param {string} taintString - string to clean - * @param {SanitizerConfig|boolean} rule - sanitizer rule - * - * @returns {string} - */ - private cleanOneItem(taintString: string, rule: SanitizerConfig|boolean): string { - if (_.isObject(rule)) { - return this.clean(taintString, rule); - } else if (rule === false) { - return this.clean(taintString, {} as SanitizerConfig); - } else { - return taintString; - } - } - - /** - * Check if passed item is a HTML Janitor rule: - * { a : true }, {}, false, true, function(){} — correct rules - * undefined, null, 0, 1, 2 — not a rules - * - * @param {SanitizerConfig} config - config to check - */ - private isRule(config: SanitizerConfig): boolean { - return _.isObject(config) || _.isBoolean(config) || _.isFunction(config); - } - - /** - * If developer uses editor's API, then he can customize sanitize restrictions. - * Or, sanitizing config can be defined globally in editors initialization. That config will be used everywhere - * At least, if there is no config overrides, that API uses Default configuration - * - * @see {@link https://www.npmjs.com/package/html-janitor} - * @license Apache-2.0 - * @see {@link https://github.com/guardian/html-janitor/blob/master/LICENSE} - * - * @param {SanitizerConfig} config - sanitizer extension - */ - private createHTMLJanitorInstance(config: {tags: SanitizerConfig}): HTMLJanitor|null { - if (config) { - return new HTMLJanitor(config); - } - - return null; - } -} diff --git a/src/components/modules/saver.ts b/src/components/modules/saver.ts index 3c0879b0..59725f65 100644 --- a/src/components/modules/saver.ts +++ b/src/components/modules/saver.ts @@ -7,9 +7,10 @@ */ import Module from '../__module'; import { OutputData } from '../../../types'; -import { ValidatedData } from '../../../types/data-formats'; +import { SavedData, ValidatedData } from '../../../types/data-formats'; import Block from '../block'; import * as _ from '../utils'; +import { sanitizeBlocks } from '../utils/sanitizer'; declare const VERSION: string; @@ -27,24 +28,28 @@ export default class Saver extends Module { * @returns {OutputData} */ public async save(): Promise { - const { BlockManager, Sanitizer, ModificationsObserver } = this.Editor; + const { BlockManager, Tools, ModificationsObserver } = this.Editor; const blocks = BlockManager.blocks, chainData = []; - /** - * Disable modifications observe while saving - */ - ModificationsObserver.disable(); - try { + /** + * Disable onChange callback on save to not to spam those events + */ + ModificationsObserver.disable(); + blocks.forEach((block: Block) => { chainData.push(this.getSavedData(block)); }); - const extractedData = await Promise.all(chainData); - const sanitizedData = await Sanitizer.sanitizeBlocks(extractedData); + const extractedData = await Promise.all(chainData) as Array>; + const sanitizedData = await sanitizeBlocks(extractedData, (name) => { + return Tools.blockTools.get(name).sanitizeConfig; + }); return this.makeOutput(sanitizedData); + } catch (e) { + _.logLabeled(`Saving failed due to the Error %o`, 'error', e); } finally { ModificationsObserver.enable(); } @@ -78,7 +83,7 @@ export default class Saver extends Module { _.log('[Editor.js saving]:', 'groupCollapsed'); - allExtractedData.forEach(({ tool, data, time, isValid }) => { + allExtractedData.forEach(({ id, tool, data, tunes, time, isValid }) => { totalTime += time; /** @@ -104,10 +109,16 @@ export default class Saver extends Module { return; } - blocks.push({ + const output = { + id, type: tool, data, - }); + ...!_.isEmpty(tunes) && { + tunes, + }, + }; + + blocks.push(output); }); _.log('Total', 'log', totalTime); diff --git a/src/components/modules/toolbar/blockSettings.ts b/src/components/modules/toolbar/blockSettings.ts index a3a61c54..b6ad7922 100644 --- a/src/components/modules/toolbar/blockSettings.ts +++ b/src/components/modules/toolbar/blockSettings.ts @@ -144,7 +144,7 @@ export default class BlockSettings extends Module { /** * Add default settings that presents for all Blocks */ - this.addDefaultSettings(); + this.addTunes(); /** Tell to subscribers that block settings is opened */ this.eventsDispatcher.emit(this.events.opened); @@ -229,16 +229,21 @@ export default class BlockSettings extends Module { * Add Tool's settings */ private addToolSettings(): void { - if (_.isFunction(this.Editor.BlockManager.currentBlock.tool.renderSettings)) { - $.append(this.nodes.toolSettings, this.Editor.BlockManager.currentBlock.tool.renderSettings()); + const settingsElement = this.Editor.BlockManager.currentBlock.renderSettings(); + + if (settingsElement) { + $.append(this.nodes.toolSettings, settingsElement); } } /** - * Add default settings + * Add tunes: provided by user and default ones */ - private addDefaultSettings(): void { - $.append(this.nodes.defaultSettings, this.Editor.BlockManager.currentBlock.renderTunes()); + private addTunes(): void { + const [toolTunes, defaultTunes] = this.Editor.BlockManager.currentBlock.renderTunes(); + + $.append(this.nodes.toolSettings, toolTunes); + $.append(this.nodes.defaultSettings, defaultTunes); } /** diff --git a/src/components/modules/toolbar/conversion.ts b/src/components/modules/toolbar/conversion.ts index a258bf2d..c40aed18 100644 --- a/src/components/modules/toolbar/conversion.ts +++ b/src/components/modules/toolbar/conversion.ts @@ -1,11 +1,11 @@ import Module from '../../__module'; import $ from '../../dom'; -import { BlockToolConstructable } from '../../../../types'; import * as _ from '../../utils'; import { SavedData } from '../../../../types/data-formats'; import Flipper from '../../flipper'; import I18n from '../../i18n'; import { I18nInternalNS } from '../../i18n/namespace-internal'; +import { clean } from '../../utils/sanitizer'; /** * HTML Elements used for ConversionToolbar @@ -182,10 +182,9 @@ export default class ConversionToolbar extends Module { * * @type {BlockToolConstructable} */ - const currentBlockClass = this.Editor.BlockManager.currentBlock.class; + const currentBlockTool = this.Editor.BlockManager.currentBlock.tool; const currentBlockName = this.Editor.BlockManager.currentBlock.name; const savedBlock = await this.Editor.BlockManager.currentBlock.save() as SavedData; - const { INTERNAL_SETTINGS } = this.Editor.Tools; const blockData = savedBlock.data; /** @@ -201,7 +200,7 @@ export default class ConversionToolbar extends Module { * * @type {BlockToolConstructable} */ - const replacingTool = this.Editor.Tools.toolsClasses[replacingToolName] as BlockToolConstructable; + const replacingTool = this.Editor.Tools.blockTools.get(replacingToolName); /** * Export property can be: @@ -211,7 +210,7 @@ export default class ConversionToolbar extends Module { * In both cases returning value must be a string */ let exportData = ''; - const exportProp = currentBlockClass[INTERNAL_SETTINGS.CONVERSION_CONFIG].export; + const exportProp = currentBlockTool.conversionConfig.export; if (_.isFunction(exportProp)) { exportData = exportProp(blockData); @@ -227,9 +226,9 @@ export default class ConversionToolbar extends Module { /** * Clean exported data with replacing sanitizer config */ - const cleaned: string = this.Editor.Sanitizer.clean( + const cleaned: string = clean( exportData, - replacingTool.sanitize + replacingTool.sanitizeConfig ); /** @@ -238,7 +237,7 @@ export default class ConversionToolbar extends Module { * string — the name of data field to import */ let newBlockData = {}; - const importProp = replacingTool[INTERNAL_SETTINGS.CONVERSION_CONFIG].import; + const importProp = replacingTool.conversionConfig.import; if (_.isFunction(importProp)) { newBlockData = importProp(cleaned); @@ -272,37 +271,28 @@ export default class ConversionToolbar extends Module { private addTools(): void { const tools = this.Editor.Tools.blockTools; - for (const toolName in tools) { - if (!Object.prototype.hasOwnProperty.call(tools, toolName)) { - continue; - } + Array + .from(tools.entries()) + .forEach(([name, tool]) => { + const toolboxSettings = tool.toolbox; + const conversionConfig = tool.conversionConfig; - const internalSettings = this.Editor.Tools.INTERNAL_SETTINGS; - const toolClass = tools[toolName] as BlockToolConstructable; - const toolToolboxSettings = toolClass[internalSettings.TOOLBOX]; - const conversionConfig = toolClass[internalSettings.CONVERSION_CONFIG]; + /** + * Skip tools that don't pass 'toolbox' property + */ + if (_.isEmpty(toolboxSettings) || !toolboxSettings.icon) { + return; + } - const userSettings = this.Editor.Tools.USER_SETTINGS; - const userToolboxSettings = this.Editor.Tools.getToolSettings(toolName)[userSettings.TOOLBOX]; + /** + * Skip tools without «import» rule specified + */ + if (!conversionConfig || !conversionConfig.import) { + return; + } - const toolboxSettings = userToolboxSettings ?? toolToolboxSettings; - - /** - * Skip tools that don't pass 'toolbox' property - */ - if (_.isEmpty(toolboxSettings) || !toolboxSettings.icon) { - continue; - } - - /** - * Skip tools without «import» rule specified - */ - if (!conversionConfig || !conversionConfig.import) { - continue; - } - - this.addTool(toolName, toolboxSettings.icon, toolboxSettings.title); - } + this.addTool(name, toolboxSettings.icon, toolboxSettings.title); + }); } /** diff --git a/src/components/modules/toolbar/index.ts b/src/components/modules/toolbar/index.ts index ea8e6f29..7f0e319d 100644 --- a/src/components/modules/toolbar/index.ts +++ b/src/components/modules/toolbar/index.ts @@ -3,6 +3,10 @@ import $ from '../../dom'; import * as _ from '../../utils'; import I18n from '../../i18n'; import { I18nInternalNS } from '../../i18n/namespace-internal'; +import Tooltip from '../../utils/tooltip'; +import { ModuleConfig } from '../../../types-internal/module-config'; +import { EditorConfig } from '../../../../types'; +import SelectionUtils from '../../selection'; /** * HTML Elements used for Toolbar UI @@ -72,6 +76,24 @@ interface ToolbarNodes { * @property {Element} nodes.defaultSettings - Default Settings section of Settings Panel */ export default class Toolbar extends Module { + /** + * Tooltip utility Instance + */ + private tooltip: Tooltip; + /** + * @class + * @param {object} moduleConfiguration - Module Configuration + * @param {EditorConfig} moduleConfiguration.config - Editor's config + * @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher + */ + constructor({ config, eventsDispatcher }: ModuleConfig) { + super({ + config, + eventsDispatcher, + }); + this.tooltip = new Tooltip(); + } + /** * CSS styles * @@ -277,7 +299,7 @@ export default class Toolbar extends Module { textContent: '⇥ Tab', })); - this.Editor.Tooltip.onHover(this.nodes.plusButton, tooltipContent); + this.tooltip.onHover(this.nodes.plusButton, tooltipContent); /** * Fill Actions Zone: @@ -293,7 +315,7 @@ export default class Toolbar extends Module { $.append(this.nodes.blockActionsButtons, this.nodes.settingsToggler); $.append(this.nodes.actions, this.nodes.blockActionsButtons); - this.Editor.Tooltip.onHover( + this.tooltip.onHover( this.nodes.settingsToggler, I18n.ui(I18nInternalNS.ui.blockTunes.toggler, 'Click to tune'), { @@ -326,10 +348,19 @@ export default class Toolbar extends Module { private enableModuleBindings(): void { /** * Settings toggler + * + * mousedown is used because on click selection is lost in Safari and FF */ - this.readOnlyMutableListeners.on(this.nodes.settingsToggler, 'click', () => { + this.readOnlyMutableListeners.on(this.nodes.settingsToggler, 'mousedown', (e) => { + /** + * Stop propagation to prevent block selection clearance + * + * @see UI.documentClicked + */ + e.stopPropagation(); + this.settingsTogglerClicked(); - }); + }, true); } /** @@ -384,5 +415,6 @@ export default class Toolbar extends Module { */ private destroy(): void { this.removeAllNodes(); + this.tooltip.destroy(); } } diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index 79630309..41ca2ddf 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -2,12 +2,15 @@ import Module from '../../__module'; import $ from '../../dom'; import SelectionUtils from '../../selection'; import * as _ from '../../utils'; -import { InlineTool, InlineToolConstructable, ToolConstructable, ToolSettings } from '../../../../types'; +import { InlineTool as IInlineTool, EditorConfig } from '../../../../types'; import Flipper from '../../flipper'; import I18n from '../../i18n'; import { I18nInternalNS } from '../../i18n/namespace-internal'; import Shortcuts from '../../utils/shortcuts'; -import { EditorModules } from '../../../types-internal/editor-modules'; +import Tooltip from '../../utils/tooltip'; +import { ModuleConfig } from '../../../types-internal/module-config'; +import InlineTool from '../../tools/inline'; +import { CommonInternalSettings } from '../../tools/base'; /** * Inline Toolbar elements @@ -66,9 +69,11 @@ export default class InlineToolbar extends Module { private readonly toolbarVerticalMargin: number = 5; /** + * TODO: Get rid of this + * * Currently visible tools instances */ - private toolsInstances: Map; + private toolsInstances: Map; /** * Buttons List @@ -90,35 +95,21 @@ export default class InlineToolbar extends Module { private flipper: Flipper = null; /** - * Internal inline tools: Link, Bold, Italic + * Tooltip utility Instance */ - private internalTools: {[name: string]: InlineToolConstructable} = {}; - + private tooltip: Tooltip; /** - * Editor modules setter - * - * @param {EditorModules} Editor - Editor's Modules + * @class + * @param {object} moduleConfiguration - Module Configuration + * @param {EditorConfig} moduleConfiguration.config - Editor's config + * @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher */ - public set state(Editor: EditorModules) { - this.Editor = Editor; - - const { Tools } = Editor; - - /** - * Set internal inline tools - */ - Object - .entries(Tools.internalTools) - .filter(([, toolClass]: [string, ToolConstructable | ToolSettings]) => { - if (_.isFunction(toolClass)) { - return toolClass[Tools.INTERNAL_SETTINGS.IS_INLINE]; - } - - return (toolClass as ToolSettings).class[Tools.INTERNAL_SETTINGS.IS_INLINE]; - }) - .map(([name, toolClass]: [string, InlineToolConstructable | ToolSettings]) => { - this.internalTools[name] = _.isFunction(toolClass) ? toolClass : (toolClass as ToolSettings).class; - }); + constructor({ config, eventsDispatcher }: ModuleConfig) { + super({ + config, + eventsDispatcher, + }); + this.tooltip = new Tooltip(); } /** @@ -143,10 +134,11 @@ export default class InlineToolbar extends Module { /** * Shows Inline Toolbar if something is selected * - * @param {boolean} [needToClose] - pass true to close toolbar if it is not allowed. + * @param [needToClose] - pass true to close toolbar if it is not allowed. * Avoid to use it just for closing IT, better call .close() clearly. + * @param [needToShowConversionToolbar] - pass false to not to show Conversion Toolbar */ - public tryToShow(needToClose = false): void { + public tryToShow(needToClose = false, needToShowConversionToolbar = true): void { if (!this.allowedToShow()) { if (needToClose) { this.close(); @@ -156,7 +148,7 @@ export default class InlineToolbar extends Module { } this.move(); - this.open(); + this.open(needToShowConversionToolbar); this.Editor.Toolbar.close(); } @@ -242,8 +234,10 @@ export default class InlineToolbar extends Module { /** * Shows Inline Toolbar + * + * @param [needToShowConversionToolbar] - pass false to not to show Conversion Toolbar */ - public open(): void { + public open(needToShowConversionToolbar = true): void { if (this.opened) { return; } @@ -260,7 +254,7 @@ export default class InlineToolbar extends Module { this.buttonsList = this.nodes.buttons.querySelectorAll(`.${this.CSS.inlineToolButton}`); this.opened = true; - if (this.Editor.ConversionToolbar.hasTools()) { + if (needToShowConversionToolbar && this.Editor.ConversionToolbar.hasTools()) { /** * Change Conversion Dropdown content for current tool */ @@ -305,80 +299,7 @@ export default class InlineToolbar extends Module { } this.removeAllNodes(); - } - - /** - * Returns inline toolbar settings for a particular tool - * - * @param {string} toolName - user specified name of tool - * @returns {string[] | boolean} array of ordered tool names or false - */ - private getInlineToolbarSettings(toolName): string[] | boolean { - const toolSettings = this.Editor.Tools.getToolSettings(toolName); - - /** - * InlineToolbar property of a particular tool - */ - const settingsForTool = toolSettings[this.Editor.Tools.USER_SETTINGS.ENABLED_INLINE_TOOLS]; - - /** - * Whether to enable IT for a particular tool is the decision of the editor user. - * He can enable it by the inlineToolbar settings for this tool. To enable, he should pass true or strings[] - */ - const enabledForTool = settingsForTool === true || Array.isArray(settingsForTool); - - /** - * Disabled by user - */ - if (!enabledForTool) { - return false; - } - - /** - * 1st priority. - * - * If user pass the list of inline tools for the particular tool, return it. - */ - if (Array.isArray(settingsForTool)) { - return settingsForTool; - } - - /** - * 2nd priority. - * - * If user pass just 'true' for tool, get common inlineToolbar settings - * - if common settings is an array, use it - * - if common settings is 'true' or not specified, get default order - */ - - /** - * Common inlineToolbar settings got from the root of EditorConfig - */ - const commonInlineToolbarSettings = this.config.inlineToolbar; - - /** - * If common settings is an array, use it - */ - if (Array.isArray(commonInlineToolbarSettings)) { - return commonInlineToolbarSettings; - } - - /** - * If common settings is 'true' or not specified (will be set as true at core.ts), get the default order - */ - if (commonInlineToolbarSettings === true) { - const defaultToolsOrder: string[] = Object.entries(this.Editor.Tools.available) - .filter(([name, tool]) => { - return tool[this.Editor.Tools.INTERNAL_SETTINGS.IS_INLINE]; - }) - .map(([name, tool]) => { - return name; - }); - - return defaultToolsOrder; - } - - return false; + this.tooltip.destroy(); } /** @@ -489,12 +410,7 @@ export default class InlineToolbar extends Module { return false; } - /** - * getInlineToolbarSettings could return an string[] (order of tools) or false (Inline Toolbar disabled). - */ - const inlineToolbarSettings = this.getInlineToolbarSettings(currentBlock.name); - - return inlineToolbarSettings !== false; + return currentBlock.tool.inlineTools.size !== 0; } /** @@ -538,7 +454,7 @@ export default class InlineToolbar extends Module { }); }); - this.Editor.Tooltip.onHover(this.nodes.conversionToggler, I18n.ui(I18nInternalNS.ui.inlineToolbar.converter, 'Convert to'), { + this.tooltip.onHover(this.nodes.conversionToggler, I18n.ui(I18nInternalNS.ui.inlineToolbar.converter, 'Convert to'), { placement: 'top', hidingDelay: 100, }); @@ -548,13 +464,14 @@ export default class InlineToolbar extends Module { * Changes Conversion Dropdown content for current block's Tool */ private setConversionTogglerContent(): void { - const { BlockManager, Tools } = this.Editor; - const toolName = BlockManager.currentBlock.name; + const { BlockManager } = this.Editor; + const { currentBlock } = BlockManager; + const toolName = currentBlock.name; /** * If tool does not provide 'export' rule, hide conversion dropdown */ - const conversionConfig = Tools.available[toolName][Tools.INTERNAL_SETTINGS.CONVERSION_CONFIG] || {}; + const conversionConfig = currentBlock.tool.conversionConfig; const exportRuleDefined = conversionConfig && conversionConfig.export; this.nodes.conversionToggler.hidden = !exportRuleDefined; @@ -563,14 +480,10 @@ export default class InlineToolbar extends Module { /** * Get icon or title for dropdown */ - const toolSettings = Tools.getToolSettings(toolName); - const toolboxSettings = Tools.available[toolName][Tools.INTERNAL_SETTINGS.TOOLBOX] || {}; - const userToolboxSettings = toolSettings.toolbox || {}; + const toolboxSettings = currentBlock.tool.toolbox || {}; this.nodes.conversionTogglerContent.innerHTML = - userToolboxSettings.icon || toolboxSettings.icon || - userToolboxSettings.title || toolboxSettings.title || _.capitalize(toolName); } @@ -603,21 +516,8 @@ export default class InlineToolbar extends Module { this.nodes.actions.innerHTML = ''; this.toolsInstances = new Map(); - /** - * Filter buttons if Block Tool pass config like inlineToolbar=['link'] - * Else filter them according to the default inlineToolbar property. - * - * For this moment, inlineToolbarOrder could not be 'false' - * because this method will be called only if the Inline Toolbar is enabled - */ - const inlineToolbarOrder = this.getInlineToolbarSettings(currentBlock.name) as string[]; - - inlineToolbarOrder.forEach((toolName) => { - const toolSettings = this.Editor.Tools.getToolSettings(toolName); - const tool = this.Editor.Tools.constructInline(this.Editor.Tools.inline[toolName], toolName, toolSettings); - - this.addTool(toolName, tool); - tool.checkState(SelectionUtils.get()); + Array.from(currentBlock.tool.inlineTools.values()).forEach(tool => { + this.addTool(tool); }); /** @@ -629,43 +529,38 @@ export default class InlineToolbar extends Module { /** * Add tool button and activate clicks * - * @param {string} toolName - name of Tool to add - * @param {InlineTool} tool - Tool class instance + * @param {InlineTool} tool - InlineTool object */ - private addTool(toolName: string, tool: InlineTool): void { - const { - Tools, - Tooltip, - } = this.Editor; - - const button = tool.render(); + private addTool(tool: InlineTool): void { + const instance = tool.create(); + const button = instance.render(); if (!button) { - _.log('Render method must return an instance of Node', 'warn', toolName); + _.log('Render method must return an instance of Node', 'warn', tool.name); return; } - button.dataset.tool = toolName; + button.dataset.tool = tool.name; this.nodes.buttons.appendChild(button); - this.toolsInstances.set(toolName, tool); + this.toolsInstances.set(tool.name, instance); - if (_.isFunction(tool.renderActions)) { - const actions = tool.renderActions(); + if (_.isFunction(instance.renderActions)) { + const actions = instance.renderActions(); this.nodes.actions.appendChild(actions); } this.listeners.on(button, 'click', (event) => { - this.toolClicked(tool); + this.toolClicked(instance); event.preventDefault(); }); - const shortcut = this.getToolShortcut(toolName); + const shortcut = this.getToolShortcut(tool.name); if (shortcut) { try { - this.enableShortcuts(tool, shortcut); + this.enableShortcuts(instance, shortcut); } catch (e) {} } @@ -675,7 +570,7 @@ export default class InlineToolbar extends Module { const tooltipContent = $.make('div'); const toolTitle = I18n.t( I18nInternalNS.toolNames, - Tools.toolsClasses[toolName][Tools.INTERNAL_SETTINGS.TITLE] || _.capitalize(toolName) + tool.title || _.capitalize(tool.name) ); tooltipContent.appendChild($.text(toolTitle)); @@ -686,10 +581,12 @@ export default class InlineToolbar extends Module { })); } - Tooltip.onHover(button, tooltipContent, { + this.tooltip.onHover(button, tooltipContent, { placement: 'top', hidingDelay: 100, }); + + instance.checkState(SelectionUtils.get()); } /** @@ -704,21 +601,20 @@ export default class InlineToolbar extends Module { * Enable shortcuts * Ignore tool that doesn't have shortcut or empty string */ - const toolSettings = Tools.getToolSettings(toolName); - const tool = this.toolsInstances.get(toolName); + const tool = Tools.inlineTools.get(toolName); /** * 1) For internal tools, check public getter 'shortcut' * 2) For external tools, check tool's settings * 3) If shortcut is not set in settings, check Tool's public property */ - if (Object.keys(this.internalTools).includes(toolName)) { - return this.inlineTools[toolName][Tools.INTERNAL_SETTINGS.SHORTCUT]; - } else if (toolSettings && toolSettings[Tools.USER_SETTINGS.SHORTCUT]) { - return toolSettings[Tools.USER_SETTINGS.SHORTCUT]; - } else if (tool.shortcut) { - return tool.shortcut; + const internalTools = Tools.internal.inlineTools; + + if (Array.from(internalTools.keys()).includes(toolName)) { + return this.inlineTools[toolName][CommonInternalSettings.Shortcut]; } + + return tool.shortcut; } /** @@ -727,7 +623,7 @@ export default class InlineToolbar extends Module { * @param {InlineTool} tool - Tool instance * @param {string} shortcut - shortcut according to the ShortcutData Module format */ - private enableShortcuts(tool: InlineTool, shortcut: string): void { + private enableShortcuts(tool: IInlineTool, shortcut: string): void { Shortcuts.add({ name: shortcut, handler: (event) => { @@ -747,9 +643,7 @@ export default class InlineToolbar extends Module { */ // if (SelectionUtils.isCollapsed) return; - const toolSettings = this.Editor.Tools.getToolSettings(currentBlock.name); - - if (!toolSettings || !toolSettings[this.Editor.Tools.USER_SETTINGS.ENABLED_INLINE_TOOLS]) { + if (!currentBlock.tool.enabledInlineTools) { return; } @@ -765,7 +659,7 @@ export default class InlineToolbar extends Module { * * @param {InlineTool} tool - Tool's instance */ - private toolClicked(tool: InlineTool): void { + private toolClicked(tool: IInlineTool): void { const range = SelectionUtils.range; tool.surround(range); @@ -785,16 +679,14 @@ export default class InlineToolbar extends Module { * Get inline tools tools * Tools that has isInline is true */ - private get inlineTools(): { [name: string]: InlineTool } { + private get inlineTools(): { [name: string]: IInlineTool } { const result = {}; - for (const tool in this.Editor.Tools.inline) { - if (Object.prototype.hasOwnProperty.call(this.Editor.Tools.inline, tool)) { - const toolSettings = this.Editor.Tools.getToolSettings(tool); - - result[tool] = this.Editor.Tools.constructInline(this.Editor.Tools.inline[tool], tool, toolSettings); - } - } + Array + .from(this.Editor.Tools.inlineTools.entries()) + .forEach(([name, tool]) => { + result[name] = tool.create(); + }); return result; } diff --git a/src/components/modules/toolbar/toolbox.ts b/src/components/modules/toolbar/toolbox.ts index add3e9e7..4c9eb175 100644 --- a/src/components/modules/toolbar/toolbox.ts +++ b/src/components/modules/toolbar/toolbox.ts @@ -1,12 +1,15 @@ import Module from '../../__module'; import $ from '../../dom'; import * as _ from '../../utils'; -import { BlockToolConstructable, ToolConstructable } from '../../../../types'; import Flipper from '../../flipper'; import { BlockToolAPI } from '../../block'; import I18n from '../../i18n'; import { I18nInternalNS } from '../../i18n/namespace-internal'; import Shortcuts from '../../utils/shortcuts'; +import Tooltip from '../../utils/tooltip'; +import { ModuleConfig } from '../../../types-internal/module-config'; +import EventsDispatcher from '../../utils/events'; +import BlockTool from '../../tools/block'; /** * HTMLElements used for Toolbox UI @@ -40,7 +43,7 @@ export default class Toolbox extends Module { * * @returns {object.} */ - public get CSS(): {[name: string]: string} { + public get CSS(): { [name: string]: string } { return { toolbox: 'ce-toolbox', toolboxButton: 'ce-toolbox__button', @@ -83,6 +86,24 @@ export default class Toolbox extends Module { */ private flipper: Flipper = null; + /** + * Tooltip utility Instance + */ + private tooltip: Tooltip; + /** + * @class + * @param {object} moduleConfiguration - Module Configuration + * @param {EditorConfig} moduleConfiguration.config - Editor's config + * @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher + */ + constructor({ config, eventsDispatcher }: ModuleConfig) { + super({ + config, + eventsDispatcher, + }); + this.tooltip = new Tooltip(); + } + /** * Makes the Toolbox */ @@ -107,6 +128,7 @@ export default class Toolbox extends Module { this.removeAllNodes(); this.removeAllShortcuts(); + this.tooltip.destroy(); } /** @@ -116,9 +138,7 @@ export default class Toolbox extends Module { * @param {string} toolName - button to activate */ public toolButtonActivate(event: MouseEvent|KeyboardEvent, toolName: string): void { - const tool = this.Editor.Tools.toolsClasses[toolName] as BlockToolConstructable; - - this.insertNewBlock(tool, toolName); + this.insertNewBlock(toolName); } /** @@ -162,36 +182,30 @@ export default class Toolbox extends Module { * Iterates available tools and appends them to the Toolbox */ private addTools(): void { - const tools = this.Editor.Tools.available; + const tools = this.Editor.Tools.blockTools; - for (const toolName in tools) { - if (Object.prototype.hasOwnProperty.call(tools, toolName)) { - this.addTool(toolName, tools[toolName] as BlockToolConstructable); - } - } + Array + .from(tools.values()) + .forEach((tool) => this.addTool(tool)); } /** * Append Tool to the Toolbox * - * @param {string} toolName - tool name - * @param {BlockToolConstructable} tool - tool class + * @param {BlockToolConstructable} tool - BlockTool object */ - private addTool(toolName: string, tool: BlockToolConstructable): void { - const internalSettings = this.Editor.Tools.INTERNAL_SETTINGS; - const userSettings = this.Editor.Tools.USER_SETTINGS; - - const toolToolboxSettings = tool[internalSettings.TOOLBOX]; + private addTool(tool: BlockTool): void { + const toolToolboxSettings = tool.toolbox; /** * Skip tools that don't pass 'toolbox' property */ - if (_.isEmpty(toolToolboxSettings)) { + if (!toolToolboxSettings) { return; } if (toolToolboxSettings && !toolToolboxSettings.icon) { - _.log('Toolbar icon is missed. Tool %o skipped', 'warn', toolName); + _.log('Toolbar icon is missed. Tool %o skipped', 'warn', tool.name); return; } @@ -204,19 +218,10 @@ export default class Toolbox extends Module { // return; // } - const userToolboxSettings = this.Editor.Tools.getToolSettings(toolName)[userSettings.TOOLBOX]; - - /** - * Hide Toolbox button if Toolbox settings is false - */ - if ((userToolboxSettings ?? toolToolboxSettings) === false) { - return; - } - const button = $.make('li', [ this.CSS.toolboxButton ]); - button.dataset.tool = toolName; - button.innerHTML = (userToolboxSettings && userToolboxSettings.icon) || toolToolboxSettings.icon; + button.dataset.tool = tool.name; + button.innerHTML = toolToolboxSettings.icon; $.append(this.nodes.toolbox, button); @@ -227,61 +232,40 @@ export default class Toolbox extends Module { * Add click listener */ this.listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => { - this.toolButtonActivate(event, toolName); + this.toolButtonActivate(event, tool.name); }); /** * Add listeners to show/hide toolbox tooltip */ - const tooltipContent = this.drawTooltip(toolName); + const tooltipContent = this.drawTooltip(tool); - this.Editor.Tooltip.onHover(button, tooltipContent, { + this.tooltip.onHover(button, tooltipContent, { placement: 'bottom', hidingDelay: 200, }); - const shortcut = this.getToolShortcut(toolName, tool); + const shortcut = tool.shortcut; if (shortcut) { - this.enableShortcut(tool, toolName, shortcut); + this.enableShortcut(tool.name, shortcut); } /** Increment Tools count */ this.displayedToolsCount++; } - /** - * Returns tool's shortcut - * It can be specified via internal 'shortcut' static getter or by user settings for tool - * - * @param {string} toolName - tool's name - * @param {ToolConstructable} tool - tool's class (not instance) - */ - private getToolShortcut(toolName: string, tool: ToolConstructable): string|null { - /** - * Enable shortcut - */ - const toolSettings = this.Editor.Tools.getToolSettings(toolName); - const internalToolShortcut = tool[this.Editor.Tools.INTERNAL_SETTINGS.SHORTCUT]; - const userSpecifiedShortcut = toolSettings ? toolSettings[this.Editor.Tools.USER_SETTINGS.SHORTCUT] : null; - - return userSpecifiedShortcut || internalToolShortcut; - } - /** * Draw tooltip for toolbox tools * - * @param {string} toolName - toolbox tool name + * @param tool - BlockTool object * @returns {HTMLElement} */ - private drawTooltip(toolName: string): HTMLElement { - const tool = this.Editor.Tools.available[toolName]; - const toolSettings = this.Editor.Tools.getToolSettings(toolName); - const toolboxSettings = this.Editor.Tools.available[toolName][this.Editor.Tools.INTERNAL_SETTINGS.TOOLBOX] || {}; - const userToolboxSettings = toolSettings.toolbox || {}; - const name = I18n.t(I18nInternalNS.toolNames, userToolboxSettings.title || toolboxSettings.title || toolName); + private drawTooltip(tool: BlockTool): HTMLElement { + const toolboxSettings = tool.toolbox || {}; + const name = I18n.t(I18nInternalNS.toolNames, toolboxSettings.title || tool.name); - let shortcut = this.getToolShortcut(toolName, tool); + let shortcut = tool.shortcut; const tooltip = $.make('div', this.CSS.buttonTooltip); const hint = document.createTextNode(_.capitalize(name)); @@ -302,16 +286,15 @@ export default class Toolbox extends Module { /** * Enable shortcut Block Tool implemented shortcut * - * @param {BlockToolConstructable} tool - Tool class * @param {string} toolName - Tool name * @param {string} shortcut - shortcut according to the ShortcutData Module format */ - private enableShortcut(tool: BlockToolConstructable, toolName: string, shortcut: string): void { + private enableShortcut(toolName: string, shortcut: string): void { Shortcuts.add({ name: shortcut, handler: (event: KeyboardEvent) => { event.preventDefault(); - this.insertNewBlock(tool, toolName); + this.insertNewBlock(toolName); }, on: this.Editor.UI.nodes.redactor, }); @@ -322,17 +305,17 @@ export default class Toolbox extends Module { * Fired when the Read-Only mode is activated */ private removeAllShortcuts(): void { - const tools = this.Editor.Tools.available; + const tools = this.Editor.Tools.blockTools; - for (const toolName in tools) { - if (Object.prototype.hasOwnProperty.call(tools, toolName)) { - const shortcut = this.getToolShortcut(toolName, tools[toolName]); + Array + .from(tools.values()) + .forEach((tool) => { + const shortcut = tool.shortcut; if (shortcut) { Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut); } - } - } + }); } /** @@ -351,10 +334,9 @@ export default class Toolbox extends Module { * Inserts new block * Can be called when button clicked on Toolbox or by ShortcutData * - * @param {BlockToolConstructable} tool - Tool Class * @param {string} toolName - Tool name */ - private insertNewBlock(tool: BlockToolConstructable, toolName: string): void { + private insertNewBlock(toolName: string): void { const { BlockManager, Caret } = this.Editor; const { currentBlock } = BlockManager; diff --git a/src/components/modules/tools.ts b/src/components/modules/tools.ts index ddcf66b1..2b4c9bcc 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -1,21 +1,19 @@ -import Paragraph from '../tools/paragraph/dist/bundle'; +import Paragraph from '../../tools/paragraph/dist/bundle'; import Module from '../__module'; import * as _ from '../utils'; -import { - BlockToolConstructable, - EditorConfig, - InlineTool, - InlineToolConstructable, Tool, - ToolConfig, - ToolConstructable, - ToolSettings -} from '../../../types'; +import { SanitizerConfig, ToolConstructable, ToolSettings } from '../../../types'; import BoldInlineTool from '../inline-tools/inline-tool-bold'; import ItalicInlineTool from '../inline-tools/inline-tool-italic'; import LinkInlineTool from '../inline-tools/inline-tool-link'; -import Stub from '../tools/stub'; -import { ModuleConfig } from '../../types-internal/module-config'; -import EventsDispatcher from '../utils/events'; +import Stub from '../../tools/stub'; +import ToolsFactory from '../tools/factory'; +import InlineTool from '../tools/inline'; +import BlockTool from '../tools/block'; +import BlockTune from '../tools/tune'; +import MoveDownTune from '../block-tunes/block-tune-move-down'; +import DeleteTune from '../block-tunes/block-tune-delete'; +import MoveUpTune from '../block-tunes/block-tune-move-up'; +import ToolsCollection from '../tools/collection'; /** * @module Editor.js Tools Submodule @@ -23,6 +21,8 @@ import EventsDispatcher from '../utils/events'; * Creates Instances from Plugins and binds external config to the instances */ +type ToolClass = BlockTool | InlineTool | BlockTune; + /** * Class properties: * @@ -47,7 +47,7 @@ export default class Tools extends Module { * * @returns {object} */ - public get available(): { [name: string]: ToolConstructable } { + public get available(): ToolsCollection { return this.toolsAvailable; } @@ -56,7 +56,7 @@ export default class Tools extends Module { * * @returns {Tool[]} */ - public get unavailable(): { [name: string]: ToolConstructable } { + public get unavailable(): ToolsCollection { return this.toolsUnavailable; } @@ -65,170 +65,53 @@ export default class Tools extends Module { * * @returns {object} - object of Inline Tool's classes */ - public get inline(): { [name: string]: InlineToolConstructable } { - if (this._inlineTools) { - return this._inlineTools; - } - - const tools = Object.entries(this.available).filter(([name, tool]) => { - if (!tool[this.INTERNAL_SETTINGS.IS_INLINE]) { - return false; - } - - /** - * Some Tools validation - */ - const inlineToolRequiredMethods = ['render', 'surround', 'checkState']; - const notImplementedMethods = inlineToolRequiredMethods.filter((method) => !this.constructInline(tool, name)[method]); - - if (notImplementedMethods.length) { - _.log( - `Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`, - 'warn', - notImplementedMethods - ); - - return false; - } - - return true; - }); - - /** - * collected inline tools with key of tool name - */ - const result = {}; - - tools.forEach(([name, tool]) => { - result[name] = tool; - }); - - /** - * Cache prepared Tools - */ - this._inlineTools = result; - - return this._inlineTools; + public get inlineTools(): ToolsCollection { + return this.available.inlineTools; } /** * Return editor block tools */ - public get blockTools(): { [name: string]: BlockToolConstructable } { - const tools = Object.entries(this.available).filter(([, tool]) => { - return !tool[this.INTERNAL_SETTINGS.IS_INLINE]; - }); - - /** - * collected block tools with key of tool name - */ - const result = {}; - - tools.forEach(([name, tool]) => { - result[name] = tool; - }); - - return result; + public get blockTools(): ToolsCollection { + return this.available.blockTools; } /** - * Constant for available Tools internal settings provided by Tool developer + * Return available Block Tunes * - * @returns {object} + * @returns {object} - object of Inline Tool's classes */ - public get INTERNAL_SETTINGS(): { [name: string]: string } { - return { - IS_ENABLED_LINE_BREAKS: 'enableLineBreaks', - IS_INLINE: 'isInline', - TITLE: 'title', // for Inline Tools. Block Tools can pass title along with icon through the 'toolbox' static prop. - SHORTCUT: 'shortcut', - TOOLBOX: 'toolbox', - SANITIZE_CONFIG: 'sanitize', - CONVERSION_CONFIG: 'conversionConfig', - IS_READ_ONLY_SUPPORTED: 'isReadOnlySupported', - }; + public get blockTunes(): ToolsCollection { + return this.available.blockTunes; } /** - * Constant for available Tools settings provided by user - * - * return {object} + * Returns default Tool object */ - public get USER_SETTINGS(): { [name: string]: string } { - return { - SHORTCUT: 'shortcut', - TOOLBOX: 'toolbox', - ENABLED_INLINE_TOOLS: 'inlineToolbar', - CONFIG: 'config', - }; + public get defaultTool(): BlockTool { + return this.blockTools.get(this.config.defaultBlock); } /** - * Map {name: Class, ...} where: - * name — block type name in JSON. Got from EditorConfig.tools keys - * - * @type {object} + * Tools objects factory */ - public readonly toolsClasses: { [name: string]: ToolConstructable } = {}; + private factory: ToolsFactory; /** * Tools` classes available to use */ - private readonly toolsAvailable: { [name: string]: ToolConstructable } = {}; + private readonly toolsAvailable: ToolsCollection = new ToolsCollection(); /** * Tools` classes not available to use because of preparation failure */ - private readonly toolsUnavailable: { [name: string]: ToolConstructable } = {}; + private readonly toolsUnavailable: ToolsCollection = new ToolsCollection(); /** - * Tools settings in a map {name: settings, ...} - * - * @type {object} + * Returns internal tools */ - private readonly toolsSettings: { [name: string]: ToolSettings } = {}; - - /** - * Cache for the prepared inline tools - * - * @type {null|object} - * @private - */ - private _inlineTools: { [name: string]: ToolConstructable } = {}; - - /** - * @class - * - * @param {EditorConfig} config - Editor's configuration - * @param {EventsDispatcher} eventsDispatcher - Editor's event dispatcher - */ - constructor({ config, eventsDispatcher }: ModuleConfig) { - super({ - config, - eventsDispatcher, - }); - - this.toolsClasses = {}; - - this.toolsSettings = {}; - - /** - * Available tools list - * {name: Class, ...} - * - * @type {object} - */ - this.toolsAvailable = {}; - - /** - * Tools that rejected a prepare method - * {name: Class, ... } - * - * @type {object} - */ - this.toolsUnavailable = {}; - - this._inlineTools = null; + public get internal(): ToolsCollection { + return this.available.internalTools; } /** @@ -236,7 +119,7 @@ export default class Tools extends Module { * * @returns {Promise} */ - public prepare(): Promise { + public async prepare(): Promise { this.validateTools(); /** @@ -248,54 +131,14 @@ export default class Tools extends Module { throw Error('Can\'t start without tools'); } - /** - * Save Tools settings to a map - */ - for (const toolName in this.config.tools) { - /** - * If Tool is an object not a Tool's class then - * save class and settings separately - */ - if (_.isObject(this.config.tools[toolName])) { - /** - * Save Tool's class from 'class' field - * - * @type {Tool} - */ - this.toolsClasses[toolName] = (this.config.tools[toolName] as ToolSettings).class; + const config = this.prepareConfig(); - /** - * Save Tool's settings - * - * @type {ToolSettings} - */ - this.toolsSettings[toolName] = this.config.tools[toolName] as ToolSettings; - - /** - * Remove Tool's class from settings - */ - delete this.toolsSettings[toolName].class; - } else { - /** - * Save Tool's class - * - * @type {Tool} - */ - this.toolsClasses[toolName] = this.config.tools[toolName] as ToolConstructable; - - /** - * Set empty settings for Block by default - * - * @type {{}} - */ - this.toolsSettings[toolName] = { class: this.config.tools[toolName] as ToolConstructable }; - } - } + this.factory = new ToolsFactory(config, this.config, this.Editor.API); /** * getting classes that has prepare method */ - const sequenceData = this.getListOfPrepareFunctions(); + const sequenceData = this.getListOfPrepareFunctions(config); /** * if sequence data contains nothing then resolve current chain and run other module prepare @@ -307,109 +150,28 @@ export default class Tools extends Module { /** * to see how it works {@link '../utils.ts#sequence'} */ - return _.sequence(sequenceData, (data: { toolName: string }) => { - this.success(data); + await _.sequence(sequenceData, (data: { toolName: string }) => { + this.toolPrepareMethodSuccess(data); }, (data: { toolName: string }) => { - this.fallback(data); + this.toolPrepareMethodFallback(data); }); + + this.prepareBlockTools(); } /** - * Success callback - * - * @param {object} data - append tool to available list + * Return general Sanitizer config for all inline tools */ - public success(data: { toolName: string }): void { - this.toolsAvailable[data.toolName] = this.toolsClasses[data.toolName]; - } + @_.cacheable + public getAllInlineToolsSanitizeConfig(): SanitizerConfig { + const config: SanitizerConfig = {} as SanitizerConfig; - /** - * Fail callback - * - * @param {object} data - append tool to unavailable list - */ - public fallback(data: { toolName: string }): void { - this.toolsUnavailable[data.toolName] = this.toolsClasses[data.toolName]; - } + Array.from(this.inlineTools.values()) + .forEach(inlineTool => { + Object.assign(config, inlineTool.sanitizeConfig); + }); - /** - * Return Inline Tool's instance - * - * @param {InlineTool} tool - Inline Tool instance - * @param {string} name - tool name - * @param {ToolSettings} toolSettings - tool settings - * - * @returns {InlineTool} — instance - */ - public constructInline( - tool: InlineToolConstructable, - name: string, - toolSettings: ToolSettings = {} as ToolSettings - ): InlineTool { - const constructorOptions = { - api: this.Editor.API.getMethodsForTool(name), - config: (toolSettings[this.USER_SETTINGS.CONFIG] || {}) as ToolSettings, - }; - - // eslint-disable-next-line new-cap - return new tool(constructorOptions) as InlineTool; - } - - /** - * Check if passed Tool is an instance of Default Block Tool - * - * @param {Tool} tool - Tool to check - * - * @returns {boolean} - */ - public isDefault(tool): boolean { - return tool instanceof this.available[this.config.defaultBlock]; - } - - /** - * Return Tool's config by name - * - * @param {string} toolName - name of tool - * - * @returns {ToolSettings} - */ - public getToolSettings(toolName): ToolSettings { - const settings = this.toolsSettings[toolName]; - const config = settings[this.USER_SETTINGS.CONFIG] || {}; - - // Pass placeholder to default Block config - if (toolName === this.config.defaultBlock && !config.placeholder) { - config.placeholder = this.config.placeholder; - settings[this.USER_SETTINGS.CONFIG] = config; - } - - return settings; - } - - /** - * Returns internal tools - * Includes Bold, Italic, Link and Paragraph - */ - public get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings } { - return { - bold: { class: BoldInlineTool }, - italic: { class: ItalicInlineTool }, - link: { class: LinkInlineTool }, - paragraph: { - class: Paragraph, - inlineToolbar: true, - }, - stub: { class: Stub }, - }; - } - - /** - * Returns true if tool supports read-only mode - * - * @param tool - tool to check - */ - public isReadOnlySupported(tool: BlockToolConstructable): boolean { - return tool[this.INTERNAL_SETTINGS.IS_READ_ONLY_SUPPORTED] === true; + return config; } /** @@ -424,44 +186,201 @@ export default class Tools extends Module { } /** - * Binds prepare function of plugins with user or default config - * - * @returns {Array} list of functions that needs to be fired sequentially + * Returns internal tools + * Includes Bold, Italic, Link and Paragraph */ - private getListOfPrepareFunctions(): Array<{ - function: (data: { toolName: string; config: ToolConfig }) => void; - data: { toolName: string; config: ToolConfig }; - }> { - const toolPreparationList: Array<{ - function: (data: { toolName: string; config: ToolConfig }) => void; - data: { toolName: string; config: ToolConfig }; - } - > = []; + private get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings & { isInternal?: boolean } } { + return { + bold: { + class: BoldInlineTool, + isInternal: true, + }, + italic: { + class: ItalicInlineTool, + isInternal: true, + }, + link: { + class: LinkInlineTool, + isInternal: true, + }, + paragraph: { + class: Paragraph, + inlineToolbar: true, + isInternal: true, + }, + stub: { + class: Stub, + isInternal: true, + }, + moveUp: { + class: MoveUpTune, + isInternal: true, + }, + delete: { + class: DeleteTune, + isInternal: true, + }, + moveDown: { + class: MoveDownTune, + isInternal: true, + }, + }; + } - for (const toolName in this.toolsClasses) { - if (Object.prototype.hasOwnProperty.call(this.toolsClasses, toolName)) { - const toolClass = this.toolsClasses[toolName]; - const toolConfig = this.toolsSettings[toolName][this.USER_SETTINGS.CONFIG]; + /** + * Tool prepare method success callback + * + * @param {object} data - append tool to available list + */ + private toolPrepareMethodSuccess(data: { toolName: string }): void { + const tool = this.factory.get(data.toolName); - /** - * If Tool hasn't a prepare method, - * still push it to tool preparation list to save tools order in Toolbox. - * As Tool's prepare method might be async, _.sequence util helps to save the order. - */ - toolPreparationList.push({ - // eslint-disable-next-line @typescript-eslint/no-empty-function - function: _.isFunction(toolClass.prepare) ? toolClass.prepare : (): void => { }, - data: { - toolName, - config: toolConfig, - }, - }); + if (tool.isInline()) { + /** + * Some Tools validation + */ + const inlineToolRequiredMethods = ['render', 'surround', 'checkState']; + const notImplementedMethods = inlineToolRequiredMethods.filter((method) => !tool.create()[method]); + + if (notImplementedMethods.length) { + _.log( + `Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`, + 'warn', + notImplementedMethods + ); + + this.toolsUnavailable.set(tool.name, tool); + + return; } } + this.toolsAvailable.set(tool.name, tool); + } + + /** + * Tool prepare method fail callback + * + * @param {object} data - append tool to unavailable list + */ + private toolPrepareMethodFallback(data: { toolName: string }): void { + this.toolsUnavailable.set(data.toolName, this.factory.get(data.toolName)); + } + + /** + * Binds prepare function of plugins with user or default config + * + * @returns {Array} list of functions that needs to be fired sequentially + * @param config - tools config + */ + private getListOfPrepareFunctions(config: {[name: string]: ToolSettings}): { + function: (data: { toolName: string }) => void | Promise; + data: { toolName: string }; + }[] { + const toolPreparationList: { + function: (data: { toolName: string }) => void | Promise; + data: { toolName: string }; + }[] = []; + + Object + .entries(config) + .forEach(([toolName, settings]) => { + toolPreparationList.push({ + // eslint-disable-next-line @typescript-eslint/no-empty-function + function: _.isFunction(settings.class.prepare) ? settings.class.prepare : (): void => {}, + data: { + toolName, + }, + }); + }); + return toolPreparationList; } + /** + * Assign enabled Inline Tools and Block Tunes for Block Tool + */ + private prepareBlockTools(): void { + Array.from(this.blockTools.values()).forEach(tool => { + this.assignInlineToolsToBlockTool(tool); + this.assignBlockTunesToBlockTool(tool); + }); + } + + /** + * Assign enabled Inline Tools for Block Tool + * + * @param tool - Block Tool + */ + private assignInlineToolsToBlockTool(tool: BlockTool): void { + /** + * If common inlineToolbar property is false no Inline Tools should be assigned + */ + if (this.config.inlineToolbar === false) { + return; + } + + /** + * If user pass just 'true' for tool, get common inlineToolbar settings + * - if common settings is an array, use it + * - if common settings is 'true' or not specified, get default order + */ + if (tool.enabledInlineTools === true) { + tool.inlineTools = new ToolsCollection( + Array.isArray(this.config.inlineToolbar) + ? this.config.inlineToolbar.map(name => [name, this.inlineTools.get(name)]) + /** + * If common settings is 'true' or not specified (will be set as true at core.ts), get the default order + */ + : Array.from(this.inlineTools.entries()) + ); + + return; + } + + /** + * If user pass the list of inline tools for the particular tool, return it. + */ + if (Array.isArray(tool.enabledInlineTools)) { + tool.inlineTools = new ToolsCollection( + tool.enabledInlineTools.map(name => [name, this.inlineTools.get(name)]) + ); + } + } + + /** + * Assign enabled Block Tunes for Block Tool + * + * @param tool — Block Tool + */ + private assignBlockTunesToBlockTool(tool: BlockTool): void { + if (tool.enabledBlockTunes === false) { + return; + } + + if (Array.isArray(tool.enabledBlockTunes)) { + const userTunes = new ToolsCollection( + tool.enabledBlockTunes.map(name => [name, this.blockTunes.get(name)]) + ); + + tool.tunes = new ToolsCollection([...userTunes, ...this.blockTunes.internalTools]); + + return; + } + + if (Array.isArray(this.config.tunes)) { + const userTunes = new ToolsCollection( + this.config.tunes.map(name => [name, this.blockTunes.get(name)]) + ); + + tool.tunes = new ToolsCollection([...userTunes, ...this.blockTunes.internalTools]); + + return; + } + + tool.tunes = this.blockTunes.internalTools; + } + /** * Validate Tools configuration objects and throw Error for user if it is invalid */ @@ -485,23 +404,28 @@ export default class Tools extends Module { } } } -} - -/** - * What kind of plugins developers can create - */ -export enum ToolType { - /** - * Block tool - */ - Block, - /** - * Inline tool - */ - Inline, /** - * Block tune + * Unify tools config */ - Tune, + private prepareConfig(): {[name: string]: ToolSettings} { + const config: {[name: string]: ToolSettings} = {}; + + /** + * Save Tools settings to a map + */ + for (const toolName in this.config.tools) { + /** + * If Tool is an object not a Tool's class then + * save class and settings separately + */ + if (_.isObject(this.config.tools[toolName])) { + config[toolName] = this.config.tools[toolName] as ToolSettings; + } else { + config[toolName] = { class: this.config.tools[toolName] as ToolConstructable }; + } + } + + return config; + } } diff --git a/src/components/modules/ui.ts b/src/components/modules/ui.ts index 1d9874b3..c697e47a 100644 --- a/src/components/modules/ui.ts +++ b/src/components/modules/ui.ts @@ -340,7 +340,7 @@ export default class UI extends Module { this.documentKeydown(event); }, true); - this.readOnlyMutableListeners.on(document, 'click', (event: MouseEvent) => { + this.readOnlyMutableListeners.on(document, 'mousedown', (event: MouseEvent) => { this.documentClicked(event); }, true); @@ -591,9 +591,7 @@ export default class UI extends Module { /** * Clear Selection if user clicked somewhere */ - if (!this.Editor.CrossBlockSelection.isCrossBlockSelectionStarted) { - this.Editor.BlockSelection.clearSelection(event); - } + this.Editor.BlockSelection.clearSelection(event); } /** @@ -664,6 +662,8 @@ export default class UI extends Module { * - otherwise, add a new empty Block and set a Caret to that */ private redactorClicked(event: MouseEvent): void { + const { BlockSelection } = this.Editor; + if (!Selection.isCollapsed) { return; } @@ -691,10 +691,33 @@ export default class UI extends Module { return; } - if (!this.Editor.BlockManager.currentBlock) { + const isClickedBottom = event.target instanceof Element && + event.target.isEqualNode(this.nodes.redactor) && + /** + * If there is cross block selection started, target will be equal to redactor so we need additional check + */ + !BlockSelection.anyBlockSelected; + + if (isClickedBottom) { stopPropagation(); - this.Editor.BlockManager.insert(); + const { BlockManager, Caret, Toolbar } = this.Editor; + + /** + * Insert a default-block at the bottom if: + * - last-block is not a default-block (Text) + * to prevent unnecessary tree-walking on Tools with many nodes (for ex. Table) + * - Or, default-block is not empty + */ + if (!BlockManager.lastBlock.tool.isDefault || !BlockManager.lastBlock.isEmpty) { + BlockManager.insertAtEnd(); + } + + /** + * Set the caret and toolbar to empty Block + */ + Caret.setToTheLastBlock(); + Toolbar.move(); } /** @@ -702,7 +725,7 @@ export default class UI extends Module { * - Block is an default-block (Text) * - Block is empty */ - const isDefaultBlock = this.Editor.Tools.isDefault(this.Editor.BlockManager.currentBlock.tool); + const isDefaultBlock = this.Editor.BlockManager.currentBlock.tool.isDefault; if (isDefaultBlock) { stopPropagation(); @@ -736,10 +759,28 @@ export default class UI extends Module { } /** - * Event can be fired on clicks at the Editor elements, for example, at the Inline Toolbar - * We need to skip such firings + * Usual clicks on some controls, for example, Block Tunes Toggler */ - if (!focusedElement || !focusedElement.closest(`.${Block.CSS.content}`)) { + if (!focusedElement) { + /** + * If there is no selected range, close inline toolbar + * + * @todo Make this method more straightforward + */ + if (!Selection.range) { + this.Editor.InlineToolbar.close(); + } + + return; + } + + /** + * Event can be fired on clicks at non-block-content elements, + * for example, at the Inline Toolbar or some Block Tune element + */ + const clickedOutsideBlockContent = focusedElement.closest(`.${Block.CSS.content}`) === null; + + if (clickedOutsideBlockContent) { /** * If new selection is not on Inline Toolbar, we need to close it */ @@ -747,7 +788,16 @@ export default class UI extends Module { this.Editor.InlineToolbar.close(); } - return; + /** + * Case when we click on external tool elements, + * for example some Block Tune element. + * If this external content editable element has data-inline-toolbar="true" + */ + const inlineToolbarEnabledForExternalTool = (focusedElement as HTMLElement).dataset.inlineToolbar === 'true'; + + if (!inlineToolbarEnabledForExternalTool) { + return; + } } /** @@ -757,10 +807,12 @@ export default class UI extends Module { this.Editor.BlockManager.setCurrentBlockByChildNode(focusedElement); } + const isNeedToShowConversionToolbar = clickedOutsideBlockContent !== true; + /** * @todo add debounce */ - this.Editor.InlineToolbar.tryToShow(true); + this.Editor.InlineToolbar.tryToShow(true, isNeedToShowConversionToolbar); } /** diff --git a/src/components/selection.ts b/src/components/selection.ts index 32c12ee0..d4bbcbf9 100644 --- a/src/components/selection.ts +++ b/src/components/selection.ts @@ -34,6 +34,34 @@ interface Document { * @typedef {SelectionUtils} SelectionUtils */ export default class SelectionUtils { + /** + * Selection instances + * + * @todo Check if this is still relevant + */ + public instance: Selection = null; + public selection: Selection = null; + + /** + * This property can store SelectionUtils's range for restoring later + * + * @type {Range|null} + */ + public savedSelectionRange: Range = null; + + /** + * Fake background is active + * + * @returns {boolean} + */ + public isFakeBackgroundEnabled = false; + + /** + * Native Document's commands for fake background + */ + private readonly commandBackground: string = 'backColor'; + private readonly commandRemoveFormat: string = 'removeFormat'; + /** * Editor styles * @@ -112,7 +140,18 @@ export default class SelectionUtils { * @returns {boolean} */ public static get isAtEditor(): boolean { - const selection = SelectionUtils.get(); + return this.isSelectionAtEditor(SelectionUtils.get()); + } + + /** + * Check if passed selection is at Editor's zone + * + * @param selection - Selectoin object to check + */ + public static isSelectionAtEditor(selection: Selection): boolean { + if (!selection) { + return false; + } /** * Something selected on document @@ -125,14 +164,42 @@ export default class SelectionUtils { let editorZone = null; - if (selectedNode) { + if (selectedNode && selectedNode instanceof Element) { editorZone = selectedNode.closest(`.${SelectionUtils.CSS.editorZone}`); } /** * SelectionUtils is not out of Editor because Editor's wrapper was found */ - return editorZone && editorZone.nodeType === Node.ELEMENT_NODE; + return editorZone ? editorZone.nodeType === Node.ELEMENT_NODE : false; + } + + /** + * Check if passed range at Editor zone + * + * @param range - range to check + */ + public static isRangeAtEditor(range: Range): boolean { + if (!range) { + return; + } + + let selectedNode = range.startContainer as HTMLElement; + + if (selectedNode && selectedNode.nodeType === Node.TEXT_NODE) { + selectedNode = selectedNode.parentNode as HTMLElement; + } + + let editorZone = null; + + if (selectedNode && selectedNode instanceof Element) { + editorZone = selectedNode.closest(`.${SelectionUtils.CSS.editorZone}`); + } + + /** + * SelectionUtils is not out of Editor because Editor's wrapper was found + */ + return editorZone ? editorZone.nodeType === Node.ELEMENT_NODE : false; } /** @@ -150,8 +217,15 @@ export default class SelectionUtils { * @returns {Range|null} */ public static get range(): Range | null { - const selection = window.getSelection(); + return this.getRangeFromSelection(this.get()); + } + /** + * Returns range from passed Selection object + * + * @param selection - Selection object to get Range from + */ + public static getRangeFromSelection(selection: Selection): Range { return selection && selection.rangeCount ? selection.getRangeAt(0) : null; } @@ -237,34 +311,6 @@ export default class SelectionUtils { return window.getSelection ? window.getSelection().toString() : ''; } - /** - * Selection instances - * - * @todo Check if this is still relevant - */ - public instance: Selection = null; - public selection: Selection = null; - - /** - * This property can store SelectionUtils's range for restoring later - * - * @type {Range|null} - */ - public savedSelectionRange: Range = null; - - /** - * Fake background is active - * - * @returns {boolean} - */ - public isFakeBackgroundEnabled = false; - - /** - * Native Document's commands for fake background - */ - private readonly commandBackground: string = 'backColor'; - private readonly commandRemoveFormat: string = 'removeFormat'; - /** * Returns window SelectionUtils * {@link https://developer.mozilla.org/ru/docs/Web/API/Window/getSelection} @@ -308,6 +354,36 @@ export default class SelectionUtils { return range.getBoundingClientRect(); } + /** + * Adds fake cursor to the current range + * + * @param [container] - if passed cursor will be added only if container contains current range + */ + public static addFakeCursor(container?: HTMLElement): void { + const range = SelectionUtils.range; + const fakeCursor = $.make('span', 'codex-editor__fake-cursor'); + + fakeCursor.dataset.mutationFree = 'true'; + + if (!range || (container && !container.contains(range.startContainer))) { + return; + } + + range.collapse(); + range.insertNode(fakeCursor); + } + + /** + * Removes fake cursor from a container + * + * @param container - container to look for + */ + public static removeFakeCursor(container: HTMLElement = document.body): void { + const fakeCursor = $.find(container, `.codex-editor__fake-cursor`); + + fakeCursor && fakeCursor.remove(); + } + /** * Removes fake background */ diff --git a/src/components/tools/base.ts b/src/components/tools/base.ts new file mode 100644 index 00000000..a4147979 --- /dev/null +++ b/src/components/tools/base.ts @@ -0,0 +1,277 @@ +import { Tool, ToolConstructable, ToolSettings } from '../../../types/tools'; +import { SanitizerConfig } from '../../../types'; +import * as _ from '../utils'; +import type InlineTool from './inline'; +import type BlockTool from './block'; +import type BlockTune from './tune'; +import API from '../modules/api'; + +/** + * What kind of plugins developers can create + */ +export enum ToolType { + /** + * Block tool + */ + Block, + /** + * Inline tool + */ + Inline, + + /** + * Block tune + */ + Tune, +} + +/** + * Enum of Tool options provided by user + */ +export enum UserSettings { + /** + * Shortcut for Tool + */ + Shortcut = 'shortcut', + /** + * Toolbox config for Tool + */ + Toolbox = 'toolbox', + /** + * Enabled Inline Tools for Block Tool + */ + EnabledInlineTools = 'inlineToolbar', + /** + * Enabled Block Tunes for Block Tool + */ + EnabledBlockTunes = 'tunes', + /** + * Tool configuration + */ + Config = 'config', +} + +/** + * Enum of Tool options provided by Tool + */ +export enum CommonInternalSettings { + /** + * Shortcut for Tool + */ + Shortcut = 'shortcut', + /** + * Sanitize configuration for Tool + */ + SanitizeConfig = 'sanitize', + +} + +/** + * Enum of Tool optoins provided by Block Tool + */ +export enum InternalBlockToolSettings { + /** + * Is linebreaks enabled for Tool + */ + IsEnabledLineBreaks = 'enableLineBreaks', + /** + * Tool Toolbox config + */ + Toolbox = 'toolbox', + /** + * Tool conversion config + */ + ConversionConfig = 'conversionConfig', + /** + * Is readonly mode supported for Tool + */ + IsReadOnlySupported = 'isReadOnlySupported', + /** + * Tool paste config + */ + PasteConfig = 'pasteConfig' +} + +/** + * Enum of Tool options provided by Inline Tool + */ +export enum InternalInlineToolSettings { + /** + * Flag specifies Tool is inline + */ + IsInline = 'isInline', + /** + * Inline Tool title for toolbar + */ + Title = 'title', // for Inline Tools. Block Tools can pass title along with icon through the 'toolbox' static prop. +} + +/** + * Enum of Tool options provided by Block Tune + */ +export enum InternalTuneSettings { + /** + * Flag specifies Tool is Block Tune + */ + IsTune = 'isTune', +} + +export type ToolOptions = Omit + +interface ConstructorOptions { + name: string; + constructable: ToolConstructable; + config: ToolOptions; + api: API; + isDefault: boolean; + isInternal: boolean; + defaultPlaceholder?: string | false; +} + +/** + * Base abstract class for Tools + */ +export default abstract class BaseTool { + /** + * Tool type: Block, Inline or Tune + */ + public type: ToolType; + + /** + * Tool name specified in EditorJS config + */ + public name: string; + + /** + * Flag show is current Tool internal (bundled with EditorJS core) or not + */ + public readonly isInternal: boolean; + + /** + * Flag show is current Tool default or not + */ + public readonly isDefault: boolean; + + /** + * EditorJS API for current Tool + */ + protected api: API; + + /** + * Current tool user configuration + */ + protected config: ToolOptions; + + /** + * Tool's constructable blueprint + */ + protected constructable: ToolConstructable; + + /** + * Default placeholder specified in EditorJS user configuration + */ + protected defaultPlaceholder?: string | false; + + /** + * @class + * + * @param {ConstructorOptions} - Constructor options + */ + constructor({ + name, + constructable, + config, + api, + isDefault, + isInternal = false, + defaultPlaceholder, + }: ConstructorOptions) { + this.api = api; + this.name = name; + this.constructable = constructable; + this.config = config; + this.isDefault = isDefault; + this.isInternal = isInternal; + this.defaultPlaceholder = defaultPlaceholder; + } + + /** + * Returns Tool user configuration + */ + public get settings(): ToolOptions { + const config = this.config[UserSettings.Config] || {}; + + if (this.isDefault && !('placeholder' in config) && this.defaultPlaceholder) { + config.placeholder = this.defaultPlaceholder; + } + + return config; + } + + /** + * Calls Tool's reset method + */ + public reset(): void | Promise { + if (_.isFunction(this.constructable.reset)) { + return this.constructable.reset(); + } + } + + /** + * Calls Tool's prepare method + */ + public prepare(): void | Promise { + if (_.isFunction(this.constructable.prepare)) { + return this.constructable.prepare({ + toolName: this.name, + config: this.settings, + }); + } + } + + /** + * Returns shortcut for Tool (internal or specified by user) + */ + public get shortcut(): string | undefined { + const toolShortcut = this.constructable[CommonInternalSettings.Shortcut]; + const userShortcut = this.config[UserSettings.Shortcut]; + + return userShortcut || toolShortcut; + } + + /** + * Returns Tool's sanitizer configuration + */ + public get sanitizeConfig(): SanitizerConfig { + return this.constructable[CommonInternalSettings.SanitizeConfig] || {}; + } + + /** + * Returns true if Tools is inline + */ + public isInline(): this is InlineTool { + return this.type === ToolType.Inline; + } + + /** + * Returns true if Tools is block + */ + public isBlock(): this is BlockTool { + return this.type === ToolType.Block; + } + + /** + * Returns true if Tools is tune + */ + public isTune(): this is BlockTune { + return this.type === ToolType.Tune; + } + + /** + * Constructs new Tool instance from constructable blueprint + * + * @param args + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public abstract create(...args: any[]): Type; +} diff --git a/src/components/tools/block.ts b/src/components/tools/block.ts new file mode 100644 index 00000000..e410851d --- /dev/null +++ b/src/components/tools/block.ts @@ -0,0 +1,169 @@ +import BaseTool, { InternalBlockToolSettings, ToolType, UserSettings } from './base'; +import { + BlockAPI, + BlockTool as IBlockTool, + BlockToolConstructable, + BlockToolData, + ConversionConfig, + PasteConfig, SanitizerConfig, + ToolboxConfig +} from '../../../types'; +import * as _ from '../utils'; +import InlineTool from './inline'; +import BlockTune from './tune'; +import ToolsCollection from './collection'; + +/** + * Class to work with Block tools constructables + */ +export default class BlockTool extends BaseTool { + /** + * Tool type — Block + */ + public type = ToolType.Block; + + /** + * InlineTool collection for current Block Tool + */ + public inlineTools: ToolsCollection = new ToolsCollection(); + + /** + * BlockTune collection for current Block Tool + */ + public tunes: ToolsCollection = new ToolsCollection(); + + /** + * Tool's constructable blueprint + */ + protected constructable: BlockToolConstructable; + + /** + * Creates new Tool instance + * + * @param data - Tool data + * @param block - BlockAPI for current Block + * @param readOnly - True if Editor is in read-only mode + */ + public create(data: BlockToolData, block: BlockAPI, readOnly: boolean): IBlockTool { + // eslint-disable-next-line new-cap + return new this.constructable({ + data, + block, + readOnly, + api: this.api.getMethodsForTool(this), + config: this.settings, + }) as IBlockTool; + } + + /** + * Returns true if read-only mode is supported by Tool + */ + public get isReadOnlySupported(): boolean { + return this.constructable[InternalBlockToolSettings.IsReadOnlySupported] === true; + } + + /** + * Returns true if Tool supports linebreaks + */ + public get isLineBreaksEnabled(): boolean { + return this.constructable[InternalBlockToolSettings.IsEnabledLineBreaks]; + } + + /** + * Returns Tool toolbox configuration (internal or user-specified) + */ + public get toolbox(): ToolboxConfig { + const toolToolboxSettings = this.constructable[InternalBlockToolSettings.Toolbox] as ToolboxConfig; + const userToolboxSettings = this.config[UserSettings.Toolbox]; + + if (_.isEmpty(toolToolboxSettings)) { + return; + } + + if ((userToolboxSettings ?? toolToolboxSettings) === false) { + return; + } + + return Object.assign({}, toolToolboxSettings, userToolboxSettings); + } + + /** + * Returns Tool conversion configuration + */ + public get conversionConfig(): ConversionConfig { + return this.constructable[InternalBlockToolSettings.ConversionConfig]; + } + + /** + * Returns enabled inline tools for Tool + */ + public get enabledInlineTools(): boolean | string[] { + return this.config[UserSettings.EnabledInlineTools] || false; + } + + /** + * Returns enabled tunes for Tool + */ + public get enabledBlockTunes(): boolean | string[] { + return this.config[UserSettings.EnabledBlockTunes]; + } + + /** + * Returns Tool paste configuration + */ + public get pasteConfig(): PasteConfig { + return this.constructable[InternalBlockToolSettings.PasteConfig] || {}; + } + + /** + * Returns sanitize configuration for Block Tool including configs from related Inline Tools and Block Tunes + */ + @_.cacheable + public get sanitizeConfig(): SanitizerConfig { + const toolRules = super.sanitizeConfig; + const baseConfig = this.baseSanitizeConfig; + + if (_.isEmpty(toolRules)) { + return baseConfig; + } + + const toolConfig = {} as SanitizerConfig; + + for (const fieldName in toolRules) { + if (Object.prototype.hasOwnProperty.call(toolRules, fieldName)) { + const rule = toolRules[fieldName]; + + /** + * If rule is object, merge it with Inline Tools configuration + * + * Otherwise pass as it is + */ + if (_.isObject(rule)) { + toolConfig[fieldName] = Object.assign({}, baseConfig, rule); + } else { + toolConfig[fieldName] = rule; + } + } + } + + return toolConfig; + } + + /** + * Returns sanitizer configuration composed from sanitize config of Inline Tools enabled for Tool + */ + @_.cacheable + public get baseSanitizeConfig(): SanitizerConfig { + const baseConfig = {}; + + Array + .from(this.inlineTools.values()) + .forEach(tool => Object.assign(baseConfig, tool.sanitizeConfig)); + + Array + .from(this.tunes.values()) + .forEach(tune => Object.assign(baseConfig, tune.sanitizeConfig)); + + return baseConfig; + } +} diff --git a/src/components/tools/collection.ts b/src/components/tools/collection.ts new file mode 100644 index 00000000..38ebf8c4 --- /dev/null +++ b/src/components/tools/collection.ts @@ -0,0 +1,65 @@ +import BlockTool from './block'; +import InlineTool from './inline'; +import BlockTune from './tune'; + +export type ToolClass = BlockTool | InlineTool | BlockTune; + +/** + * Class to store Editor Tools + */ +export default class ToolsCollection extends Map { + /** + * Returns Block Tools collection + */ + public get blockTools(): ToolsCollection { + const tools = Array + .from(this.entries()) + .filter(([, tool]) => tool.isBlock()) as [string, BlockTool][]; + + return new ToolsCollection(tools); + } + + /** + * Returns Inline Tools collection + */ + public get inlineTools(): ToolsCollection { + const tools = Array + .from(this.entries()) + .filter(([, tool]) => tool.isInline()) as [string, InlineTool][]; + + return new ToolsCollection(tools); + } + + /** + * Returns Block Tunes collection + */ + public get blockTunes(): ToolsCollection { + const tools = Array + .from(this.entries()) + .filter(([, tool]) => tool.isTune()) as [string, BlockTune][]; + + return new ToolsCollection(tools); + } + + /** + * Returns internal Tools collection + */ + public get internalTools(): ToolsCollection { + const tools = Array + .from(this.entries()) + .filter(([, tool]) => tool.isInternal); + + return new ToolsCollection(tools); + } + + /** + * Returns Tools collection provided by user + */ + public get externalTools(): ToolsCollection { + const tools = Array + .from(this.entries()) + .filter(([, tool]) => !tool.isInternal); + + return new ToolsCollection(tools); + } +} diff --git a/src/components/tools/factory.ts b/src/components/tools/factory.ts new file mode 100644 index 00000000..f994d396 --- /dev/null +++ b/src/components/tools/factory.ts @@ -0,0 +1,83 @@ +import { ToolConstructable, ToolSettings } from '../../../types/tools'; +import { InternalInlineToolSettings, InternalTuneSettings } from './base'; +import InlineTool from './inline'; +import BlockTune from './tune'; +import BlockTool from './block'; +import API from '../modules/api'; +import { EditorConfig } from '../../../types/configs'; + +type ToolConstructor = typeof InlineTool | typeof BlockTool | typeof BlockTune; + +/** + * Factory to construct classes to work with tools + */ +export default class ToolsFactory { + /** + * Tools configuration specified by user + */ + private config: {[name: string]: ToolSettings & { isInternal?: boolean }}; + + /** + * EditorJS API Module + */ + private api: API; + + /** + * EditorJS configuration + */ + private editorConfig: EditorConfig; + + /** + * @class + * + * @param config - tools config + * @param editorConfig - EditorJS config + * @param api - EditorJS API module + */ + constructor( + config: {[name: string]: ToolSettings & { isInternal?: boolean }}, + editorConfig: EditorConfig, + api: API + ) { + this.api = api; + this.config = config; + this.editorConfig = editorConfig; + } + + /** + * Returns Tool object based on it's type + * + * @param name - tool name + */ + public get(name: string): InlineTool | BlockTool | BlockTune { + const { class: constructable, isInternal = false, ...config } = this.config[name]; + + const Constructor = this.getConstructor(constructable); + + return new Constructor({ + name, + constructable, + config, + api: this.api, + isDefault: name === this.editorConfig.defaultBlock, + defaultPlaceholder: this.editorConfig.placeholder, + isInternal, + }); + } + + /** + * Find appropriate Tool object constructor for Tool constructable + * + * @param constructable - Tools constructable + */ + private getConstructor(constructable: ToolConstructable): ToolConstructor { + switch (true) { + case constructable[InternalInlineToolSettings.IsInline]: + return InlineTool; + case constructable[InternalTuneSettings.IsTune]: + return BlockTune; + default: + return BlockTool; + } + } +} diff --git a/src/components/tools/inline.ts b/src/components/tools/inline.ts new file mode 100644 index 00000000..281d0d7d --- /dev/null +++ b/src/components/tools/inline.ts @@ -0,0 +1,35 @@ +import BaseTool, { InternalInlineToolSettings, ToolType } from './base'; +import { InlineTool as IInlineTool, InlineToolConstructable } from '../../../types'; + +/** + * InlineTool object to work with Inline Tools constructables + */ +export default class InlineTool extends BaseTool { + /** + * Tool type — Inline + */ + public type = ToolType.Inline; + + /** + * Tool's constructable blueprint + */ + protected constructable: InlineToolConstructable; + + /** + * Returns title for Inline Tool if specified by user + */ + public get title(): string { + return this.constructable[InternalInlineToolSettings.Title]; + } + + /** + * Constructs new InlineTool instance from constructable + */ + public create(): IInlineTool { + // eslint-disable-next-line new-cap + return new this.constructable({ + api: this.api.getMethodsForTool(this), + config: this.settings, + }) as IInlineTool; + } +} diff --git a/src/components/tools/tune.ts b/src/components/tools/tune.ts new file mode 100644 index 00000000..52230cbd --- /dev/null +++ b/src/components/tools/tune.ts @@ -0,0 +1,36 @@ +import BaseTool, { ToolType } from './base'; +import { BlockAPI, BlockTune as IBlockTune, BlockTuneConstructable } from '../../../types'; +import { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; + +/** + * Stub class for BlockTunes + * + * @todo Implement + */ +export default class BlockTune extends BaseTool { + /** + * Tool type — Tune + */ + public type = ToolType.Tune; + + /** + * Tool's constructable blueprint + */ + protected readonly constructable: BlockTuneConstructable; + + /** + * Constructs new BlockTune instance from constructable + * + * @param data - Tune data + * @param block - Block API object + */ + public create(data: BlockTuneData, block: BlockAPI): IBlockTune { + // eslint-disable-next-line new-cap + return new this.constructable({ + api: this.api.getMethodsForTool(this), + config: this.settings, + block, + data, + }); + } +} diff --git a/src/components/utils.ts b/src/components/utils.ts index 4634e590..90c3c32f 100644 --- a/src/components/utils.ts +++ b/src/components/utils.ts @@ -2,6 +2,7 @@ * Class Util */ +import { nanoid } from 'nanoid'; import Dom from './dom'; /** @@ -428,14 +429,12 @@ export function isValidMimeType(type: string): boolean { * @param {boolean} immediate - call now * @returns {Function} */ -export function debounce(func: () => void, wait?: number, immediate?: boolean): () => void { +export function debounce(func: (...args: unknown[]) => void, wait?: number, immediate?: boolean): () => void { let timeout; - return (): void => { + return (...args: unknown[]): void => { // eslint-disable-next-line @typescript-eslint/no-this-alias - const context = this, - // eslint-disable-next-line prefer-rest-params - args = arguments; + const context = this; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const later = () => { @@ -607,6 +606,15 @@ export function getValidUrl(url: string): string { } } +/** + * Create a block id + * + * @returns {string} + */ +export function generateBlockId(): string { + return nanoid(10); +} + /** * Opens new Tab with passed URL * @@ -642,3 +650,53 @@ export function deprecationAssert(condition: boolean, oldProperty: string, newPr logLabeled(message, 'warn'); } } + +/** + * Decorator which provides ability to cache method or accessor result + * + * @param target - target instance or constructor function + * @param propertyKey - method or accessor name + * @param descriptor - property descriptor + */ +export function cacheable( + target: Target, + propertyKey: string, + descriptor: PropertyDescriptor +): PropertyDescriptor { + const propertyToOverride = descriptor.value ? 'value' : 'get'; + const originalMethod = descriptor[propertyToOverride]; + const cacheKey = `#${propertyKey}Cache`; + + /** + * Override get or value descriptor property to cache return value + * + * @param args - method args + */ + descriptor[propertyToOverride] = function (...args: Arguments): Value { + /** + * If there is no cache, create it + */ + if (this[cacheKey] === undefined) { + this[cacheKey] = originalMethod.apply(this, ...args); + } + + return this[cacheKey]; + }; + + /** + * If get accessor has been overridden, we need to override set accessor to clear cache + * + * @param value - value to set + */ + if (propertyToOverride === 'get' && descriptor.set) { + const originalSet = descriptor.set; + + descriptor.set = function (value: unknown): void { + delete target[cacheKey]; + + originalSet.apply(this, value); + }; + } + + return descriptor; +}; diff --git a/src/components/utils/events.ts b/src/components/utils/events.ts index 10d501ec..63812282 100644 --- a/src/components/utils/events.ts +++ b/src/components/utils/events.ts @@ -1,3 +1,5 @@ +import { isEmpty } from '../utils'; + /** * @class EventDispatcher * @@ -11,7 +13,7 @@ * @typedef {Events} Events * @property {object} subscribers - all subscribers grouped by event name */ -export default class EventsDispatcher { +export default class EventsDispatcher { /** * Object with events` names as key and array of callback functions as value * @@ -25,7 +27,7 @@ export default class EventsDispatcher { * @param {string} eventName - event name * @param {Function} callback - subscriber */ - public on(eventName: string, callback: (data: object) => object): void { + public on(eventName: Events, callback: (data: object) => object): void { if (!(eventName in this.subscribers)) { this.subscribers[eventName] = []; } @@ -40,7 +42,7 @@ export default class EventsDispatcher { * @param {string} eventName - event name * @param {Function} callback - subscriber */ - public once(eventName: string, callback: (data: object) => object): void { + public once(eventName: Events, callback: (data: object) => object): void { if (!(eventName in this.subscribers)) { this.subscribers[eventName] = []; } @@ -67,8 +69,8 @@ export default class EventsDispatcher { * @param {string} eventName - event name * @param {object} data - subscribers get this data when they were fired */ - public emit(eventName: string, data?: object): void { - if (!this.subscribers[eventName]) { + public emit(eventName: Events, data?: object): void { + if (isEmpty(this.subscribers) || !this.subscribers[eventName]) { return; } @@ -85,7 +87,7 @@ export default class EventsDispatcher { * @param {string} eventName - event name * @param {Function} callback - event handler */ - public off(eventName: string, callback: (data: object) => object): void { + public off(eventName: Events, callback: (data: object) => object): void { for (let i = 0; i < this.subscribers[eventName].length; i++) { if (this.subscribers[eventName][i] === callback) { delete this.subscribers[eventName][i]; diff --git a/src/components/modules/notifier.ts b/src/components/utils/notifier.ts similarity index 83% rename from src/components/modules/notifier.ts rename to src/components/utils/notifier.ts index 7562a0f1..9ba64bf0 100644 --- a/src/components/modules/notifier.ts +++ b/src/components/utils/notifier.ts @@ -1,5 +1,3 @@ -import Module from '../__module'; - /** * Use external package module for notifications * @@ -8,9 +6,9 @@ import Module from '../__module'; import notifier, { ConfirmNotifierOptions, NotifierOptions, PromptNotifierOptions } from 'codex-notifier'; /** - * Notifier module + * Util for showing notifications */ -export default class Notifier extends Module { +export default class Notifier { /** * Show web notification * diff --git a/src/components/utils/sanitizer.ts b/src/components/utils/sanitizer.ts new file mode 100644 index 00000000..5b4bc8a2 --- /dev/null +++ b/src/components/utils/sanitizer.ts @@ -0,0 +1,188 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ +/** + * CodeX Sanitizer + * + * Clears HTML from taint tags + * + * @version 2.0.0 + * + * @example + * + * clean(yourTaintString, yourConfig); + * + * {@link SanitizerConfig} + */ + +import * as _ from '../utils'; + +/** + * @typedef {object} SanitizerConfig + * @property {object} tags - define tags restrictions + * + * @example + * + * tags : { + * p: true, + * a: { + * href: true, + * rel: "nofollow", + * target: "_blank" + * } + * } + */ + +import HTMLJanitor from 'html-janitor'; +import { BlockToolData, SanitizerConfig } from '../../../types'; +import { SavedData } from '../../../types/data-formats'; + +/** + * Sanitize Blocks + * + * Enumerate blocks and clean data + * + * @param blocksData - blocks' data to sanitize + * @param sanitizeConfig — sanitize config to use or function to get config for Tool + */ +export function sanitizeBlocks( + blocksData: Array>, + sanitizeConfig: SanitizerConfig | ((toolName: string) => SanitizerConfig) +): Array> { + return blocksData.map((block) => { + const toolConfig = _.isFunction(sanitizeConfig) ? sanitizeConfig(block.tool) : sanitizeConfig; + + if (_.isEmpty(toolConfig)) { + return block; + } + + block.data = deepSanitize(block.data, toolConfig) as BlockToolData; + + return block; + }); +} +/** + * Cleans string from unwanted tags + * Method allows to use default config + * + * @param {string} taintString - taint string + * @param {SanitizerConfig} customConfig - allowed tags + * + * @returns {string} clean HTML + */ +export function clean(taintString: string, customConfig: SanitizerConfig = {} as SanitizerConfig): string { + const sanitizerConfig = { + tags: customConfig, + }; + + /** + * API client can use custom config to manage sanitize process + */ + const sanitizerInstance = new HTMLJanitor(sanitizerConfig); + + return sanitizerInstance.clean(taintString); +} + +/** + * Method recursively reduces Block's data and cleans with passed rules + * + * @param {BlockToolData|object|*} dataToSanitize - taint string or object/array that contains taint string + * @param {SanitizerConfig} rules - object with sanitizer rules + */ +function deepSanitize(dataToSanitize: object | string, rules: SanitizerConfig): object | string { + /** + * BlockData It may contain 3 types: + * - Array + * - Object + * - Primitive + */ + if (Array.isArray(dataToSanitize)) { + /** + * Array: call sanitize for each item + */ + return cleanArray(dataToSanitize, rules); + } else if (_.isObject(dataToSanitize)) { + /** + * Objects: just clean object deeper. + */ + return cleanObject(dataToSanitize, rules); + } else { + /** + * Primitives (number|string|boolean): clean this item + * + * Clean only strings + */ + if (_.isString(dataToSanitize)) { + return cleanOneItem(dataToSanitize, rules); + } + + return dataToSanitize; + } +} + +/** + * Clean array + * + * @param {Array} array - [1, 2, {}, []] + * @param {SanitizerConfig} ruleForItem - sanitizer config for array + */ +function cleanArray(array: Array, ruleForItem: SanitizerConfig): Array { + return array.map((arrayItem) => deepSanitize(arrayItem, ruleForItem)); +} + +/** + * Clean object + * + * @param {object} object - {level: 0, text: 'adada', items: [1,2,3]}} + * @param {object} rules - { b: true } or true|false + * @returns {object} + */ +function cleanObject(object: object, rules: SanitizerConfig|{[field: string]: SanitizerConfig}): object { + const cleanData = {}; + + for (const fieldName in object) { + if (!Object.prototype.hasOwnProperty.call(object, fieldName)) { + continue; + } + + const currentIterationItem = object[fieldName]; + + /** + * Get object from config by field name + * - if it is a HTML Janitor rule, call with this rule + * - otherwise, call with parent's config + */ + const ruleForItem = isRule(rules[fieldName] as SanitizerConfig) ? rules[fieldName] : rules; + + cleanData[fieldName] = deepSanitize(currentIterationItem, ruleForItem as SanitizerConfig); + } + + return cleanData; +} + +/** + * Clean primitive value + * + * @param {string} taintString - string to clean + * @param {SanitizerConfig|boolean} rule - sanitizer rule + * + * @returns {string} + */ +function cleanOneItem(taintString: string, rule: SanitizerConfig|boolean): string { + if (_.isObject(rule)) { + return clean(taintString, rule); + } else if (rule === false) { + return clean(taintString, {} as SanitizerConfig); + } else { + return taintString; + } +} + +/** + * Check if passed item is a HTML Janitor rule: + * { a : true }, {}, false, true, function(){} — correct rules + * undefined, null, 0, 1, 2 — not a rules + * + * @param {SanitizerConfig} config - config to check + */ +function isRule(config: SanitizerConfig): boolean { + return _.isObject(config) || _.isBoolean(config) || _.isFunction(config); +} diff --git a/src/components/modules/tooltip.ts b/src/components/utils/tooltip.ts similarity index 91% rename from src/components/modules/tooltip.ts rename to src/components/utils/tooltip.ts index f3ff6711..a0ace016 100644 --- a/src/components/modules/tooltip.ts +++ b/src/components/utils/tooltip.ts @@ -1,6 +1,4 @@ /* eslint-disable jsdoc/no-undefined-types */ -import Module from '../__module'; - /** * Use external module CodeX Tooltip */ @@ -11,7 +9,7 @@ import CodeXTooltips, { TooltipContent, TooltipOptions } from 'codex-tooltip'; * * Decorates any tooltip module like adapter */ -export default class Tooltip extends Module { +export default class Tooltip { /** * Tooltips lib: CodeX Tooltips * @@ -19,6 +17,13 @@ export default class Tooltip extends Module { */ private lib: CodeXTooltips = new CodeXTooltips(); + /** + * Release the library + */ + public destroy(): void { + this.lib.destroy(); + } + /** * Shows tooltip on element with passed HTML content * diff --git a/src/styles/inline-toolbar.css b/src/styles/inline-toolbar.css index 2a0837a7..b118df87 100644 --- a/src/styles/inline-toolbar.css +++ b/src/styles/inline-toolbar.css @@ -7,6 +7,7 @@ will-change: transform, opacity; top: 0; left: 0; + z-index: 3; &--showed { opacity: 1; diff --git a/src/components/tools/paragraph b/src/tools/paragraph similarity index 100% rename from src/components/tools/paragraph rename to src/tools/paragraph diff --git a/src/components/tools/stub/index.ts b/src/tools/stub/index.ts similarity index 94% rename from src/components/tools/stub/index.ts rename to src/tools/stub/index.ts index 38f98b15..025066c6 100644 --- a/src/components/tools/stub/index.ts +++ b/src/tools/stub/index.ts @@ -1,5 +1,5 @@ -import $ from '../../dom'; -import { API, BlockTool, BlockToolData, BlockToolConstructorOptions } from '../../../../types'; +import $ from '../../components/dom'; +import { API, BlockTool, BlockToolConstructorOptions, BlockToolData } from '../../../types'; export interface StubData extends BlockToolData { title: string; diff --git a/src/types-internal/editor-modules.d.ts b/src/types-internal/editor-modules.d.ts index 14ad7e30..95a255cb 100644 --- a/src/types-internal/editor-modules.d.ts +++ b/src/types-internal/editor-modules.d.ts @@ -5,12 +5,8 @@ import InlineToolbar from '../components/modules/toolbar/inline'; import Toolbox from '../components/modules/toolbar/toolbox'; import BlockSettings from '../components/modules/toolbar/blockSettings'; import Paste from '../components/modules/paste'; -import Notifier from '../components/modules/notifier'; -import Tooltip from '../components/modules/tooltip'; import DragNDrop from '../components/modules/dragNDrop'; -import ModificationsObserver from '../components/modules/modificationsObserver'; import Renderer from '../components/modules/renderer'; -import Sanitizer from '../components/modules/sanitizer'; import Tools from '../components/modules/tools'; import API from '../components/modules/api/index'; import Caret from '../components/modules/caret'; @@ -35,6 +31,7 @@ import TooltipAPI from '../components/modules/api/tooltip'; import ReadOnly from '../components/modules/readonly'; import ReadOnlyAPI from '../components/modules/api/readonly'; import I18nAPI from '../components/modules/api/i18n'; +import ModificationsObserver from '../components/modules/modificationsObserver'; export interface EditorModules { UI: UI; @@ -48,15 +45,11 @@ export interface EditorModules { ConversionToolbar: ConversionToolbar; Paste: Paste; DragNDrop: DragNDrop; - ModificationsObserver: ModificationsObserver; Renderer: Renderer; - Sanitizer: Sanitizer; Tools: Tools; API: API; Caret: Caret; Saver: Saver; - Notifier: Notifier; - Tooltip: Tooltip; BlockManager: BlockManager; BlocksAPI: BlocksAPI; CaretAPI: CaretAPI; @@ -74,4 +67,5 @@ export interface EditorModules { ReadOnly: ReadOnly; ReadOnlyAPI: ReadOnlyAPI; I18nAPI: I18nAPI; + ModificationsObserver: ModificationsObserver; } diff --git a/test/cypress/.eslintrc b/test/cypress/.eslintrc index 9318c8d8..8b90cf86 100644 --- a/test/cypress/.eslintrc +++ b/test/cypress/.eslintrc @@ -1,12 +1,14 @@ { "plugins": [ - "cypress" + "cypress", + "chai-friendly" ], "env": { "cypress/globals": true }, "extends": [ - "plugin:cypress/recommended" + "plugin:cypress/recommended", + "plugin:chai-friendly/recommended" ], "rules": { "cypress/require-data-selectors": 2 @@ -14,4 +16,4 @@ "globals": { "EditorJS": true } -} \ No newline at end of file +} diff --git a/test/cypress/plugins/index.ts b/test/cypress/plugins/index.ts index 11c2d42f..e3a3c4e3 100644 --- a/test/cypress/plugins/index.ts +++ b/test/cypress/plugins/index.ts @@ -1,5 +1,39 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* tslint:disable:no-var-requires */ /** * This file contains connection of Cypres plugins */ -// eslint-disable-next-line @typescript-eslint/no-empty-function -// export default function(on, config): void {} +const webpackConfig = require('../../../webpack.config.js'); +const preprocessor = require('@cypress/webpack-preprocessor'); +const codeCoverageTask = require('@cypress/code-coverage/task'); + +module.exports = (on, config): unknown => { + /** + * Add Cypress task to get code coverage + */ + codeCoverageTask(on, config); + + /** + * Prepare webpack preprocessor options + */ + const options = preprocessor.defaultOptions; + + /** + * Provide path to typescript package + */ + options.typescript = require.resolve('typescript'); + + /** + * Provide our webpack config + */ + options.webpackOptions = webpackConfig({}, { mode: 'test' }); + + /** + * Register webpack preprocessor + */ + on('file:preprocessor', preprocessor(options)); + + // It's IMPORTANT to return the config object + // with any changed environment variables + return config; +}; diff --git a/test/cypress/support/commands.ts b/test/cypress/support/commands.ts index e23e8431..b2a24276 100644 --- a/test/cypress/support/commands.ts +++ b/test/cypress/support/commands.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /** * This file contains custom commands for Cypress. * Also it can override the existing commands. @@ -5,7 +6,7 @@ * -------------------------------------------------- */ -import type { EditorConfig } from './../../../types/index'; +import type { EditorConfig, OutputData } from './../../../types/index'; import type EditorJS from '../../../types/index'; import Chainable = Cypress.Chainable; @@ -36,3 +37,92 @@ Cypress.Commands.add('createEditor', (editorConfig: EditorConfig = {}): Chainabl }); }); }); + +/** + * Paste command to dispatch paste event + * + * Usage + * cy.get('div').paste({'text/plain': 'Text', 'text/html': 'Text'}) + * + * @param data - map with MIME type as a key and data as value + */ +Cypress.Commands.add('paste', { + prevSubject: true, +}, (subject, data: {[type: string]: string}) => { + const pasteEvent = Object.assign(new Event('paste', { + bubbles: true, + cancelable: true, + }), { + clipboardData: { + getData: (type): string => data[type], + types: Object.keys(data), + }, + }); + + subject[0].dispatchEvent(pasteEvent); + + return subject; +}); + +/** + * Copy command to dispatch copy event on subject + * + * Usage: + * cy.get('div').copy().then(data => {}) + */ +Cypress.Commands.add('copy', { prevSubject: true }, async (subject) => { + const clipboardData: {[type: string]: any} = {}; + + const copyEvent = Object.assign(new Event('copy', { + bubbles: true, + cancelable: true, + }), { + clipboardData: { + setData: (type: string, data: any): void => { + console.log(type, data); + clipboardData[type] = data; + }, + }, + }); + + subject[0].dispatchEvent(copyEvent); + + return clipboardData; +}); + +/** + * Cut command to dispatch cut event on subject + * + * Usage: + * cy.get('div').cut().then(data => {}) + */ +Cypress.Commands.add('cut', { prevSubject: true }, async (subject) => { + const clipboardData: {[type: string]: any} = {}; + + const copyEvent = Object.assign(new Event('cut', { + bubbles: true, + cancelable: true, + }), { + clipboardData: { + setData: (type: string, data: any): void => { + console.log(type, data); + clipboardData[type] = data; + }, + }, + }); + + subject[0].dispatchEvent(copyEvent); + + return clipboardData; +}); + +/** + * Calls EditorJS API render method + * + * @param data — data to render + */ +Cypress.Commands.add('render', { prevSubject: true }, async (subject: EditorJS, data: OutputData): Promise => { + await subject.render(data); + + return subject; +}); diff --git a/test/cypress/support/index.d.ts b/test/cypress/support/index.d.ts index 5a9e8631..477e3e7d 100644 --- a/test/cypress/support/index.d.ts +++ b/test/cypress/support/index.d.ts @@ -2,7 +2,7 @@ // load type definitions that come with Cypress module /// -import type { EditorConfig } from './../../../types/index'; +import type { EditorConfig, OutputData } from './../../../types/index'; import type EditorJS from '../../../types/index' declare global { @@ -14,6 +14,39 @@ declare global { * @example cy.createEditor({}) */ createEditor(editorConfig: EditorConfig): Chainable + + /** + * Paste command to dispatch paste event + * + * @usage + * cy.get('div').paste({'text/plain': 'Text', 'text/html': 'Text'}) + * + * @param data - map with MIME type as a key and data as value + */ + paste(data: {[type: string]: string}): Chainable + + /** + * Copy command to dispatch copy event on subject + * + * @usage + * cy.get('div').copy().then(data => {}) + */ + copy(): Chainable<{ [type: string]: any }>; + + /** + * Cut command to dispatch cut event on subject + * + * @usage + * cy.get('div').cut().then(data => {}) + */ + cut(): Chainable<{ [type: string]: any }>; + + /** + * Calls EditorJS API render method + * + * @param data — data to render + */ + render(data: OutputData): Chainable; } interface ApplicationWindow { diff --git a/test/cypress/support/index.ts b/test/cypress/support/index.ts index 252bff1f..a023dab5 100644 --- a/test/cypress/support/index.ts +++ b/test/cypress/support/index.ts @@ -6,6 +6,8 @@ * behavior that modifies Cypress. */ +import '@cypress/code-coverage/support'; + /** * File with the helpful commands */ diff --git a/test/cypress/tests/api/blocks.spec.ts b/test/cypress/tests/api/blocks.spec.ts new file mode 100644 index 00000000..7eb74869 --- /dev/null +++ b/test/cypress/tests/api/blocks.spec.ts @@ -0,0 +1,120 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * There will be described test cases of 'blocks.*' API + */ +describe('api.blocks', () => { + const firstBlock = { + id: 'bwnFX5LoX7', + type: 'paragraph', + data: { + text: 'The first block content mock.', + }, + }; + const editorDataMock = { + blocks: [ + firstBlock, + ], + }; + + beforeEach(() => { + if (this && this.editorInstance) { + this.editorInstance.destroy(); + } else { + cy.createEditor({ + data: editorDataMock, + }).as('editorInstance'); + } + }); + + /** + * api.blocks.getById(id) + */ + describe('.getById()', () => { + /** + * Check that api.blocks.getByUd(id) returns the Block for existed id + */ + it('should return Block API for existed id', () => { + cy.get('@editorInstance').then(async (editor: any) => { + const block = editor.blocks.getById(firstBlock.id); + + expect(block).not.to.be.undefined; + expect(block.id).to.be.eq(firstBlock.id); + }); + }); + + /** + * Check that api.blocks.getByUd(id) returns null for the not-existed id + */ + it('should return null for not-existed id', () => { + cy.get('@editorInstance').then(async (editor: any) => { + expect(editor.blocks.getById('not-existed-id')).to.be.null; + }); + }); + }); + + /** + * api.blocks.update(id, newData) + */ + describe('.update()', () => { + /** + * Check if block is updated in DOM + */ + it('should update block in DOM', () => { + cy.get('@editorInstance').then(async (editor: any) => { + const idToUpdate = firstBlock.id; + const newBlockData = { + text: 'Updated text', + }; + + editor.blocks.update(idToUpdate, newBlockData); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .invoke('text') + .then(blockText => { + expect(blockText).to.be.eq(newBlockData.text); + }); + }); + }); + + /** + * Check if block's data is updated after saving + */ + it('should update block in saved data', () => { + cy.get('@editorInstance').then(async (editor: any) => { + const idToUpdate = firstBlock.id; + const newBlockData = { + text: 'Updated text', + }; + + editor.blocks.update(idToUpdate, newBlockData); + + const output = await (editor as any).save(); + const text = output.blocks[0].data.text; + + expect(text).to.be.eq(newBlockData.text); + }); + }); + + /** + * When incorrect id passed, editor should not update any block + */ + it('shouldn\'t update any block if not-existed id passed', () => { + cy.get('@editorInstance').then(async (editor: any) => { + const idToUpdate = 'wrong-id-123'; + const newBlockData = { + text: 'Updated text', + }; + + editor.blocks.update(idToUpdate, newBlockData); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .invoke('text') + .then(blockText => { + expect(blockText).to.be.eq(firstBlock.data.text); + }); + }); + }); + }); +}); diff --git a/test/cypress/tests/block-ids.spec.ts b/test/cypress/tests/block-ids.spec.ts new file mode 100644 index 00000000..4faccaec --- /dev/null +++ b/test/cypress/tests/block-ids.spec.ts @@ -0,0 +1,131 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import Header from '../../../example/tools/header'; +import { nanoid } from 'nanoid'; + +describe.only('Block ids', () => { + beforeEach(() => { + if (this && this.editorInstance) { + this.editorInstance.destroy(); + } else { + cy.createEditor({ + tools: { + header: Header, + }, + }).as('editorInstance'); + } + }); + + it('Should generate unique block ids for new blocks', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .type('First block ') + .type('{enter}') + .get('div.ce-block') + .last() + .type('Second block ') + .type('{enter}'); + + cy.get('[data-cy=editorjs]') + .get('div.ce-toolbar__plus') + .click(); + + cy.get('[data-cy=editorjs]') + .get('li.ce-toolbox__button[data-tool=header]') + .click(); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .last() + .click() + .type('Header'); + + cy.get('@editorInstance') + .then(async (editor: any) => { + const data = await editor.save(); + + data.blocks.forEach(block => { + expect(typeof block.id).to.eq('string'); + }); + }); + }); + + it('should preserve passed ids', () => { + const blocks = [ + { + id: nanoid(), + type: 'paragraph', + data: { + text: 'First block', + }, + }, + { + id: nanoid(), + type: 'paragraph', + data: { + text: 'Second block', + }, + }, + ]; + + cy.get('@editorInstance') + .render({ + blocks, + }); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .first() + .click() + .type('{movetoend} Some more text'); + + cy.get('@editorInstance') + .then(async (editor: any) => { + const data = await editor.save(); + + data.blocks.forEach((block, index) => { + expect(block.id).to.eq(blocks[index].id); + }); + }); + }); + + it('should preserve passed ids if blocks were added', () => { + const blocks = [ + { + id: nanoid(), + type: 'paragraph', + data: { + text: 'First block', + }, + }, + { + id: nanoid(), + type: 'paragraph', + data: { + text: 'Second block', + }, + }, + ]; + + cy.get('@editorInstance') + .render({ + blocks, + }); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .first() + .click() + .type('{enter}') + .next() + .type('Middle block'); + + cy.get('@editorInstance') + .then(async (editor: any) => { + const data = await editor.save(); + + expect(data.blocks[0].id).to.eq(blocks[0].id); + expect(data.blocks[2].id).to.eq(blocks[1].id); + }); + }); +}); diff --git a/test/cypress/tests/copy-paste.spec.ts b/test/cypress/tests/copy-paste.spec.ts new file mode 100644 index 00000000..5ec8e703 --- /dev/null +++ b/test/cypress/tests/copy-paste.spec.ts @@ -0,0 +1,277 @@ +import Header from '../../../example/tools/header'; +import Image from '../../../example/tools/simple-image'; +import * as _ from '../../../src/components/utils'; + +describe('Copy pasting from Editor', () => { + beforeEach(() => { + if (this && this.editorInstance) { + this.editorInstance.destroy(); + } else { + cy.createEditor({ + tools: { + header: Header, + image: Image, + }, + }).as('editorInstance'); + } + }); + + context('pasting', () => { + it('should paste plain text', () => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .paste({ + 'text/plain': 'Some plain text', + }) + .wait(0) + .should('contain', 'Some plain text'); + }); + + it('should paste inline html data', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .paste({ + 'text/html': '

Some text

', + }) + .should('contain.html', 'Some text'); + }); + + it('should paste several blocks if plain text contains new lines', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .paste({ + 'text/plain': 'First block\n\nSecond block', + }); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .then(blocks => { + expect(blocks[0].textContent).to.eq('First block'); + expect(blocks[1].textContent).to.eq('Second block'); + }); + }); + + it('should paste several blocks if html contains several paragraphs', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .paste({ + 'text/html': '

First block

Second block

', + }); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .then(blocks => { + expect(blocks[0].textContent).to.eq('First block'); + expect(blocks[1].textContent).to.eq('Second block'); + }); + }); + + it('should paste using custom data type', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .paste({ + 'application/x-editor-js': JSON.stringify([ + { + tool: 'paragraph', + data: { + text: 'First block', + }, + }, + { + tool: 'paragraph', + data: { + text: 'Second block', + }, + }, + ]), + }); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .then(blocks => { + expect(blocks[0].textContent).to.eq('First block'); + expect(blocks[1].textContent).to.eq('Second block'); + }); + }); + + it('should parse block tags', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .paste({ + 'text/html': '

First block

Second block

', + }); + + cy.get('[data-cy=editorjs]') + .get('h2.ce-header') + .should('contain', 'First block'); + + cy.get('[data-cy=editorjs]') + .get('div.ce-paragraph') + .should('contain', 'Second block'); + }); + + it('should parse pattern', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .paste({ + 'text/plain': 'https://codex.so/public/app/img/external/codex2x.png', + }); + + cy.get('[data-cy=editorjs]') + // In Edge test are performed slower, so we need to increase timeout to wait until image is loaded on the page + .get('img', { timeout: 10000 }) + .should('have.attr', 'src', 'https://codex.so/public/app/img/external/codex2x.png'); + }); + }); + + context('copying', () => { + it('should copy inline fragment', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .type('Some text{selectall}') + .copy() + .then(clipboardData => { + /** + * As no blocks selected, clipboard data will be empty as will be handled by browser + */ + expect(clipboardData).to.be.empty; + }); + }); + + it('should copy several blocks', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .type('First block{enter}'); + + cy.get('[data-cy=editorjs') + .get('div.ce-block') + .next() + .type('Second block') + .type('{movetostart}') + .trigger('keydown', { + shiftKey: true, + keyCode: _.keyCodes.UP, + }) + .copy() + .then(clipboardData => { + expect(clipboardData['text/html']).to.match(/

First block(
)?<\/p>

Second block(
)?<\/p>/); + expect(clipboardData['text/plain']).to.eq(`First block\n\nSecond block`); + + /** + * Need to wait for custom data as it is set asynchronously + */ + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(0).then(() => { + expect(clipboardData['application/x-editor-js']).not.to.be.undefined; + + const data = JSON.parse(clipboardData['application/x-editor-js']); + + expect(data[0].tool).to.eq('paragraph'); + expect(data[0].data.text).to.match(/First block(
)?/); + expect(data[1].tool).to.eq('paragraph'); + expect(data[1].data.text).to.match(/Second block(
)?/); + }); + }); + }); + }); + + context('cutting', () => { + it('should cut inline fragment', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .type('Some text{selectall}') + .cut() + .then(clipboardData => { + /** + * As no blocks selected, clipboard data will be empty as will be handled by browser + */ + expect(clipboardData).to.be.empty; + }); + }); + + it('should cut several blocks', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .type('First block{enter}'); + + cy.get('[data-cy=editorjs') + .get('div.ce-block') + .next() + .type('Second block') + .type('{movetostart}') + .trigger('keydown', { + shiftKey: true, + keyCode: _.keyCodes.UP, + }) + .cut() + .then(clipboardData => { + expect(clipboardData['text/html']).to.match(/

First block(
)?<\/p>

Second block(
)?<\/p>/); + expect(clipboardData['text/plain']).to.eq(`First block\n\nSecond block`); + + /** + * Need to wait for custom data as it is set asynchronously + */ + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(0).then(() => { + expect(clipboardData['application/x-editor-js']).not.to.be.undefined; + + const data = JSON.parse(clipboardData['application/x-editor-js']); + + expect(data[0].tool).to.eq('paragraph'); + expect(data[0].data.text).to.match(/First block(
)?/); + expect(data[1].tool).to.eq('paragraph'); + expect(data[1].data.text).to.match(/Second block(
)?/); + }); + }); + + cy.get('[data-cy=editorjs]') + .should('not.contain', 'First block') + .should('not.contain', 'Second block'); + }); + + it('should cut lots of blocks', () => { + const numberOfBlocks = 50; + + for (let i = 0; i < numberOfBlocks; i++) { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .last() + .click() + .type(`Block ${i}{enter}`); + } + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .first() + .click() + .type('{ctrl+A}') + .type('{ctrl+A}') + .cut() + .then((clipboardData) => { + /** + * Need to wait for custom data as it is set asynchronously + */ + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(0).then(() => { + expect(clipboardData['application/x-editor-js']).not.to.be.undefined; + + const data = JSON.parse(clipboardData['application/x-editor-js']); + + expect(data.length).to.eq(numberOfBlocks + 1); + }); + }); + }); + }); +}); diff --git a/test/cypress/tests/initialization.spec.ts b/test/cypress/tests/initialization.spec.ts index 67750c5f..ada89c62 100644 --- a/test/cypress/tests/initialization.spec.ts +++ b/test/cypress/tests/initialization.spec.ts @@ -9,7 +9,7 @@ describe('Editor basic initialization', () => { const editorConfig = {}; beforeEach(() => { - if (this.editorInstance) { + if (this && this.editorInstance) { this.editorInstance.destroy(); } else { cy.createEditor(editorConfig).as('editorInstance'); diff --git a/test/cypress/tests/modules/Tools.spec.ts b/test/cypress/tests/modules/Tools.spec.ts new file mode 100644 index 00000000..7e15a34f --- /dev/null +++ b/test/cypress/tests/modules/Tools.spec.ts @@ -0,0 +1,296 @@ +/* tslint:disable:max-classes-per-file */ +/* eslint-disable @typescript-eslint/ban-ts-ignore,@typescript-eslint/no-explicit-any,jsdoc/require-jsdoc */ +import Tools from '../../../../src/components/modules/tools'; +import { EditorConfig } from '../../../../types'; +import BlockTool from '../../../../src/components/tools/block'; + +describe('Tools module', () => { + const defaultConfig = { + tools: {}, + }; + + /** + * Construct Tools module for testing purposes + * + * @param config - Editor config + */ + function constructModule(config: EditorConfig = defaultConfig): Tools { + const module = new Tools({ + config, + eventsDispatcher: {}, + } as any); + + const APIMethods = { + // eslint-disable-next-line @typescript-eslint/no-empty-function + method(): void {}, + }; + + /** + * Module state should be Editor modules, so we mock required ones only + */ + module.state = { + API: { + getMethodsForTool(): typeof APIMethods { + return APIMethods; + }, + }, + } as any; + + return module; + } + + context('.prepare()', () => { + it('should return Promise resolved to void', async () => { + const module = constructModule(); + + let err; + + try { + await module.prepare(); + } catch (e) { + err = e; + } + + expect(err).to.be.undefined; + }); + + it('should throw an error if tools config is corrupted', async () => { + const module = constructModule({ + tools: { + // @ts-ignore + corruptedTool: 'value', + }, + }); + + let err; + + try { + await module.prepare(); + } catch (e) { + err = e; + } + + expect(err).to.be.instanceOf(Error); + }); + }); + + context('collection accessors', () => { + let module: Tools; + + beforeEach(async () => { + module = constructModule({ + defaultBlock: 'withoutPrepare', + tools: { + withSuccessfulPrepare: { + class: class { + // eslint-disable-next-line @typescript-eslint/no-empty-function + public static prepare(): void {} + } as any, + inlineToolbar: [ 'inlineTool2' ], + tunes: [ 'blockTune2' ], + }, + withFailedPrepare: class { + public static prepare(): void { + throw new Error(); + } + } as any, + withoutPrepare: { + class: class {} as any, + inlineToolbar: false, + tunes: false, + }, + blockTool: { + class: class {} as any, + inlineToolbar: true, + }, + blockToolWithoutSettings: class {} as any, + inlineTool: class { + public static isInline = true + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public render(): void {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public surround(): void {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public checkState(): void {} + } as any, + inlineTool2: class { + public static isInline = true + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public render(): void {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public surround(): void {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public checkState(): void {} + } as any, + /** + * This tool will be unavailable as it doesn't have required methods + */ + unavailableInlineTool: class { + public static isInline = true; + } as any, + blockTune: class { + public static isTune = true; + } as any, + blockTune2: class { + public static isTune = true; + } as any, + unavailableBlockTune: class { + public static isTune = true; + + public static prepare(): void { + throw new Error(); + } + } as any, + }, + inlineToolbar: ['inlineTool2', 'inlineTool'], + tunes: ['blockTune2', 'blockTune'], + }); + + await module.prepare(); + }); + + context('.available', () => { + it('should return Map instance', () => { + expect(module.available).to.be.instanceOf(Map); + }); + + it('should contain only ready to use Tools', () => { + expect(module.available.has('withSuccessfulPrepare')).to.be.true; + expect(module.available.has('withoutPrepare')).to.be.true; + expect(module.available.has('withFailedPrepare')).to.be.false; + expect(module.available.has('unavailableInlineTool')).to.be.false; + }); + }); + + context('.unavailable', () => { + it('should return Map instance', () => { + expect(module.unavailable).to.be.instanceOf(Map); + }); + + it('should contain only ready to use Tools', () => { + expect(module.unavailable.has('withSuccessfulPrepare')).to.be.false; + expect(module.unavailable.has('withoutPrepare')).to.be.false; + expect(module.unavailable.has('withFailedPrepare')).to.be.true; + expect(module.unavailable.has('unavailableInlineTool')).to.be.true; + }); + }); + + context('.inlineTools', () => { + it('should return Map instance', () => { + expect(module.inlineTools).to.be.instanceOf(Map); + }); + + it('should contain only available Inline Tools', () => { + expect(module.inlineTools.has('inlineTool')).to.be.true; + expect(module.inlineTools.has('unavailableInlineTool')).to.be.false; + expect(Array.from(module.inlineTools.values()).every(tool => tool.isInline())).to.be.true; + }); + }); + + context('.blockTools', () => { + it('should return Map instance', () => { + expect(module.blockTools).to.be.instanceOf(Map); + }); + + it('should contain only available Block Tools', () => { + expect(module.blockTools.has('withSuccessfulPrepare')).to.be.true; + expect(module.blockTools.has('withoutPrepare')).to.be.true; + expect(module.blockTools.has('withFailedPrepare')).to.be.false; + expect(Array.from(module.blockTools.values()).every(tool => tool.isBlock())).to.be.true; + }); + + it('Block Tools should contain default tunes if no settings is specified', () => { + const tool = module.blockTools.get('blockToolWithoutSettings'); + + expect(tool.tunes.has('delete')).to.be.true; + expect(tool.tunes.has('moveUp')).to.be.true; + expect(tool.tunes.has('moveDown')).to.be.true; + }); + + it('Block Tools should contain default tunes', () => { + const tool = module.blockTools.get('blockTool'); + + expect(tool.tunes.has('delete')).to.be.true; + expect(tool.tunes.has('moveUp')).to.be.true; + expect(tool.tunes.has('moveDown')).to.be.true; + }); + + it('Block Tools should contain tunes in correct order', () => { + let tool = module.blockTools.get('blockTool'); + + expect(tool.tunes.has('blockTune')).to.be.true; + expect(tool.tunes.has('blockTune2')).to.be.true; + expect(Array.from(tool.tunes.keys())).to.be.deep.eq(['blockTune2', 'blockTune', 'moveUp', 'delete', 'moveDown']); + + tool = module.blockTools.get('withSuccessfulPrepare'); + + expect(tool.tunes.has('blockTune')).to.be.false; + expect(tool.tunes.has('blockTune2')).to.be.true; + + tool = module.blockTools.get('withoutPrepare'); + + expect(tool.tunes.has('blockTune')).to.be.false; + expect(tool.tunes.has('blockTune2')).to.be.false; + }); + + it('Block Tools should contain inline tools in correct order', () => { + let tool = module.blockTools.get('blockTool'); + + expect(tool.inlineTools.has('inlineTool')).to.be.true; + expect(tool.inlineTools.has('inlineTool2')).to.be.true; + expect(Array.from(tool.inlineTools.keys())).to.be.deep.eq(['inlineTool2', 'inlineTool']); + + tool = module.blockTools.get('withSuccessfulPrepare'); + + expect(tool.inlineTools.has('inlineTool')).to.be.false; + expect(tool.inlineTools.has('inlineTool2')).to.be.true; + + tool = module.blockTools.get('withoutPrepare'); + + expect(tool.inlineTools.has('inlineTool')).to.be.false; + expect(tool.inlineTools.has('inlineTool2')).to.be.false; + }); + }); + + context('.blockTunes', () => { + it('should return Map instance', () => { + expect(module.blockTunes).to.be.instanceOf(Map); + }); + + it('should contain only available Block Tunes', () => { + expect(module.blockTunes.has('blockTune')).to.be.true; + expect(module.blockTunes.has('unavailableBlockTune')).to.be.false; + expect(Array.from(module.blockTunes.values()).every(tool => tool.isTune())).to.be.true; + }); + }); + + context('.internal', () => { + it('should return Map instance', () => { + expect(module.internal).to.be.instanceOf(Map); + }); + + it('should contain only internal tunes', () => { + expect(Array.from(module.internal.values()).every(tool => tool.isInternal)).to.be.true; + }); + }); + + context('.defaultTools', () => { + /** + * @todo add check if user provided default tool is not Block Tool + */ + it('should return BlockTool instance', () => { + expect(module.defaultTool).to.be.instanceOf(BlockTool); + }); + + it('should return default Tool', () => { + expect(module.defaultTool.isDefault).to.be.true; + }); + }); + }); +}); diff --git a/test/cypress/tests/onchange.spec.ts b/test/cypress/tests/onchange.spec.ts new file mode 100644 index 00000000..9e8bdaea --- /dev/null +++ b/test/cypress/tests/onchange.spec.ts @@ -0,0 +1,127 @@ +import Header from '../../../example/tools/header'; + +/** + * @todo Add checks that correct block API object is passed to onChange + * @todo Add cases for native inputs changes + */ +describe('onChange callback', () => { + const config = { + tools: { + header: Header, + }, + onChange: (): void => { + console.log('something changed'); + }, + }; + + beforeEach(() => { + if (this && this.editorInstance) { + this.editorInstance.destroy(); + } else { + cy.spy(config, 'onChange').as('onChange'); + + cy.createEditor(config).as('editorInstance'); + } + }); + + it('should fire onChange callback on block insertion', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .type('{enter}'); + + cy.get('@onChange').should('be.called'); + }); + + it('should fire onChange callback on typing into block', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .type('some text'); + + cy.get('@onChange').should('be.called'); + }); + + it('should fire onChange callback on block replacement', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click(); + + cy.get('[data-cy=editorjs]') + .get('div.ce-toolbar__plus') + .click(); + + cy.get('[data-cy=editorjs]') + .get('li.ce-toolbox__button[data-tool=header]') + .click(); + + cy.get('@onChange').should('be.calledWithMatch', Cypress.sinon.match.any, Cypress.sinon.match({ name: 'header' })); + }); + + it('should fire onChange callback on tune modifier', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click(); + + cy.get('[data-cy=editorjs]') + .get('div.ce-toolbar__plus') + .click(); + + cy.get('[data-cy=editorjs]') + .get('li.ce-toolbox__button[data-tool=header]') + .click(); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click(); + + cy.get('[data-cy=editorjs]') + .get('span.ce-toolbar__settings-btn') + .click(); + + cy.get('[data-cy=editorjs]') + .get('span.cdx-settings-button[data-level=1]') + .click(); + + cy.get('@onChange').should('be.calledWithMatch', Cypress.sinon.match.any, Cypress.sinon.match({ name: 'header' })); + }); + + it('should fire onChange callback when block is removed', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click(); + + cy.get('[data-cy=editorjs]') + .get('span.ce-toolbar__settings-btn') + .click(); + + cy.get('[data-cy=editorjs]') + .get('div.ce-settings__button--delete') + .click() + .click(); + + cy.get('@onChange').should('be.called'); + }); + + it('should fire onChange callback when block is moved', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .type('{enter}'); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .last() + .click(); + + cy.get('[data-cy=editorjs]') + .get('span.ce-toolbar__settings-btn') + .click(); + + cy.get('[data-cy=editorjs]') + .get('div.ce-tune-move-up') + .click(); + + cy.get('@onChange').should('be.called'); + }); +}); diff --git a/test/cypress/tests/sanitisation.spec.ts b/test/cypress/tests/sanitisation.spec.ts new file mode 100644 index 00000000..73326f69 --- /dev/null +++ b/test/cypress/tests/sanitisation.spec.ts @@ -0,0 +1,66 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +describe('Output sanitisation', () => { + beforeEach(() => { + if (this && this.editorInstance) { + this.editorInstance.destroy(); + } else { + cy.createEditor({}).as('editorInstance'); + } + }); + + context('Output should save inline formatting', () => { + it('should save initial formatting for paragraph', () => { + cy.createEditor({ + data: { + blocks: [ { + type: 'paragraph', + data: { text: 'Bold text' }, + } ], + }, + }).then(async editor => { + const output = await (editor as any).save(); + + const boldText = output.blocks[0].data.text; + + expect(boldText).to.eq('Bold text'); + }); + }); + + it('should save formatting for paragraph', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .type('This text should be bold.{selectall}'); + + cy.get('[data-cy=editorjs]') + .get('button.ce-inline-tool--bold') + .click(); + + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click(); + + cy.get('@editorInstance').then(async editorInstance => { + const output = await (editorInstance as any).save(); + + const text = output.blocks[0].data.text; + + expect(text).to.match(/This text should be bold\.(
)?<\/b>/); + }); + }); + + it('should save formatting for paragraph on paste', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .paste({ 'text/html': '

Text

Bold text

' }); + + cy.get('@editorInstance').then(async editorInstance => { + const output = await (editorInstance as any).save(); + + const boldText = output.blocks[1].data.text; + + expect(boldText).to.eq('Bold text'); + }); + }); + }); +}); diff --git a/test/cypress/tests/selection.spec.ts b/test/cypress/tests/selection.spec.ts new file mode 100644 index 00000000..a721e675 --- /dev/null +++ b/test/cypress/tests/selection.spec.ts @@ -0,0 +1,33 @@ +import * as _ from '../../../src/components/utils'; + +describe('Blocks selection', () => { + beforeEach(() => { + if (this && this.editorInstance) { + this.editorInstance.destroy(); + } else { + cy.createEditor({}).as('editorInstance'); + } + }); + + it('should remove block selection on click', () => { + cy.get('[data-cy=editorjs]') + .find('div.ce-block') + .click() + .type('First block{enter}'); + + cy.get('[data-cy=editorjs') + .find('div.ce-block') + .next() + .type('Second block') + .type('{movetostart}') + .trigger('keydown', { + shiftKey: true, + keyCode: _.keyCodes.UP, + }); + + cy.get('[data-cy=editorjs') + .click() + .find('div.ce-block') + .should('not.have.class', '.ce-block--selected'); + }); +}); diff --git a/test/cypress/tests/tools/BlockTool.spec.ts b/test/cypress/tests/tools/BlockTool.spec.ts new file mode 100644 index 00000000..114b820e --- /dev/null +++ b/test/cypress/tests/tools/BlockTool.spec.ts @@ -0,0 +1,475 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* tslint:disable:max-classes-per-file */ +import { BlockToolData, ToolSettings } from '../../../../types'; +import { ToolType } from '../../../../src/components/tools/base'; +import BlockTool from '../../../../src/components/tools/block'; +import InlineTool from '../../../../src/components/tools/inline'; +import ToolsCollection from '../../../../src/components/tools/collection'; + +describe('BlockTool', () => { + /** + * Mock for BlockTool constructor options + */ + const options = { + name: 'blockTool', + constructable: class { + public static sanitize = { + rule1: { + div: true, + }, + } + + public static toolbox = { + icon: 'Tool icon', + title: 'Tool title', + }; + + public static enableLineBreaks = true; + + public static pasteConfig = { + tags: [ 'div' ], + }; + + public static conversionConfig = { + import: 'import', + export: 'export', + }; + + public static isReadOnlySupported = true; + + public static reset; + public static prepare; + + public static shortcut = 'CTRL+N'; + + public data: BlockToolData; + public block: object; + public readonly: boolean; + public api: object; + public config: ToolSettings; + + /** + * + */ + constructor({ data, block, readOnly, api, config }) { + this.data = data; + this.block = block; + this.readonly = readOnly; + this.api = api; + this.config = config; + } + }, + config: { + config: { + option1: 'option1', + option2: 'option2', + }, + inlineToolbar: ['link', 'bold'], + tunes: ['anchor', 'favorites'], + shortcut: 'CMD+SHIFT+B', + toolbox: { + title: 'User Block Tool', + icon: 'User icon', + }, + }, + api: { + getMethodsForTool(): object { + return { + prop1: 'prop1', + prop2: 'prop2', + }; + }, + }, + isDefault: false, + isInternal: false, + defaultPlaceholder: 'Default placeholder', + }; + + it('.type should return ToolType.Block', () => { + const tool = new BlockTool(options as any); + + expect(tool.type).to.be.eq(ToolType.Block); + }); + + it('.name should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.name).to.be.eq(options.name); + }); + + it('.isDefault should return correct value', () => { + const tool1 = new BlockTool(options as any); + const tool2 = new BlockTool({ + ...options, + isDefault: true, + } as any); + + expect(tool1.isDefault).to.be.false; + expect(tool2.isDefault).to.be.true; + }); + + it('.isInternal should return correct value', () => { + const tool1 = new BlockTool(options as any); + const tool2 = new BlockTool({ + ...options, + isInternal: true, + } as any); + + expect(tool1.isInternal).to.be.false; + expect(tool2.isInternal).to.be.true; + }); + + context('.settings', () => { + it('should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.settings).to.be.deep.eq(options.config.config); + }); + + it('should add default placeholder if Tool is default', () => { + const tool = new BlockTool({ + ...options, + isDefault: true, + } as any); + + expect(tool.settings).to.have.property('placeholder').that.eq(options.defaultPlaceholder); + }); + }); + + context('.sanitizeConfig', () => { + it('should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.sanitizeConfig).to.be.deep.eq(options.constructable.sanitize); + }); + + it('should return composed config if there are enabled inline tools', () => { + const tool = new BlockTool(options as any); + + const inlineTool = new InlineTool({ + name: 'inlineTool', + constructable: class { + public static sanitize = { + b: true, + } + }, + api: {}, + config: {}, + } as any); + + tool.inlineTools = new ToolsCollection([ ['inlineTool', inlineTool] ]); + + const expected = options.constructable.sanitize; + + // tslint:disable-next-line:forin + for (const key in expected) { + expected[key] = { + ...expected[key], + b: true, + }; + } + + expect(tool.sanitizeConfig).to.be.deep.eq(expected); + }); + + it('should return inline tools config if block one is not set', () => { + const tool = new BlockTool({ + ...options, + constructable: class {}, + } as any); + + const inlineTool1 = new InlineTool({ + name: 'inlineTool', + constructable: class { + public static sanitize = { + b: true, + } + }, + api: {}, + config: {}, + } as any); + + const inlineTool2 = new InlineTool({ + name: 'inlineTool', + constructable: class { + public static sanitize = { + a: true, + } + }, + api: {}, + config: {}, + } as any); + + tool.inlineTools = new ToolsCollection([ ['inlineTool', inlineTool1], ['inlineTool2', inlineTool2] ]); + + expect(tool.sanitizeConfig).to.be.deep.eq(Object.assign( + {}, + inlineTool1.sanitizeConfig, + inlineTool2.sanitizeConfig + )); + }); + + it('should return empty object by default', () => { + const tool = new BlockTool({ + ...options, + constructable: class {}, + } as any); + + expect(tool.sanitizeConfig).to.be.deep.eq({}); + }); + }); + + it('.isBlock() should return true', () => { + const tool = new BlockTool(options as any); + + expect(tool.isBlock()).to.be.true; + }); + + it('.isInline() should return false', () => { + const tool = new BlockTool(options as any); + + expect(tool.isInline()).to.be.false; + }); + + it('.isTune() should return false', () => { + const tool = new BlockTool(options as any); + + expect(tool.isTune()).to.be.false; + }); + + it('.isReadOnlySupported should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.isReadOnlySupported).to.be.eq(options.constructable.isReadOnlySupported); + }); + + it('.isLineBreaksEnabled should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.isLineBreaksEnabled).to.be.eq(options.constructable.enableLineBreaks); + }); + + it('.conversionConfig should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.conversionConfig).to.be.deep.eq(options.constructable.conversionConfig); + }); + + it('.pasteConfig should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.pasteConfig).to.be.deep.eq(options.constructable.pasteConfig); + }); + + context('.enabledInlineTools', () => { + it('should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.enabledInlineTools).to.be.deep.eq(options.config.inlineToolbar); + }); + + it('should return false by default', () => { + const tool = new BlockTool({ + ...options, + config: { + ...options.config, + inlineToolbar: undefined, + }, + } as any); + + expect(tool.enabledInlineTools).to.be.false; + }); + }); + + it('.enabledBlockTunes should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.enabledBlockTunes).to.be.deep.eq(options.config.tunes); + }); + + context('.prepare()', () => { + it('should call Tool prepare method', () => { + options.constructable.prepare = cy.stub(); + const tool = new BlockTool(options as any); + + tool.prepare(); + + expect(options.constructable.prepare).to.have.been.calledWithMatch({ + toolName: tool.name, + config: tool.settings, + }); + }); + + it('should not fail if Tool prepare method is not exist', () => { + const tool = new BlockTool({ + ...options, + constructable: {}, + } as any); + + expect(tool.prepare).to.not.throw; + }); + }); + + context('.reset()', () => { + it('should call Tool reset method', () => { + options.constructable.reset = cy.stub(); + const tool = new BlockTool(options as any); + + tool.reset(); + + expect(options.constructable.reset).to.be.calledOnce; + }); + + it('should not fail if Tool reset method is not exist', () => { + const tool = new BlockTool({ + ...options, + constructable: {}, + } as any); + + expect(tool.reset).to.not.throw; + }); + }); + + context('.shortcut', () => { + it('should return user provided shortcut', () => { + const tool = new BlockTool(options as any); + + expect(tool.shortcut).to.be.eq(options.config.shortcut); + }); + + it('should return Tool provided shortcut if user one is not specified', () => { + const tool = new BlockTool({ + ...options, + config: { + ...options.config, + shortcut: undefined, + }, + } as any); + + expect(tool.shortcut).to.be.eq(options.constructable.shortcut); + }); + }); + + context('.toolbox', () => { + it('should return user provided toolbox config', () => { + const tool = new BlockTool(options as any); + + expect(tool.toolbox).to.be.deep.eq(options.config.toolbox); + }); + + it('should return Tool provided toolbox config if user one is not specified', () => { + const tool = new BlockTool({ + ...options, + config: { + ...options.config, + toolbox: undefined, + }, + } as any); + + expect(tool.toolbox).to.be.deep.eq(options.constructable.toolbox); + }); + + it('should merge Tool provided toolbox config and user one', () => { + const tool1 = new BlockTool({ + ...options, + config: { + ...options.config, + toolbox: { + title: options.config.toolbox.title, + }, + }, + } as any); + const tool2 = new BlockTool({ + ...options, + config: { + ...options.config, + toolbox: { + icon: options.config.toolbox.icon, + }, + }, + } as any); + + expect(tool1.toolbox).to.be.deep.eq(Object.assign({}, options.constructable.toolbox, { title: options.config.toolbox.title })); + expect(tool2.toolbox).to.be.deep.eq(Object.assign({}, options.constructable.toolbox, { icon: options.config.toolbox.icon })); + }); + + it('should return undefined if user specifies false as a value', () => { + const tool = new BlockTool({ + ...options, + config: { + ...options.config, + toolbox: false, + }, + } as any); + + expect(tool.toolbox).to.be.undefined; + }); + + it('should return undefined if Tool specifies false as a value', () => { + const tool = new BlockTool({ + ...options, + constructable: class { + public static toolbox = false + }, + } as any); + + expect(tool.toolbox).to.be.undefined; + }); + + it('should return undefined if Tool provides empty config', () => { + const tool = new BlockTool({ + ...options, + constructable: class { + public static toolbox = {} + }, + } as any); + + expect(tool.toolbox).to.be.undefined; + }); + }); + + context('.create()', () => { + const tool = new BlockTool(options as any); + const data = { text: 'text' }; + const blockAPI = { + // eslint-disable-next-line @typescript-eslint/no-empty-function + method(): void {}, + }; + + it('should return Tool instance', () => { + expect(tool.create(data, blockAPI as any, false)).to.be.instanceOf(options.constructable); + }); + + it('should return Tool instance with passed data', () => { + const instance = tool.create(data, blockAPI as any, false) as any; + + expect(instance.data).to.be.deep.eq(data); + }); + + it('should return Tool instance with passed BlockAPI object', () => { + const instance = tool.create(data, blockAPI as any, false) as any; + + expect(instance.block).to.be.deep.eq(blockAPI); + }); + + it('should return Tool instance with passed readOnly flag', () => { + const instance1 = tool.create(data, blockAPI as any, false) as any; + const instance2 = tool.create(data, blockAPI as any, true) as any; + + expect(instance1.readonly).to.be.eq(false); + expect(instance2.readonly).to.be.eq(true); + }); + + it('should return Tool instance with passed API object', () => { + const instance = tool.create(data, blockAPI as any, false) as any; + + expect(instance.api).to.be.deep.eq(options.api.getMethodsForTool()); + }); + + it('should return Tool instance with passed config', () => { + const instance = tool.create(data, blockAPI as any, false) as any; + + expect(instance.config).to.be.deep.eq(options.config.config); + }); + }); +}); diff --git a/test/cypress/tests/tools/BlockTune.spec.ts b/test/cypress/tests/tools/BlockTune.spec.ts new file mode 100644 index 00000000..89b47d85 --- /dev/null +++ b/test/cypress/tests/tools/BlockTune.spec.ts @@ -0,0 +1,179 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* tslint:disable:max-classes-per-file */ +import { ToolSettings } from '../../../../types'; +import { ToolType } from '../../../../src/components/tools/base'; +import BlockTune from '../../../../src/components/tools/tune'; +import { BlockTuneData } from '../../../../types/block-tunes/block-tune-data'; + +describe('BlockTune', () => { + /** + * Mock for BlockTune constructor options + */ + const options = { + name: 'blockTune', + constructable: class { + public static reset; + public static prepare; + + public api: object; + public config: ToolSettings; + public data: BlockTuneData; + public block: object; + + /** + * + */ + constructor({ api, config, block, data }) { + this.api = api; + this.config = config; + this.block = block; + this.data = data; + } + }, + config: { + config: { + option1: 'option1', + option2: 'option2', + }, + shortcut: 'CMD+SHIFT+B', + }, + api: { + getMethodsForTool(): object { + return { + prop1: 'prop1', + prop2: 'prop2', + }; + }, + }, + isDefault: false, + isInternal: false, + defaultPlaceholder: 'Default placeholder', + }; + + it('.type should return ToolType.Tune', () => { + const tool = new BlockTune(options as any); + + expect(tool.type).to.be.eq(ToolType.Tune); + }); + + it('.name should return correct value', () => { + const tool = new BlockTune(options as any); + + expect(tool.name).to.be.eq(options.name); + }); + + it('.isInternal should return correct value', () => { + const tool1 = new BlockTune(options as any); + const tool2 = new BlockTune({ + ...options, + isInternal: true, + } as any); + + expect(tool1.isInternal).to.be.false; + expect(tool2.isInternal).to.be.true; + }); + + it('.settings should return correct value', () => { + const tool = new BlockTune(options as any); + + expect(tool.settings).to.be.deep.eq(options.config.config); + }); + + it('.isBlock() should return false', () => { + const tool = new BlockTune(options as any); + + expect(tool.isBlock()).to.be.false; + }); + + it('.isInline() should return false', () => { + const tool = new BlockTune(options as any); + + expect(tool.isInline()).to.be.false; + }); + + it('.isTune() should return true', () => { + const tool = new BlockTune(options as any); + + expect(tool.isTune()).to.be.true; + }); + + context('.prepare()', () => { + it('should call Tool prepare method', () => { + options.constructable.prepare = cy.stub(); + const tool = new BlockTune(options as any); + + tool.prepare(); + + expect(options.constructable.prepare).to.have.been.calledWithMatch({ + toolName: tool.name, + config: tool.settings, + }); + }); + + it('should not fail if Tool prepare method is not exist', () => { + const tool = new BlockTune({ + ...options, + constructable: {}, + } as any); + + expect(tool.prepare).to.not.throw; + }); + }); + + context('.reset()', () => { + it('should call Tool reset method', () => { + options.constructable.reset = cy.stub(); + const tool = new BlockTune(options as any); + + tool.reset(); + + expect(options.constructable.reset).to.be.calledOnce; + }); + + it('should not fail if Tool reset method is not exist', () => { + const tool = new BlockTune({ + ...options, + constructable: {}, + } as any); + + expect(tool.reset).to.not.throw; + }); + }); + + context('.create()', () => { + const tool = new BlockTune(options as any); + const data = { text: 'text' }; + const blockAPI = { + // eslint-disable-next-line @typescript-eslint/no-empty-function + method(): void {}, + }; + + it('should return Tool instance', () => { + expect(tool.create(data, blockAPI as any)).to.be.instanceOf(options.constructable); + }); + + it('should return Tool instance with passed data', () => { + const instance = tool.create(data, blockAPI as any) as any; + + expect(instance.data).to.be.deep.eq(data); + }); + + it('should return Tool instance with passed BlockAPI object', () => { + const instance = tool.create(data, blockAPI as any) as any; + + expect(instance.block).to.be.deep.eq(blockAPI); + }); + + it('should return Tool instance with passed API object', () => { + const instance = tool.create(data, blockAPI as any) as any; + + expect(instance.api).to.be.deep.eq(options.api.getMethodsForTool()); + }); + + it('should return Tool instance with passed settings', () => { + const instance = tool.create(data, blockAPI as any) as any; + + expect(instance.config).to.be.deep.eq(options.config.config); + }); + }); +}); diff --git a/test/cypress/tests/tools/InlineTool.spec.ts b/test/cypress/tests/tools/InlineTool.spec.ts new file mode 100644 index 00000000..70bcbc37 --- /dev/null +++ b/test/cypress/tests/tools/InlineTool.spec.ts @@ -0,0 +1,197 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* tslint:disable:max-classes-per-file */ +import { ToolSettings } from '../../../../types'; +import { ToolType } from '../../../../src/components/tools/base'; +import InlineTool from '../../../../src/components/tools/inline'; + +describe('InlineTool', () => { + /** + * Mock for InlineTool constructor options + */ + const options = { + name: 'inlineTool', + constructable: class { + public static sanitize = { + rule1: 'rule1', + } + + public static title = 'Title' + + public static reset; + public static prepare; + + public static shortcut = 'CTRL+N'; + + public api: object; + public config: ToolSettings; + + /** + * + */ + constructor({ api, config }) { + this.api = api; + this.config = config; + } + }, + config: { + config: { + option1: 'option1', + option2: 'option2', + }, + shortcut: 'CMD+SHIFT+B', + }, + api: { + getMethodsForTool(): object { + return { + prop1: 'prop1', + prop2: 'prop2', + }; + }, + }, + isDefault: false, + isInternal: false, + defaultPlaceholder: 'Default placeholder', + }; + + it('.type should return ToolType.Inline', () => { + const tool = new InlineTool(options as any); + + expect(tool.type).to.be.eq(ToolType.Inline); + }); + + it('.name should return correct value', () => { + const tool = new InlineTool(options as any); + + expect(tool.name).to.be.eq(options.name); + }); + + it('.title should return correct title', () => { + const tool = new InlineTool(options as any); + + expect(tool.title).to.be.eq(options.constructable.title); + }); + + it('.isInternal should return correct value', () => { + const tool1 = new InlineTool(options as any); + const tool2 = new InlineTool({ + ...options, + isInternal: true, + } as any); + + expect(tool1.isInternal).to.be.false; + expect(tool2.isInternal).to.be.true; + }); + + it('.settings should return correct value', () => { + const tool = new InlineTool(options as any); + + expect(tool.settings).to.be.deep.eq(options.config.config); + }); + + it('.sanitizeConfig should return correct value', () => { + const tool = new InlineTool(options as any); + + expect(tool.sanitizeConfig).to.be.deep.eq(options.constructable.sanitize); + }); + + it('.isBlock() should return false', () => { + const tool = new InlineTool(options as any); + + expect(tool.isBlock()).to.be.false; + }); + + it('.isInline() should return true', () => { + const tool = new InlineTool(options as any); + + expect(tool.isInline()).to.be.true; + }); + + it('.isTune() should return false', () => { + const tool = new InlineTool(options as any); + + expect(tool.isTune()).to.be.false; + }); + + context('.prepare()', () => { + it('should call Tool prepare method', () => { + options.constructable.prepare = cy.stub(); + const tool = new InlineTool(options as any); + + tool.prepare(); + + expect(options.constructable.prepare).to.have.been.calledWithMatch({ + toolName: tool.name, + config: tool.settings, + }); + }); + + it('should not fail if Tool prepare method is not exist', () => { + const tool = new InlineTool({ + ...options, + constructable: {}, + } as any); + + expect(tool.prepare).to.not.throw; + }); + }); + + context('.reset()', () => { + it('should call Tool reset method', () => { + options.constructable.reset = cy.stub(); + const tool = new InlineTool(options as any); + + tool.reset(); + + expect(options.constructable.reset).to.be.calledOnce; + }); + + it('should not fail if Tool reset method is not exist', () => { + const tool = new InlineTool({ + ...options, + constructable: {}, + } as any); + + expect(tool.reset).to.not.throw; + }); + }); + + context('.shortcut', () => { + it('should return user provided shortcut', () => { + const tool = new InlineTool(options as any); + + expect(tool.shortcut).to.be.eq(options.config.shortcut); + }); + + it('should return Tool provided shortcut if user one is not specified', () => { + const tool = new InlineTool({ + ...options, + config: { + ...options.config, + shortcut: undefined, + }, + } as any); + + expect(tool.shortcut).to.be.eq(options.constructable.shortcut); + }); + }); + + context('.create()', () => { + const tool = new InlineTool(options as any); + + it('should return Tool instance', () => { + expect(tool.create()).to.be.instanceOf(options.constructable); + }); + + it('should return Tool instance with passed API object', () => { + const instance = tool.create() as any; + + expect(instance.api).to.be.deep.eq(options.api.getMethodsForTool()); + }); + + it('should return Tool instance with passed config', () => { + const instance = tool.create() as any; + + expect(instance.config).to.be.deep.eq(options.config.config); + }); + }); +}); diff --git a/test/cypress/tests/tools/ToolsCollection.spec.ts b/test/cypress/tests/tools/ToolsCollection.spec.ts new file mode 100644 index 00000000..12609324 --- /dev/null +++ b/test/cypress/tests/tools/ToolsCollection.spec.ts @@ -0,0 +1,186 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import ToolsCollection from '../../../../src/components/tools/collection'; +import BlockTool from '../../../../src/components/tools/block'; +import InlineTool from '../../../../src/components/tools/inline'; +import BlockTune from '../../../../src/components/tools/tune'; +import BaseTool from '../../../../src/components/tools/base'; + +const FakeTool = { + isBlock(): boolean { + return false; + }, + isInline(): boolean { + return false; + }, + isTune(): boolean { + return false; + }, + isInternal: false, +}; + +const FakeBlockTool = { + ...FakeTool, + isBlock(): boolean { + return true; + }, +}; + +const FakeInlineTool = { + ...FakeTool, + isInline(): boolean { + return true; + }, +}; + +const FakeBlockTune = { + ...FakeTool, + isTune(): boolean { + return true; + }, +}; + +/** + * Unit tests for ToolsCollection class + */ +describe('ToolsCollection', (): void => { + let collection; + + /** + * Mock for Tools in collection + */ + const fakeTools = [ + ['block1', FakeBlockTool], + ['inline1', FakeInlineTool], + ['block2', { + ...FakeBlockTool, + isInternal: true, + } ], + ['tune1', FakeBlockTune], + ['block3', FakeBlockTool], + ['inline2', { + ...FakeInlineTool, + isInternal: true, + } ], + ['tune2', FakeBlockTune], + ['tune3', { + ...FakeBlockTune, + isInternal: true, + } ], + ['block3', FakeInlineTool], + ['block4', FakeBlockTool], + ]; + + beforeEach((): void => { + collection = new ToolsCollection(fakeTools as any); + }); + + it('should be instance of Map', (): void => { + expect(collection instanceof Map).to.be.true; + }); + + context('.blockTools', (): void => { + it('should return new instance of ToolsCollection', (): void => { + expect(collection.blockTools instanceof ToolsCollection).to.be.true; + }); + + it('result should contain only block tools', (): void => { + expect( + Array + .from( + collection.blockTools.values() + ) + .every((tool: BlockTool) => tool.isBlock()) + ).to.be.true; + }); + }); + + context('.inlineTools', (): void => { + it('should return new instance of ToolsCollection', (): void => { + expect(collection.inlineTools instanceof ToolsCollection).to.be.true; + }); + + it('result should contain only inline tools', (): void => { + expect( + Array + .from( + collection.inlineTools.values() + ) + .every((tool: InlineTool) => tool.isInline()) + ).to.be.true; + }); + }); + + context('.blockTunes', (): void => { + it('should return new instance of ToolsCollection', (): void => { + expect(collection.blockTunes instanceof ToolsCollection).to.be.true; + }); + + it('result should contain only block tools', (): void => { + expect( + Array + .from( + collection.blockTunes.values() + ) + .every((tool: BlockTune) => tool.isTune()) + ).to.be.true; + }); + }); + + context('.internalTools', (): void => { + it('should return new instance of ToolsCollection', (): void => { + expect(collection.internalTools instanceof ToolsCollection).to.be.true; + }); + + it('result should contain only internal tools', (): void => { + expect( + Array + .from( + collection.internalTools.values() + ) + .every((tool: BaseTool) => tool.isInternal) + ).to.be.true; + }); + }); + + context('.externalTools', (): void => { + it('should return new instance of ToolsCollection', (): void => { + expect(collection.externalTools instanceof ToolsCollection).to.be.true; + }); + + it('result should contain only external tools', (): void => { + expect( + Array + .from( + collection.externalTools.values() + ) + .every((tool: BaseTool) => !tool.isInternal) + ).to.be.true; + }); + }); + + context('mixed access', (): void => { + context('.blockTunes.internalTools', (): void => { + it('should return only internal tunes', (): void => { + expect( + Array + .from( + collection.blockTunes.internalTools.values() + ) + .every((tool: BlockTune) => tool.isTune() && tool.isInternal) + ).to.be.true; + }); + }); + + context('.externalTools.blockTools', (): void => { + it('should return only external block tools', (): void => { + expect( + Array + .from( + collection.externalTools.blockTools.values() + ) + .every((tool: BlockTool) => tool.isBlock() && !tool.isInternal) + ).to.be.true; + }); + }); + }); +}); diff --git a/test/cypress/tests/tools/ToolsFactory.spec.ts b/test/cypress/tests/tools/ToolsFactory.spec.ts new file mode 100644 index 00000000..4aff4c41 --- /dev/null +++ b/test/cypress/tests/tools/ToolsFactory.spec.ts @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import LinkInlineTool from '../../../../src/components/inline-tools/inline-tool-link'; +import MoveUpTune from '../../../../src/components/block-tunes/block-tune-move-up'; +import ToolsFactory from '../../../../src/components/tools/factory'; +import InlineTool from '../../../../src/components/tools/inline'; +import BlockTool from '../../../../src/components/tools/block'; +import BlockTune from '../../../../src/components/tools/tune'; +import Paragraph from '../../../../src/tools/paragraph/dist/bundle'; + +describe('ToolsFactory', (): void => { + let factory; + const config = { + paragraph: { + class: Paragraph, + }, + link: { + class: LinkInlineTool, + }, + moveUp: { + class: MoveUpTune, + }, + }; + + beforeEach((): void => { + factory = new ToolsFactory( + config, + { + placeholder: 'Placeholder', + defaultBlock: 'paragraph', + } as any, + {} as any + ); + }); + + context('.get', (): void => { + it('should return appropriate tool object', (): void => { + const tool = factory.get('link'); + + expect(tool.name).to.be.eq('link'); + }); + + it('should return InlineTool object for inline tool', (): void => { + const tool = factory.get('link'); + + expect(tool instanceof InlineTool).to.be.true; + }); + + it('should return BlockTool object for block tool', (): void => { + const tool = factory.get('paragraph'); + + expect(tool instanceof BlockTool).to.be.true; + }); + + it('should return BlockTune object for tune', (): void => { + const tool = factory.get('moveUp'); + + expect(tool instanceof BlockTune).to.be.true; + }); + }); +}); diff --git a/test/cypress/tsconfig.json b/test/cypress/tsconfig.json index 86b00a57..a996c048 100644 --- a/test/cypress/tsconfig.json +++ b/test/cypress/tsconfig.json @@ -1,10 +1,13 @@ { "compilerOptions": { "target": "es2017", - "lib": ["es2017", "dom"], - "types": ["cypress"] + "lib": ["dom", "es2017", "es2018"], + "moduleResolution": "node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true }, "include": [ - "**/*.ts" + "../../**/*.ts" ] - } \ No newline at end of file + } diff --git a/test/testcases.md b/test/testcases.md index 00080a09..353c30be 100644 --- a/test/testcases.md +++ b/test/testcases.md @@ -53,13 +53,11 @@ This document will describe various test cases of the editor.js functionality. F - [ ] If omitted the Editor.js should be initialized with the default `sanitizer` configuration, which allows the tags like `paragraph`, `anchor`, and `bold` for cleaning HTML. - [ ] `tools` property - - [ ] If omitted - - [ ] Editor.js should be initialized with the Paragraph tool only. + - [ ] If omitted,the Editor.js should be initialized with the Paragraph tool only. - [ ] If `object` passed - [ ] Editor.js should be initialized with all the passed tools. - [ ] The keys of the object should be represented as `type` fields for corresponded blocks in output JSON - - [ ] If value is a JavaScript class - - [ ] This class should be used as a tool + - [ ] If value is a JavaScript class, the class should be used as a tool - [ ] If value is an `object` - [ ] Checking the `class` property - [ ] If omitted, the tool should be skipped with a warning in a console. @@ -69,14 +67,42 @@ This document will describe various test cases of the editor.js functionality. F - [ ] Checking the `shortcut` property - [ ] If `string` passed Editor.js should append the `tool` when such keys combination executed. - [ ] Checking the `inilineToolbar` property - - [ ] If `true` - - [ ] Editor.js should show the Inline Toolbar for this tool with [common](https://editorjs.io/configuration#inline-toolbar-order) settings. - - [ ] If `false` - - [ ] Editor.js should not show the Inline Toolbar for this tool. - - [ ] If `array` - - [ ] Editor.js should show the Inline Toolbar for this tool with a passed list of tools and their order. - - [ ] If omitted - - [ ] Editor.js should not show the Inline Toolbar for this tool. + - [ ] If `true` passed, the Editor.js should show the Inline Toolbar for this tool with [common](https://editorjs.io/configuration#inline-toolbar-order) settings. + - [ ] If `false` passed, the Editor.js should not show the Inline Toolbar for this tool. + - [ ] If `array` passed, the Editor.js should show the Inline Toolbar for this tool with a passed list of tools and their order. + - [ ] If omitted, the Editor.js should not show the Inline Toolbar for this tool. - [ ] Checking the `toolbox` property - [ ] If it contains `title`, this title should be used as a tool title - [ ] If it contains `icon`, this HTML code (maybe SVG) should be used as a tool icon + +- [ ] `onReady` property + - [ ] If `function` passed, the Editor.js should call the `function` when it's ready to work. + - [ ] If omitted, the Editor.js should be initialized with the `tools` only. + +- [ ] `onChange` property + - [ ] If `function` passed,the Editor.js should call the `function` when something changed in Editor.js DOM. + - [ ] If omitted, the Editor.js should be initialized with the `tools` only. + +- [ ] `data` property + - [ ] If omitted + - [ ] the Editor.js should be initialized with the `tools` only. + - [ ] the Editor.js should be empty. + - [ ] If `object` passed + - [ ] Checking the `blocks` property + - [ ] If `array` of `object` passed, + - [ ] for each `object` + - [ ] Checking the `type` and `data` property + - [ ] the Editor.js should be initialize with `block` of class `type` + - [ ] If `type` not present in `tools`, the Editor.js should throw an error. + - [ ] If omitted + - [ ] the Editor.js should be initialized with the `tools` only. + - [ ] the Editor.js should be empty. + +- [ ] `readOnly` property + - [ ] If `true` passed, + - [ ] If any `tool` have not readOnly getter defined,The Editor.js should throw an error. + - [ ] otherwise, the Editor.js should be initialize with readOnly mode. + - [ ] If `false` passed,the Editor.js should be initialized with the `tools` only. + - [ ] If omitted,the Editor.js should be initialized with the `tools` only. + +- [ ] `i18n` property \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index fd0aa5e1..c95c063b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "resolveJsonModule": true, // allows to omit export default in .json files - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true } } diff --git a/types/api/block.d.ts b/types/api/block.d.ts index cb3ca293..81ba721f 100644 --- a/types/api/block.d.ts +++ b/types/api/block.d.ts @@ -5,6 +5,11 @@ import {SavedData} from '../data-formats'; * @interface BlockAPI Describes Block API methods and properties */ export interface BlockAPI { + /** + * Block unique identifier + */ + readonly id: string; + /** * Tool name */ diff --git a/types/api/blocks.d.ts b/types/api/blocks.d.ts index b933a817..f5293158 100644 --- a/types/api/blocks.d.ts +++ b/types/api/blocks.d.ts @@ -54,6 +54,12 @@ export interface Blocks { */ getBlockByIndex(index: number): BlockAPI | void; + /** + * Returns Block API object by passed Block id + * @param id - id of the block + */ + getById(id: string): BlockAPI | null; + /** * Returns current Block index * @returns {number} @@ -99,4 +105,12 @@ export interface Blocks { needToFocus?: boolean, ): void; + + /** + * Updates block data by id + * + * @param id - id of the block to update + * @param data - the new data + */ + update(id: string, data: BlockToolData): void; } diff --git a/types/api/readonly.d.ts b/types/api/readonly.d.ts index a0bf4ca2..3766bf72 100644 --- a/types/api/readonly.d.ts +++ b/types/api/readonly.d.ts @@ -9,4 +9,9 @@ export interface ReadOnly { * @returns {Promise} current value */ toggle: (state?: boolean) => Promise; + + /** + * Contains current read-only state + */ + isEnabled: boolean; } diff --git a/types/block-tunes/block-tune-data.d.ts b/types/block-tunes/block-tune-data.d.ts new file mode 100644 index 00000000..d9c9805b --- /dev/null +++ b/types/block-tunes/block-tune-data.d.ts @@ -0,0 +1 @@ +export type BlockTuneData = any; diff --git a/types/block-tunes/block-tune.d.ts b/types/block-tunes/block-tune.d.ts index 17618a9b..f323fd82 100644 --- a/types/block-tunes/block-tune.d.ts +++ b/types/block-tunes/block-tune.d.ts @@ -1,4 +1,5 @@ -import {API, ToolConfig} from '../index'; +import {API, BlockAPI, SanitizerConfig, ToolConfig} from '../index'; +import { BlockTuneData } from './block-tune-data'; /** * Describes BLockTune blueprint @@ -10,11 +11,61 @@ export interface BlockTune { * @return {HTMLElement} */ render(): HTMLElement; + + /** + * Method called on Tool render. Pass Tool content as an argument. + * + * You can wrap Tool's content with any wrapper you want to provide Tune's UI + * + * @param {HTMLElement} pluginsContent — Tool's content wrapper + * + * @return {HTMLElement} + */ + wrap?(pluginsContent: HTMLElement): HTMLElement; + + /** + * Called on Tool's saving. Should return any data Tune needs to save + * + * @return {BlockTuneData} + */ + save?(): BlockTuneData; } /** * Describes BlockTune class constructor function */ export interface BlockTuneConstructable { - new (config: {api: API, settings?: ToolConfig}): BlockTune; + + /** + * Flag show Tool is Block Tune + */ + isTune: boolean; + + /** + * Tune's sanitize configuration + */ + sanitize?: SanitizerConfig; + + /** + * @constructor + * + * @param config - Block Tune config + */ + new(config: { + api: API, + config?: ToolConfig, + block: BlockAPI, + data: BlockTuneData, + }): BlockTune; + + /** + * Tune`s prepare method. Can be async + * @param data + */ + prepare?(): Promise | void; + + /** + * Tune`s reset method to clean up anything set by prepare. Can be async + */ + reset?(): void | Promise; } diff --git a/types/configs/editor-config.d.ts b/types/configs/editor-config.d.ts index a9353f57..4e93f6ce 100644 --- a/types/configs/editor-config.d.ts +++ b/types/configs/editor-config.d.ts @@ -1,5 +1,5 @@ import {ToolConstructable, ToolSettings} from '../tools'; -import {API, LogLevels, OutputData} from '../index'; +import {API, BlockAPI, LogLevels, OutputData} from '../index'; import {SanitizerConfig} from './sanitizer-config'; import {I18nConfig} from './i18n-config'; @@ -53,7 +53,9 @@ export interface EditorConfig { /** * Map of Tools to use */ - tools?: {[toolName: string]: ToolConstructable|ToolSettings}; + tools?: { + [toolName: string]: ToolConstructable|ToolSettings; + } /** * Data to render on Editor start @@ -88,11 +90,17 @@ export interface EditorConfig { /** * Fires when something changed in DOM * @param {API} api - editor.js api + * @param block - changed block API */ - onChange?(api: API): void; + onChange?(api: API, block: BlockAPI): void; /** * Defines default toolbar for all tools. */ inlineToolbar?: string[]|boolean; + + /** + * Common Block Tunes list. Will be added to all the blocks which do not specify their own 'tunes' set + */ + tunes?: string[]; } diff --git a/types/data-formats/block-data.d.ts b/types/data-formats/block-data.d.ts index 063b63b6..f4843a47 100644 --- a/types/data-formats/block-data.d.ts +++ b/types/data-formats/block-data.d.ts @@ -4,6 +4,7 @@ import {BlockToolData} from '../tools'; * Tool's saved data */ export interface SavedData { + id: string; tool: string; data: BlockToolData; time: number; @@ -13,6 +14,7 @@ export interface SavedData { * Tool's data after validation */ export interface ValidatedData { + id?: string; tool?: string; data?: BlockToolData; time?: number; diff --git a/types/data-formats/output-data.d.ts b/types/data-formats/output-data.d.ts index 1e2c0a64..07f296e1 100644 --- a/types/data-formats/output-data.d.ts +++ b/types/data-formats/output-data.d.ts @@ -1,4 +1,5 @@ import {BlockToolData} from '../tools'; +import {BlockTuneData} from '../block-tunes/block-tune-data'; /** * Output of one Tool @@ -7,6 +8,10 @@ import {BlockToolData} from '../tools'; * @template Data - the structure describing a data object supported by the tool */ export interface OutputBlockData { + /** + * Unique Id of the block + */ + id?: string; /** * Tool type */ @@ -15,6 +20,11 @@ export interface OutputBlockData; + + /** + * Block Tunes data + */ + tunes?: {[name: string]: BlockTuneData}; } export interface OutputData { diff --git a/types/tools/index.d.ts b/types/tools/index.d.ts index 4d43d407..b50dd40e 100644 --- a/types/tools/index.d.ts +++ b/types/tools/index.d.ts @@ -1,6 +1,6 @@ -import {BlockTool, BlockToolConstructable} from './block-tool'; -import {InlineTool, InlineToolConstructable} from './inline-tool'; -import {BaseTool, BaseToolConstructable} from './tool'; +import { BlockTool, BlockToolConstructable } from './block-tool'; +import { InlineTool, InlineToolConstructable } from './inline-tool'; +import { BlockTune, BlockTuneConstructable } from '../block-tunes'; export * from './block-tool'; export * from './block-tool-data'; @@ -11,5 +11,5 @@ export * from './tool-settings'; export * from './paste-events'; export * from './hook-events'; -export type Tool = BlockTool | InlineTool; -export type ToolConstructable = BlockToolConstructable | InlineToolConstructable; +export type Tool = BlockTool | InlineTool | BlockTune; +export type ToolConstructable = BlockToolConstructable | InlineToolConstructable | BlockTuneConstructable; diff --git a/types/tools/inline-tool.d.ts b/types/tools/inline-tool.d.ts index 705a1491..00b96e27 100644 --- a/types/tools/inline-tool.d.ts +++ b/types/tools/inline-tool.d.ts @@ -1,5 +1,5 @@ import {BaseTool, BaseToolConstructable} from './tool'; -import {API, ToolConfig} from "../index"; +import {API, ToolConfig} from '../index'; /** * Base structure for the Inline Toolbar Tool */ diff --git a/types/tools/tool-settings.d.ts b/types/tools/tool-settings.d.ts index 53b8f8c1..f093d969 100644 --- a/types/tools/tool-settings.d.ts +++ b/types/tools/tool-settings.d.ts @@ -21,7 +21,7 @@ export interface ToolboxConfig { * * @template Config - the structure describing a config object supported by the tool */ -export interface ToolSettings { +export interface ExternalToolSettings { /** * Tool's class @@ -39,6 +39,12 @@ export interface ToolSettings { */ inlineToolbar?: boolean | string[]; + /** + * BlockTunes for Tool + * Can accept array of tune names or boolean. + */ + tunes?: boolean | string[]; + /** * Define shortcut that will render Tool */ @@ -50,3 +56,13 @@ export interface ToolSettings { */ toolbox?: ToolboxConfig | false; } + +/** + * For internal Tools 'class' property is optional + */ +export type InternalToolSettings = Omit, 'class'> & Partial, 'class'>>; + +/** + * Union of external and internal Tools settings + */ +export type ToolSettings = InternalToolSettings | ExternalToolSettings; diff --git a/yarn.lock b/yarn.lock index 0a5b9f99..cbaba152 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8,6 +8,18 @@ dependencies: "@babel/highlight" "^7.8.3" +"@babel/code-frame@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + +"@babel/compat-data@^7.13.8": + version "7.13.11" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.11.tgz#9c8fe523c206979c9a81b1e12fe50c1254f1aa35" + integrity sha512-BwKEkO+2a67DcFeS3RLl0Z3Gs2OvdXewuWjc1Hfokhb5eQWP9YRYH1/+VrVZvql2CfjOiNGqSAFOYt4lsqTHzg== + "@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" @@ -16,6 +28,26 @@ invariant "^2.2.4" semver "^5.5.0" +"@babel/core@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a" + integrity sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.4.4" + "@babel/helpers" "^7.4.4" + "@babel/parser" "^7.4.5" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.5" + "@babel/types" "^7.4.4" + convert-source-map "^1.1.0" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.11" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + "@babel/core@>=7.9.0", "@babel/core@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" @@ -37,6 +69,37 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/core@^7.7.5": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.10.tgz#07de050bbd8193fcd8a3c27918c0890613a94559" + integrity sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.9" + "@babel/helper-compilation-targets" "^7.13.10" + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helpers" "^7.13.10" + "@babel/parser" "^7.13.10" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + lodash "^4.17.19" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/generator@^7.13.0", "@babel/generator@^7.13.9", "@babel/generator@^7.4.4": + version "7.13.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39" + integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== + dependencies: + "@babel/types" "^7.13.0" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/generator@^7.9.0", "@babel/generator@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" @@ -46,12 +109,27 @@ lodash "^4.17.13" source-map "^0.5.0" +"@babel/helper-annotate-as-pure@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" + integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw== + dependencies: + "@babel/types" "^7.12.13" + "@babel/helper-annotate-as-pure@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" dependencies: "@babel/types" "^7.8.3" +"@babel/helper-builder-binary-assignment-operator-visitor@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz#6bc20361c88b0a74d05137a65cac8d3cbf6f61fc" + integrity sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.12.13" + "@babel/types" "^7.12.13" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" @@ -59,6 +137,16 @@ "@babel/helper-explode-assignable-expression" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-compilation-targets@^7.13.10", "@babel/helper-compilation-targets@^7.13.8": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.10.tgz#1310a1678cb8427c07a753750da4f8ce442bdd0c" + integrity sha512-/Xju7Qg1GQO4mHZ/Kcs6Au7gfafgZnwm+a7sy/ow/tV1sHeraRUHbjdat8/UvDor4Tez+siGKDk6zIKtCPKVJA== + dependencies: + "@babel/compat-data" "^7.13.8" + "@babel/helper-validator-option" "^7.12.17" + browserslist "^4.14.5" + semver "^6.3.0" + "@babel/helper-compilation-targets@^7.8.7": version "7.8.7" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" @@ -69,6 +157,25 @@ levenary "^1.1.1" semver "^5.5.0" +"@babel/helper-create-class-features-plugin@^7.13.0", "@babel/helper-create-class-features-plugin@^7.3.0": + version "7.13.11" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz#30d30a005bca2c953f5653fc25091a492177f4f6" + integrity sha512-ays0I7XYq9xbjCSvT+EvysLgfc3tOkwCULHjrnscGT3A9qD4sk3wXnJ3of0MAWsWGjdinFvajHU2smYuqXKMrw== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-member-expression-to-functions" "^7.13.0" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/helper-replace-supers" "^7.13.0" + "@babel/helper-split-export-declaration" "^7.12.13" + +"@babel/helper-create-regexp-features-plugin@^7.12.13": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz#a2ac87e9e319269ac655b8d4415e94d38d663cb7" + integrity sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + regexpu-core "^4.7.1" + "@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": version "7.8.8" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" @@ -85,6 +192,13 @@ "@babel/types" "^7.8.3" lodash "^4.17.13" +"@babel/helper-explode-assignable-expression@^7.12.13": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz#17b5c59ff473d9f956f40ef570cf3a76ca12657f" + integrity sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA== + dependencies: + "@babel/types" "^7.13.0" + "@babel/helper-explode-assignable-expression@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" @@ -92,6 +206,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-function-name@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a" + integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA== + dependencies: + "@babel/helper-get-function-arity" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/types" "^7.12.13" + "@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" @@ -100,30 +223,74 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.9.5" +"@babel/helper-get-function-arity@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" + integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== + dependencies: + "@babel/types" "^7.12.13" + "@babel/helper-get-function-arity@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" dependencies: "@babel/types" "^7.8.3" +"@babel/helper-hoist-variables@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz#5d5882e855b5c5eda91e0cadc26c6e7a2c8593d8" + integrity sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g== + dependencies: + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + "@babel/helper-hoist-variables@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" dependencies: "@babel/types" "^7.8.3" +"@babel/helper-member-expression-to-functions@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz#6aa4bb678e0f8c22f58cdb79451d30494461b091" + integrity sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ== + dependencies: + "@babel/types" "^7.13.0" + "@babel/helper-member-expression-to-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" dependencies: "@babel/types" "^7.8.3" +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0" + integrity sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g== + dependencies: + "@babel/types" "^7.12.13" + "@babel/helper-module-imports@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" dependencies: "@babel/types" "^7.8.3" +"@babel/helper-module-transforms@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.0.tgz#42eb4bd8eea68bab46751212c357bfed8b40f6f1" + integrity sha512-Ls8/VBwH577+pw7Ku1QkUWIyRRNHpYlts7+qSqBBFCW3I8QteB9DxfcZ5YJpOwH6Ihe/wn8ch7fMGOP1OhEIvw== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-replace-supers" "^7.13.0" + "@babel/helper-simple-access" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/helper-validator-identifier" "^7.12.11" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + lodash "^4.17.19" + "@babel/helper-module-transforms@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" @@ -136,6 +303,13 @@ "@babel/types" "^7.9.0" lodash "^4.17.13" +"@babel/helper-optimise-call-expression@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" + integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA== + dependencies: + "@babel/types" "^7.12.13" + "@babel/helper-optimise-call-expression@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" @@ -146,12 +320,26 @@ version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" +"@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" + integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== + "@babel/helper-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" dependencies: lodash "^4.17.13" +"@babel/helper-remap-async-to-generator@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209" + integrity sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-wrap-function" "^7.13.0" + "@babel/types" "^7.13.0" + "@babel/helper-remap-async-to-generator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" @@ -162,6 +350,16 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz#6034b7b51943094cb41627848cb219cb02be1d24" + integrity sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.13.0" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + "@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" @@ -171,6 +369,13 @@ "@babel/traverse" "^7.8.6" "@babel/types" "^7.8.6" +"@babel/helper-simple-access@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz#8478bcc5cacf6aa1672b251c1d2dde5ccd61a6c4" + integrity sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA== + dependencies: + "@babel/types" "^7.12.13" + "@babel/helper-simple-access@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" @@ -178,16 +383,50 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-skip-transparent-expression-wrappers@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf" + integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-split-export-declaration@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" + integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== + dependencies: + "@babel/types" "^7.12.13" + "@babel/helper-split-export-declaration@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" dependencies: "@babel/types" "^7.8.3" +"@babel/helper-validator-identifier@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== + "@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" +"@babel/helper-validator-option@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" + integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw== + +"@babel/helper-wrap-function@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz#bdb5c66fda8526ec235ab894ad53a1235c79fcc4" + integrity sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + "@babel/helper-wrap-function@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" @@ -197,6 +436,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helpers@^7.13.10", "@babel/helpers@^7.4.4": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8" + integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ== + dependencies: + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + "@babel/helpers@^7.9.0": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" @@ -205,6 +453,15 @@ "@babel/traverse" "^7.9.0" "@babel/types" "^7.9.0" +"@babel/highlight@^7.12.13": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1" + integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/highlight@^7.8.3": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" @@ -213,10 +470,24 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@^7.12.13", "@babel/parser@^7.13.0", "@babel/parser@^7.13.10", "@babel/parser@^7.4.5": + version "7.13.11" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.11.tgz#f93ebfc99d21c1772afbbaa153f47e7ce2f50b88" + integrity sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q== + "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": version "7.9.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" +"@babel/plugin-proposal-async-generator-functions@^7.2.0": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.8.tgz#87aacb574b3bc4b5603f6fe41458d72a5a2ec4b1" + integrity sha512-rPBnhj+WgoSmgq+4gQUtXx/vOcU+UYtjy1AA/aeD61Hwj410fwYyqfUcRP3lR8ucgliVJL/G7sXcNUecC75IXA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-remap-async-to-generator" "^7.13.0" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" @@ -225,6 +496,14 @@ "@babel/helper-remap-async-to-generator" "^7.8.3" "@babel/plugin-syntax-async-generators" "^7.8.0" +"@babel/plugin-proposal-class-properties@7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz#272636bc0fa19a0bc46e601ec78136a173ea36cd" + integrity sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.3.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" @@ -232,6 +511,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-dynamic-import" "^7.8.0" +"@babel/plugin-proposal-json-strings@^7.2.0": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz#bf1fb362547075afda3634ed31571c5901afef7b" + integrity sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-proposal-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" @@ -253,6 +540,25 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-numeric-separator" "^7.8.3" +"@babel/plugin-proposal-object-rest-spread@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz#6d1859882d4d778578e41f82cc5d7bf3d5daf6c1" + integrity sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + +"@babel/plugin-proposal-object-rest-spread@^7.4.4": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz#5d210a4d727d6ce3b18f9de82cc99a3964eed60a" + integrity sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g== + dependencies: + "@babel/compat-data" "^7.13.8" + "@babel/helper-compilation-targets" "^7.13.8" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.13.0" + "@babel/plugin-proposal-object-rest-spread@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz#3fd65911306d8746014ec0d0cf78f0e39a149116" @@ -261,6 +567,14 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.9.5" +"@babel/plugin-proposal-optional-catch-binding@^7.2.0": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz#3ad6bd5901506ea996fc31bdcf3ccfa2bed71107" + integrity sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-catch-binding@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" @@ -282,7 +596,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.8.8" "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-async-generators@^7.8.0": +"@babel/plugin-syntax-async-generators@^7.2.0", "@babel/plugin-syntax-async-generators@^7.8.0", "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" dependencies: @@ -294,12 +608,19 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-json-strings@^7.8.0": +"@babel/plugin-syntax-json-strings@^7.2.0", "@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-jsx@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz#044fb81ebad6698fe62c478875575bcbb9b70f15" + integrity sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" @@ -312,13 +633,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-object-rest-spread@^7.8.0": +"@babel/plugin-syntax-object-rest-spread@^7.2.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-catch-binding@^7.8.0": +"@babel/plugin-syntax-optional-catch-binding@^7.2.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" dependencies: @@ -336,12 +657,35 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-syntax-typescript@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz#9dff111ca64154cef0f4dc52cf843d9f12ce4474" + integrity sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-arrow-functions@^7.2.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz#10a59bebad52d637a027afa692e8d5ceff5e3dae" + integrity sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-transform-arrow-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-async-to-generator@^7.4.4": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz#8e112bf6771b82bf1e974e5e26806c5c99aa516f" + integrity sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-remap-async-to-generator" "^7.13.0" + "@babel/plugin-transform-async-to-generator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" @@ -350,12 +694,26 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-remap-async-to-generator" "^7.8.3" +"@babel/plugin-transform-block-scoped-functions@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz#a9bf1836f2a39b4eb6cf09967739de29ea4bf4c4" + integrity sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-block-scoped-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-block-scoping@^7.4.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz#f36e55076d06f41dfd78557ea039c1b581642e61" + integrity sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-block-scoping@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" @@ -363,6 +721,19 @@ "@babel/helper-plugin-utils" "^7.8.3" lodash "^4.17.13" +"@babel/plugin-transform-classes@^7.4.4": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz#0265155075c42918bf4d3a4053134176ad9b533b" + integrity sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-replace-supers" "^7.13.0" + "@babel/helper-split-export-declaration" "^7.12.13" + globals "^11.1.0" + "@babel/plugin-transform-classes@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c" @@ -376,12 +747,26 @@ "@babel/helper-split-export-declaration" "^7.8.3" globals "^11.1.0" +"@babel/plugin-transform-computed-properties@^7.2.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz#845c6e8b9bb55376b1fa0b92ef0bdc8ea06644ed" + integrity sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-transform-computed-properties@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-destructuring@^7.4.4": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz#c5dce270014d4e1ebb1d806116694c12b7028963" + integrity sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-transform-destructuring@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50" @@ -395,12 +780,27 @@ "@babel/helper-create-regexp-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-duplicate-keys@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz#6f06b87a8b803fd928e54b81c258f0a0033904de" + integrity sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-duplicate-keys@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-exponentiation-operator@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz#4d52390b9a273e651e4aba6aee49ef40e80cd0a1" + integrity sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-exponentiation-operator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" @@ -408,12 +808,27 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-for-of@^7.4.4": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz#c799f881a8091ac26b54867a845c3e97d2696062" + integrity sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-transform-for-of@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-function-name@^7.4.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz#bb024452f9aaed861d374c8e7a24252ce3a50051" + integrity sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-function-name@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" @@ -421,18 +836,41 @@ "@babel/helper-function-name" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-literals@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz#2ca45bafe4a820197cf315794a4d26560fe4bdb9" + integrity sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-literals@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-member-expression-literals@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz#5ffa66cd59b9e191314c9f1f803b938e8c081e40" + integrity sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-member-expression-literals@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-modules-amd@^7.2.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz#19f511d60e3d8753cc5a6d4e775d3a5184866cc3" + integrity sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ== + dependencies: + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-amd@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" @@ -441,6 +879,16 @@ "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" +"@babel/plugin-transform-modules-commonjs@^7.4.4": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz#7b01ad7c2dcf2275b06fa1781e00d13d420b3e1b" + integrity sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw== + dependencies: + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-simple-access" "^7.12.13" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-commonjs@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" @@ -450,6 +898,17 @@ "@babel/helper-simple-access" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" +"@babel/plugin-transform-modules-systemjs@^7.4.4": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz#6d066ee2bff3c7b3d60bf28dec169ad993831ae3" + integrity sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A== + dependencies: + "@babel/helper-hoist-variables" "^7.13.0" + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-validator-identifier" "^7.12.11" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-systemjs@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" @@ -459,6 +918,14 @@ "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" +"@babel/plugin-transform-modules-umd@^7.2.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz#8a3d96a97d199705b9fd021580082af81c06e70b" + integrity sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw== + dependencies: + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-transform-modules-umd@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" @@ -466,18 +933,40 @@ "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-named-capturing-groups-regex@^7.4.5": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz#2213725a5f5bbbe364b50c3ba5998c9599c5c9d9" + integrity sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" dependencies: "@babel/helper-create-regexp-features-plugin" "^7.8.3" +"@babel/plugin-transform-new-target@^7.4.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz#e22d8c3af24b150dd528cbd6e685e799bf1c351c" + integrity sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-new-target@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-object-super@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz#b4416a2d63b8f7be314f3d349bd55a9c1b5171f7" + integrity sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-replace-supers" "^7.12.13" + "@babel/plugin-transform-object-super@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" @@ -485,6 +974,13 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-replace-supers" "^7.8.3" +"@babel/plugin-transform-parameters@^7.13.0", "@babel/plugin-transform-parameters@^7.4.4": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz#8fa7603e3097f9c0b7ca1a4821bc2fb52e9e5007" + integrity sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-transform-parameters@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795" @@ -492,24 +988,87 @@ "@babel/helper-get-function-arity" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-property-literals@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz#4e6a9e37864d8f1b3bc0e2dce7bf8857db8b1a81" + integrity sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-property-literals@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-react-display-name@^7.0.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.13.tgz#c28effd771b276f4647411c9733dbb2d2da954bd" + integrity sha512-MprESJzI9O5VnJZrL7gg1MpdqmiFcUv41Jc7SahxYsNP2kDkFqClxxTZq+1Qv4AFCamm+GXMRDQINNn+qrxmiA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-react-jsx-self@^7.0.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.13.tgz#422d99d122d592acab9c35ea22a6cfd9bf189f60" + integrity sha512-FXYw98TTJ125GVCCkFLZXlZ1qGcsYqNQhVBQcZjyrwf8FEUtVfKIoidnO8S0q+KBQpDYNTmiGo1gn67Vti04lQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-react-jsx-source@^7.0.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.13.tgz#051d76126bee5c9a6aa3ba37be2f6c1698856bcb" + integrity sha512-O5JJi6fyfih0WfDgIJXksSPhGP/G0fQpfxYy87sDc+1sFmsCS6wr3aAn+whbzkhbjtq4VMqLRaSzR6IsshIC0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.17.tgz#dd2c1299f5e26de584939892de3cfc1807a38f24" + integrity sha512-mwaVNcXV+l6qJOuRhpdTEj8sT/Z0owAVWf9QujTZ0d2ye9X/K+MTOTSizcgKOj18PGnTc/7g1I4+cIUjsKhBcw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-jsx" "^7.12.13" + "@babel/types" "^7.12.17" + +"@babel/plugin-transform-regenerator@^7.4.5": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz#b628bcc9c85260ac1aeb05b45bde25210194a2f5" + integrity sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA== + dependencies: + regenerator-transform "^0.14.2" + "@babel/plugin-transform-regenerator@^7.8.7": version "7.8.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" dependencies: regenerator-transform "^0.14.2" +"@babel/plugin-transform-reserved-words@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz#7d9988d4f06e0fe697ea1d9803188aa18b472695" + integrity sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-reserved-words@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-runtime@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.2.0.tgz#566bc43f7d0aedc880eaddbd29168d0f248966ea" + integrity sha512-jIgkljDdq4RYDnJyQsiWbdvGeei/0MOTtSHKO/rfbd/mXBxNpdlulMx49L0HQ4pug1fXannxoqCI+fYSle9eSw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + resolve "^1.8.1" + semver "^5.5.1" + "@babel/plugin-transform-runtime@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" @@ -519,18 +1078,40 @@ resolve "^1.8.1" semver "^5.5.1" +"@babel/plugin-transform-shorthand-properties@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz#db755732b70c539d504c6390d9ce90fe64aff7ad" + integrity sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-shorthand-properties@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-spread@^7.2.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz#84887710e273c1815ace7ae459f6f42a5d31d5fd" + integrity sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/plugin-transform-spread@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-sticky-regex@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz#760ffd936face73f860ae646fb86ee82f3d06d1f" + integrity sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-sticky-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" @@ -538,6 +1119,13 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-regex" "^7.8.3" +"@babel/plugin-transform-template-literals@^7.4.4": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz#a36049127977ad94438dee7443598d1cefdf409d" + integrity sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-transform-template-literals@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" @@ -545,12 +1133,36 @@ "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-typeof-symbol@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz#785dd67a1f2ea579d9c2be722de8c84cb85f5a7f" + integrity sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-typeof-symbol@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-typescript@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz#4a498e1f3600342d2a9e61f60131018f55774853" + integrity sha512-elQEwluzaU8R8dbVuW2Q2Y8Nznf7hnjM7+DSCd14Lo5fF63C9qNLbwZYbmZrtV9/ySpSUpkRpQXvJb6xyu4hCQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-typescript" "^7.12.13" + +"@babel/plugin-transform-unicode-regex@^7.4.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz#b52521685804e155b1202e83fc188d34bb70f5ac" + integrity sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-unicode-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" @@ -565,6 +1177,60 @@ core-js "^2.6.5" regenerator-runtime "^0.13.4" +"@babel/preset-env@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.5.tgz#2fad7f62983d5af563b5f3139242755884998a58" + integrity sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-json-strings" "^7.2.0" + "@babel/plugin-proposal-object-rest-spread" "^7.4.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-json-strings" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/plugin-transform-arrow-functions" "^7.2.0" + "@babel/plugin-transform-async-to-generator" "^7.4.4" + "@babel/plugin-transform-block-scoped-functions" "^7.2.0" + "@babel/plugin-transform-block-scoping" "^7.4.4" + "@babel/plugin-transform-classes" "^7.4.4" + "@babel/plugin-transform-computed-properties" "^7.2.0" + "@babel/plugin-transform-destructuring" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/plugin-transform-duplicate-keys" "^7.2.0" + "@babel/plugin-transform-exponentiation-operator" "^7.2.0" + "@babel/plugin-transform-for-of" "^7.4.4" + "@babel/plugin-transform-function-name" "^7.4.4" + "@babel/plugin-transform-literals" "^7.2.0" + "@babel/plugin-transform-member-expression-literals" "^7.2.0" + "@babel/plugin-transform-modules-amd" "^7.2.0" + "@babel/plugin-transform-modules-commonjs" "^7.4.4" + "@babel/plugin-transform-modules-systemjs" "^7.4.4" + "@babel/plugin-transform-modules-umd" "^7.2.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.5" + "@babel/plugin-transform-new-target" "^7.4.4" + "@babel/plugin-transform-object-super" "^7.2.0" + "@babel/plugin-transform-parameters" "^7.4.4" + "@babel/plugin-transform-property-literals" "^7.2.0" + "@babel/plugin-transform-regenerator" "^7.4.5" + "@babel/plugin-transform-reserved-words" "^7.2.0" + "@babel/plugin-transform-shorthand-properties" "^7.2.0" + "@babel/plugin-transform-spread" "^7.2.0" + "@babel/plugin-transform-sticky-regex" "^7.2.0" + "@babel/plugin-transform-template-literals" "^7.4.4" + "@babel/plugin-transform-typeof-symbol" "^7.2.0" + "@babel/plugin-transform-unicode-regex" "^7.4.4" + "@babel/types" "^7.4.4" + browserslist "^4.6.0" + core-js-compat "^3.1.1" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.5.0" + "@babel/preset-env@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.5.tgz#8ddc76039bc45b774b19e2fc548f6807d8a8919f" @@ -640,6 +1306,26 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" +"@babel/preset-react@7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0" + integrity sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + +"@babel/preset-typescript@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.13.0.tgz#ab107e5f050609d806fbb039bec553b33462c60a" + integrity sha512-LXJwxrHy0N3f6gIJlYbLta1D9BDtHpQeqwzM0LIfjDlr6UE/D5Mc7W4iDiQzaE+ks0sTjT26ArcHWnJVt0QiHw== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-validator-option" "^7.12.17" + "@babel/plugin-transform-typescript" "^7.13.0" + "@babel/register@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.9.0.tgz#02464ede57548bddbb5e9f705d263b7c3f43d48b" @@ -650,12 +1336,28 @@ pirates "^4.0.0" source-map-support "^0.5.16" +"@babel/runtime@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" + integrity sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA== + dependencies: + regenerator-runtime "^0.12.0" + "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" dependencies: regenerator-runtime "^0.13.4" +"@babel/template@^7.12.13", "@babel/template@^7.4.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" + integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/parser" "^7.12.13" + "@babel/types" "^7.12.13" + "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -664,6 +1366,21 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" +"@babel/traverse@^7.13.0", "@babel/traverse@^7.4.5": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc" + integrity sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.0" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.13.0" + "@babel/types" "^7.13.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" @@ -678,6 +1395,15 @@ globals "^11.1.0" lodash "^4.17.13" +"@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.12.17", "@babel/types@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80" + integrity sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" @@ -694,6 +1420,43 @@ version "1.4.0" resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" +"@cypress/browserify-preprocessor@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cypress/browserify-preprocessor/-/browserify-preprocessor-3.0.1.tgz#ab86335b0c061d11f5ad7df03f06b1877b836f71" + integrity sha512-sErmFSEr5287bLMRl0POGnyFtJCs/lSk5yxrUIJUIHZ8eDvtTEr0V93xRgLjJVG54gJU4MbpHy1mRPA9VZbtQA== + dependencies: + "@babel/core" "7.4.5" + "@babel/plugin-proposal-class-properties" "7.3.0" + "@babel/plugin-proposal-object-rest-spread" "7.3.2" + "@babel/plugin-transform-runtime" "7.2.0" + "@babel/preset-env" "7.4.5" + "@babel/preset-react" "7.0.0" + "@babel/runtime" "7.3.1" + babel-plugin-add-module-exports "1.0.2" + babelify "10.0.0" + bluebird "3.5.3" + browserify "16.2.3" + coffeeify "3.0.1" + coffeescript "1.12.7" + debug "4.1.1" + fs-extra "9.0.0" + lodash.clonedeep "4.5.0" + through2 "^2.0.0" + watchify "3.11.1" + +"@cypress/code-coverage@^3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@cypress/code-coverage/-/code-coverage-3.9.2.tgz#582cdb3a7858b3ecf294933b043d0bc236ce0bd9" + integrity sha512-YnzkRBxdsY/Ek/68nr+MowqW59UJsd28j10mFOerW/wrSkuxGrWvOldMs8Y4tU70L4fgd4wDPqGGMer3+UzbwA== + dependencies: + "@cypress/browserify-preprocessor" "3.0.1" + debug "4.3.1" + execa "4.1.0" + globby "11.0.2" + istanbul-lib-coverage "3.0.0" + js-yaml "3.14.1" + nyc "15.1.0" + "@cypress/listr-verbose-renderer@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" @@ -730,6 +1493,15 @@ tunnel-agent "^0.6.0" uuid "^3.3.2" +"@cypress/webpack-preprocessor@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.6.0.tgz#9648ae22d2e52f17a604e2a493af27a9c96568bd" + integrity sha512-kSelTDe6gs3Skp4vPP2vfTvAl+Ua+9rR/AMTir7bgJihDvzFESqnjWtF6N1TrPo+vCFVGx0VUA6JUvDkhvpwhA== + dependencies: + bluebird "^3.7.1" + debug "4.3.2" + lodash "^4.17.20" + "@cypress/xvfb@^1.2.4": version "1.2.4" resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" @@ -738,6 +1510,22 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -800,6 +1588,16 @@ version "13.13.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.2.tgz#160d82623610db590a64e8ca81784e11117e5a54" +"@types/node@12.12.50": + version "12.12.50" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.50.tgz#e9b2e85fafc15f2a8aa8fdd41091b983da5fd6ee" + integrity sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w== + +"@types/node@^14.14.35": + version "14.14.35" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313" + integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -902,6 +1700,11 @@ semver "^6.3.0" tsutils "^3.17.1" +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -1037,14 +1840,41 @@ version "4.2.2" resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" +JSONStream@^1.0.3: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + acorn-jsx@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" +acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2, acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + acorn@^6.4.1: version "6.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" +acorn@^7.0.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + acorn@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" @@ -1096,6 +1926,11 @@ alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-escapes@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -1136,6 +1971,13 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + ansi-styles@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" @@ -1155,6 +1997,21 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -1164,12 +2021,22 @@ arch@^2.1.2: resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1229,7 +2096,7 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assert@^1.1.1: +assert@^1.1.1, assert@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" dependencies: @@ -1309,7 +2176,7 @@ babel-loader@^8.1.0: pify "^4.0.1" schema-utils "^2.6.5" -babel-plugin-add-module-exports@^1.0.0: +babel-plugin-add-module-exports@1.0.2, babel-plugin-add-module-exports@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-1.0.2.tgz#96cd610d089af664f016467fc4567c099cce2d9c" optionalDependencies: @@ -1319,12 +2186,28 @@ babel-plugin-class-display-name@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-class-display-name/-/babel-plugin-class-display-name-2.1.0.tgz#198ff12b9eabd33e011ee13f2f9898985608b4d1" -babel-plugin-dynamic-import-node@^2.3.0: +babel-plugin-dynamic-import-node@^2.3.0, babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" dependencies: object.assign "^4.1.0" +babel-plugin-istanbul@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" + integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^4.0.0" + test-exclude "^6.0.0" + +babelify@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/babelify/-/babelify-10.0.0.tgz#fe73b1a22583f06680d8d072e25a1e0d1d1d7fb5" + integrity sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg== + bail@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" @@ -1337,6 +2220,11 @@ base64-js@^1.0.2: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -1364,6 +2252,11 @@ binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -1375,14 +2268,19 @@ blob-util@2.0.2: resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -bluebird@^3.5.5, bluebird@^3.7.2: +bluebird@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + +bluebird@^3.5.5, bluebird@^3.7.1, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" @@ -1410,17 +2308,48 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" dependencies: fill-range "^7.0.1" -brorand@^1.0.1: +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-pack@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.1.0.tgz#c34ba10d0b9ce162b5af227c7131c92c2ecd5774" + integrity sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA== + dependencies: + JSONStream "^1.0.3" + combine-source-map "~0.8.0" + defined "^1.0.0" + safe-buffer "^5.1.1" + through2 "^2.0.0" + umd "^3.0.0" + +browser-resolve@^1.11.0: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== + dependencies: + resolve "1.1.7" + +browser-resolve@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-2.0.0.tgz#99b7304cb392f8d73dba741bb2d7da28c6d7842b" + integrity sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ== + dependencies: + resolve "^1.17.0" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -1468,12 +2397,120 @@ browserify-sign@^4.0.0: inherits "^2.0.1" parse-asn1 "^5.0.0" -browserify-zlib@^0.2.0: +browserify-zlib@^0.2.0, browserify-zlib@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" dependencies: pako "~1.0.5" +browserify@16.2.3: + version "16.2.3" + resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.2.3.tgz#7ee6e654ba4f92bce6ab3599c3485b1cc7a0ad0b" + integrity sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ== + dependencies: + JSONStream "^1.0.3" + assert "^1.4.0" + browser-pack "^6.0.1" + browser-resolve "^1.11.0" + browserify-zlib "~0.2.0" + buffer "^5.0.2" + cached-path-relative "^1.0.0" + concat-stream "^1.6.0" + console-browserify "^1.1.0" + constants-browserify "~1.0.0" + crypto-browserify "^3.0.0" + defined "^1.0.0" + deps-sort "^2.0.0" + domain-browser "^1.2.0" + duplexer2 "~0.1.2" + events "^2.0.0" + glob "^7.1.0" + has "^1.0.0" + htmlescape "^1.1.0" + https-browserify "^1.0.0" + inherits "~2.0.1" + insert-module-globals "^7.0.0" + labeled-stream-splicer "^2.0.0" + mkdirp "^0.5.0" + module-deps "^6.0.0" + os-browserify "~0.3.0" + parents "^1.0.1" + path-browserify "~0.0.0" + process "~0.11.0" + punycode "^1.3.2" + querystring-es3 "~0.2.0" + read-only-stream "^2.0.0" + readable-stream "^2.0.2" + resolve "^1.1.4" + shasum "^1.0.0" + shell-quote "^1.6.1" + stream-browserify "^2.0.0" + stream-http "^2.0.0" + string_decoder "^1.1.1" + subarg "^1.0.0" + syntax-error "^1.1.1" + through2 "^2.0.0" + timers-browserify "^1.0.1" + tty-browserify "0.0.1" + url "~0.11.0" + util "~0.10.1" + vm-browserify "^1.0.0" + xtend "^4.0.0" + +browserify@^16.1.0: + version "16.5.2" + resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.2.tgz#d926835e9280fa5fd57f5bc301f2ef24a972ddfe" + integrity sha512-TkOR1cQGdmXU9zW4YukWzWVSJwrxmNdADFbqbE3HFgQWe5wqZmOawqZ7J/8MPCwk/W8yY7Y0h+7mOtcZxLP23g== + dependencies: + JSONStream "^1.0.3" + assert "^1.4.0" + browser-pack "^6.0.1" + browser-resolve "^2.0.0" + browserify-zlib "~0.2.0" + buffer "~5.2.1" + cached-path-relative "^1.0.0" + concat-stream "^1.6.0" + console-browserify "^1.1.0" + constants-browserify "~1.0.0" + crypto-browserify "^3.0.0" + defined "^1.0.0" + deps-sort "^2.0.0" + domain-browser "^1.2.0" + duplexer2 "~0.1.2" + events "^2.0.0" + glob "^7.1.0" + has "^1.0.0" + htmlescape "^1.1.0" + https-browserify "^1.0.0" + inherits "~2.0.1" + insert-module-globals "^7.0.0" + labeled-stream-splicer "^2.0.0" + mkdirp-classic "^0.5.2" + module-deps "^6.2.3" + os-browserify "~0.3.0" + parents "^1.0.1" + path-browserify "~0.0.0" + process "~0.11.0" + punycode "^1.3.2" + querystring-es3 "~0.2.0" + read-only-stream "^2.0.0" + readable-stream "^2.0.2" + resolve "^1.1.4" + shasum "^1.0.0" + shell-quote "^1.6.1" + stream-browserify "^2.0.0" + stream-http "^3.0.0" + string_decoder "^1.1.1" + subarg "^1.0.0" + syntax-error "^1.1.1" + through2 "^2.0.0" + timers-browserify "^1.0.1" + tty-browserify "0.0.1" + url "~0.11.0" + util "~0.10.1" + vm-browserify "^1.0.0" + xtend "^4.0.0" + browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.6.4, browserslist@^4.8.5, browserslist@^4.9.1: version "4.12.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" @@ -1483,6 +2520,17 @@ browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.6.4, browserslist@^4. node-releases "^1.1.53" pkg-up "^2.0.0" +browserslist@^4.14.5, browserslist@^4.16.3, browserslist@^4.6.0: + version "4.16.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" + integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== + dependencies: + caniuse-lite "^1.0.30001181" + colorette "^1.2.1" + electron-to-chromium "^1.3.649" + escalade "^3.1.1" + node-releases "^1.1.70" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1504,6 +2552,22 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.0.2: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffer@~5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" + integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1569,11 +2633,26 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cached-path-relative@^1.0.0, cached-path-relative@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" + integrity sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg== + cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^3.0.0" + caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -1606,6 +2685,11 @@ camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" +camelcase@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -1619,6 +2703,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001039, can version "1.0.30001048" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e" +caniuse-lite@^1.0.30001181: + version "1.0.30001202" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001202.tgz#4cb3bd5e8a808e8cd89e4e66c549989bc8137201" + integrity sha512-ZcijQNqrcF8JNLjzvEiXqX4JUYxoZa7Pvcsd9UD8Kz4TvhTonOSNRsK+qtvpVL4l6+T1Rh4LFtLfnNWg6BGWCQ== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1704,7 +2793,22 @@ cheerio@^0.19.0: htmlparser2 "~3.8.1" lodash "^3.2.0" -chokidar@^2.0.4, chokidar@^2.1.8: +chokidar@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.3.1" + +chokidar@^2.0.4, chokidar@^2.1.1, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" dependencies: @@ -1807,6 +2911,24 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-regexp@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" @@ -1834,10 +2956,23 @@ codex-notifier@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/codex-notifier/-/codex-notifier-1.1.2.tgz#a733079185f4c927fa296f1d71eb8753fe080895" -codex-tooltip@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/codex-tooltip/-/codex-tooltip-1.0.1.tgz#f6e4f39d81507f9c455b667f1287746d14ee8056" - integrity sha512-1xLb1NZbxguNtf02xBRhDphq/EXvMMeEbY0ievjQTHqf8UjXsD41evGk9rqcbjpl+JOjNgtwnp1OaU/X/h6fhQ== +codex-tooltip@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/codex-tooltip/-/codex-tooltip-1.0.2.tgz#81a9d3e2937658c6e5312106b47b9f094ff7be63" + integrity sha512-oC+Bu5X/zyhbPydgMSLWKoM/+vkJMqaLWu3Dt/jZgXS3MWK23INwC5DMBrVXZSufAFk0i0SUni38k9rLMyZn/w== + +coffeeify@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/coffeeify/-/coffeeify-3.0.1.tgz#5e2753000c50bd24c693115f33864248dd11136c" + integrity sha512-Qjnr7UX6ldK1PHV7wCnv7AuCd4q19KTUtwJnu/6JRJB4rfm12zvcXtKdacUoePOKr1I4ka/ydKiwWpNAdsQb0g== + dependencies: + convert-source-map "^1.3.0" + through2 "^2.0.0" + +coffeescript@1.12.7: + version "1.12.7" + resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" + integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA== collapse-white-space@^1.0.2: version "1.0.6" @@ -1884,11 +3019,26 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +colorette@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + colors@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== +combine-source-map@^0.8.0, combine-source-map@~0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" + integrity sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos= + dependencies: + convert-source-map "~1.1.0" + inline-source-map "~0.6.0" + lodash.memoize "~3.0.3" + source-map "~0.5.3" + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1900,10 +3050,10 @@ commander@^2.12.1, commander@^2.20.0, commander@^2.8.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" -commander@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== comment-parser@^0.7.2: version "0.7.2" @@ -1926,7 +3076,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.5.0, concat-stream@^1.6.2: +concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@^1.6.2, concat-stream@~1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" dependencies: @@ -1939,7 +3089,7 @@ console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" -constants-browserify@^1.0.0: +constants-browserify@^1.0.0, constants-browserify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -1947,12 +3097,17 @@ contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" -convert-source-map@^1.7.0: +convert-source-map@^1.1.0, convert-source-map@^1.3.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" dependencies: safe-buffer "~5.1.1" +convert-source-map@~1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" + integrity sha1-SCnId+n+SbMWHzvzZziI4gRpmGA= + copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" @@ -1968,6 +3123,14 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" +core-js-compat@^3.1.1: + version "3.9.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.9.1.tgz#4e572acfe90aff69d76d8c37759d21a5c59bb455" + integrity sha512-jXAirMQxrkbiiLsCx9bQPJFA6llDadKMpYrBJQJ3/c4/vsPP/fAf29h24tviRlvwUL6AmY5CHLu2GvjuYviQqA== + dependencies: + browserslist "^4.16.3" + semver "7.0.0" + core-js-compat@^3.6.2: version "3.6.5" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" @@ -2053,7 +3216,7 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" -crypto-browserify@^3.11.0: +crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" dependencies: @@ -2245,14 +3408,22 @@ cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" -cypress@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.5.0.tgz#1da0355794a43247f8a80cb7f505e83e1cf847cb" - integrity sha512-UHEiTca8AUTevbT2pWkHQlxoHtXmbq+h6Eiu/Mz8DqpNkF98zjTBLv/HFiKJUU5rQzp9EwSWtms33p5TWCJ8tQ== +cypress-intellij-reporter@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/cypress-intellij-reporter/-/cypress-intellij-reporter-0.0.6.tgz#5c396b6fe0a6fcef3b380ec6e62b9c229d62781c" + integrity sha512-KDxeWKKAAGekhg1xmGToSsHDWgogM1hUYakAL4yjKQr9gSI2iyRxcrKlq1/jG4omCbUEY+AZGiiwyKOscY9+Gg== + dependencies: + mocha latest + +cypress@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.8.0.tgz#8338f39212a8f71e91ff8c017a1b6e22d823d8c1" + integrity sha512-W2e9Oqi7DmF48QtOD0LfsOLVq6ef2hcXZvJXI/E3PgFNmZXEVwBefhAxVCW9yTPortjYA2XkM20KyC4HRkOm9w== dependencies: "@cypress/listr-verbose-renderer" "^0.4.1" "@cypress/request" "^2.88.5" "@cypress/xvfb" "^1.2.4" + "@types/node" "12.12.50" "@types/sinonjs__fake-timers" "^6.0.1" "@types/sizzle" "^2.3.2" arch "^2.1.2" @@ -2262,9 +3433,10 @@ cypress@^5.5.0: chalk "^4.1.0" check-more-types "^2.24.0" cli-table3 "~0.6.0" - commander "^4.1.1" + commander "^5.1.0" common-tags "^1.8.0" - debug "^4.1.1" + dayjs "^1.9.3" + debug "4.3.2" eventemitter2 "^6.4.2" execa "^4.0.2" executable "^4.1.1" @@ -2278,10 +3450,10 @@ cypress@^5.5.0: lodash "^4.17.19" log-symbols "^4.0.0" minimist "^1.2.5" - moment "^2.27.0" + moment "^2.29.1" ospath "^1.2.2" pretty-bytes "^5.4.1" - ramda "~0.26.1" + ramda "~0.27.1" request-progress "^3.0.0" supports-color "^7.2.0" tmp "~0.2.1" @@ -2289,6 +3461,11 @@ cypress@^5.5.0: url "^0.11.0" yauzl "^2.10.0" +dash-ast@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" + integrity sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2301,6 +3478,31 @@ date-fns@^1.27.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== +dayjs@^1.9.3: + version "1.10.4" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" + integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== + +debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + dependencies: + ms "^2.1.1" + +debug@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +debug@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2314,12 +3516,6 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - dependencies: - ms "^2.1.1" - decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" @@ -2331,6 +3527,11 @@ decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -2339,6 +3540,13 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" +default-require-extensions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.0.tgz#e03f93aac9b2b6443fc52e5e4a37b3ad9ad8df96" + integrity sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg== + dependencies: + strip-bom "^4.0.0" + define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -2364,11 +3572,26 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +deps-sort@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.1.tgz#9dfdc876d2bcec3386b6829ac52162cda9fa208d" + integrity sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw== + dependencies: + JSONStream "^1.0.3" + shasum-object "^1.0.0" + subarg "^1.0.0" + through2 "^2.0.0" + des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" @@ -2380,6 +3603,20 @@ detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -2425,7 +3662,7 @@ dom-serializer@~0.1.0: domelementtype "^1.3.0" entities "^1.1.1" -domain-browser@^1.1.1: +domain-browser@^1.1.1, domain-browser@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -2475,6 +3712,13 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" +duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" @@ -2496,23 +3740,28 @@ electron-to-chromium@^1.3.413: version "1.3.418" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.418.tgz#840021191f466b803a873e154113620c9f53cec6" +electron-to-chromium@^1.3.649: + version "1.3.690" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.690.tgz#54df63ec42fba6b8e9e05fe4be52caeeedb6e634" + integrity sha512-zPbaSv1c8LUKqQ+scNxJKv01RYFkVVF1xli+b+3Ty8ONujHjAMg+t/COmdZqrtnS1gT+g4hbSodHillymt1Lww== + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= elliptic@^6.0.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" emoji-regex@^7.0.1: version "7.0.3" @@ -2600,10 +3849,25 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + es6-promise@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-2.3.0.tgz#96edb9f2fdb01995822b263dd8aadab6748181bc" +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2650,6 +3914,11 @@ eslint-module-utils@^2.4.1: debug "^2.6.9" pkg-dir "^2.0.0" +eslint-plugin-chai-friendly@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.6.0.tgz#54052fab79302ed0cea76ab997351ea4809bfb77" + integrity sha512-Uvvv1gkbRGp/qfN15B0kQyQWg+oFA8buDSqrwmW3egNSk/FpqH2MjQqKOuKwmEL6w4QIQrIjDp+gg6kGGmD3oQ== + eslint-plugin-cypress@^2.11.2: version "2.11.2" resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.2.tgz#a8f3fe7ec840f55e4cea37671f93293e6c3e76a0" @@ -2825,6 +4094,11 @@ eventemitter2@^6.4.2: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.3.tgz#35c563619b13f3681e7eb05cbdaf50f56ba58820" integrity sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ== +events@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5" + integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg== + events@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" @@ -2836,6 +4110,21 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" +execa@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -2993,6 +4282,11 @@ fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fast-safe-stringify@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" + integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + fastq@^1.6.0: version "1.7.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.7.0.tgz#fcd79a08c5bd7ec5b55cd3f5c4720db551929801" @@ -3064,7 +4358,7 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" -find-cache-dir@^3.3.1: +find-cache-dir@^3.2.0, find-cache-dir@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" dependencies: @@ -3072,6 +4366,14 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -3108,6 +4410,11 @@ flat-cache@^2.0.1: rimraf "2.6.3" write "1.0.3" +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + flatted@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" @@ -3127,6 +4434,14 @@ for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -3154,6 +4469,21 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" +fromentries@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + +fs-extra@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3" + integrity sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -3198,6 +4528,11 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" +fsevents@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3210,10 +4545,25 @@ gensync@^1.0.0-beta.1: version "1.0.0-beta.1" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" -get-caller-file@^2.0.1: +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-assigned-identifiers@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1" + integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ== + +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-stdin@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" @@ -3262,7 +4612,14 @@ glob-parent@^5.0.0, glob-parent@^5.1.0: dependencies: is-glob "^4.0.1" -glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@7.1.6, glob@^7.1.0, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" dependencies: @@ -3322,6 +4679,18 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globby@11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" + integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@^11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.0.tgz#56fd0e9f0d4f8fb0c456f1ab0dee96e1380bc154" @@ -3347,6 +4716,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 version "4.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -3431,11 +4805,24 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -3466,6 +4853,11 @@ html-comment-regex@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + html-janitor@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/html-janitor/-/html-janitor-2.0.4.tgz#ae5a115cdf3331cd5501edd7b5471b18ea44cdbb" @@ -3474,6 +4866,11 @@ html-tags@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" +htmlescape@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" + integrity sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E= + htmlparser2@^3.10.0: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" @@ -3525,6 +4922,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: dependencies: postcss "^7.0.14" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -3606,7 +5008,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3624,6 +5026,13 @@ ini@^1.3.4, ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +inline-source-map@~0.6.0: + version "0.6.2" + resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" + integrity sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU= + dependencies: + source-map "~0.5.3" + inquirer@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" @@ -3642,6 +5051,22 @@ inquirer@^7.0.0: strip-ansi "^6.0.0" through "^2.3.6" +insert-module-globals@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.2.1.tgz#d5e33185181a4e1f33b15f7bf100ee91890d5cb3" + integrity sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg== + dependencies: + JSONStream "^1.0.3" + acorn-node "^1.5.2" + combine-source-map "^0.8.0" + concat-stream "^1.6.1" + is-buffer "^1.1.0" + path-is-absolute "^1.0.1" + process "~0.11.0" + through2 "^2.0.0" + undeclared-identifiers "^1.1.2" + xtend "^4.0.0" + interpret@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -3701,7 +5126,14 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.5: +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@^1.1.0, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -3731,6 +5163,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -3806,7 +5245,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" dependencies: @@ -3854,7 +5293,7 @@ is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" -is-plain-obj@^2.0.0: +is-plain-obj@^2.0.0, is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" @@ -3954,6 +5393,67 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +istanbul-lib-coverage@3.0.0, istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.0-alpha.1: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-processinfo@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz#e1426514662244b2f25df728e8fd1ba35fe53b9c" + integrity sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.0" + istanbul-lib-coverage "^3.0.0-alpha.1" + make-dir "^3.0.0" + p-map "^3.0.0" + rimraf "^3.0.0" + uuid "^3.3.3" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" + integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + jest-worker@^25.4.0: version "25.4.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.4.0.tgz#ee0e2ceee5a36ecddf5172d6d7e0ab00df157384" @@ -3961,10 +5461,30 @@ jest-worker@^25.4.0: merge-stream "^2.0.0" supports-color "^7.0.0" +js-levenshtein@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" +js-yaml@3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" + integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== + dependencies: + argparse "^2.0.1" + js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -4010,6 +5530,13 @@ json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" +json-stable-stringify@~0.0.0: + version "0.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" + integrity sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U= + dependencies: + jsonify "~0.0.0" + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -4021,6 +5548,13 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +json5@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + json5@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" @@ -4042,6 +5576,16 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -4076,6 +5620,14 @@ known-css-properties@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.18.0.tgz#d6e00b56ee1d5b0d171fd86df1583cfb012c521f" +labeled-stream-splicer@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz#42a41a16abcd46fd046306cf4f2c3576fffb1c21" + integrity sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw== + dependencies: + inherits "^2.0.1" + stream-splicer "^2.0.0" + lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -4216,14 +5768,36 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" +lodash.clonedeep@4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" +lodash.memoize@~3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" + integrity sha1-LcvSwofLwKVcxCMovQxzYVDVPj8= + lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -4259,6 +5833,18 @@ lodash@^4.17.19: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +lodash@^4.17.20: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@4.0.0, log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -4278,13 +5864,6 @@ log-symbols@^3.0.0: dependencies: chalk "^2.4.2" -log-symbols@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== - dependencies: - chalk "^4.0.0" - log-update@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" @@ -4317,7 +5896,7 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.2: +make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" dependencies: @@ -4491,12 +6070,12 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -4509,7 +6088,7 @@ minimist-options@^4.0.1: arrify "^1.0.1" is-plain-obj "^1.1.0" -minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" @@ -4559,7 +6138,12 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.1: +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" dependencies: @@ -4570,7 +6154,59 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment@^2.27.0: +mocha@latest: + version "8.3.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.3.2.tgz#53406f195fa86fbdebe71f8b1c6fb23221d69fcc" + integrity sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.1" + debug "4.3.1" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.1.6" + growl "1.10.5" + he "1.2.0" + js-yaml "4.0.0" + log-symbols "4.0.0" + minimatch "3.0.4" + ms "2.1.3" + nanoid "3.1.20" + serialize-javascript "5.0.1" + strip-json-comments "3.1.1" + supports-color "8.1.1" + which "2.0.2" + wide-align "1.1.3" + workerpool "6.1.0" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +module-deps@^6.0.0, module-deps@^6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-6.2.3.tgz#15490bc02af4b56cf62299c7c17cba32d71a96ee" + integrity sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA== + dependencies: + JSONStream "^1.0.3" + browser-resolve "^2.0.0" + cached-path-relative "^1.0.2" + concat-stream "~1.6.0" + defined "^1.0.0" + detective "^5.2.0" + duplexer2 "^0.1.2" + inherits "^2.0.1" + parents "^1.0.0" + readable-stream "^2.0.2" + resolve "^1.4.0" + stream-combiner2 "^1.1.1" + subarg "^1.0.0" + through2 "^2.0.0" + xtend "^4.0.0" + +moment@^2.29.1: version "2.29.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== @@ -4590,10 +6226,15 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.1.1: +ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -4602,6 +6243,16 @@ nan@^2.12.1: version "2.14.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" +nanoid@3.1.20: + version "3.1.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" + integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== + +nanoid@^3.1.22: + version "3.1.22" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" + integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -4662,10 +6313,22 @@ node-modules-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + node-releases@^1.1.53: version "1.1.53" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" +node-releases@^1.1.70: + version "1.1.71" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" + integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== + normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -4681,7 +6344,7 @@ normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -4725,6 +6388,39 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= +nyc@15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" + integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^2.0.0" + get-package-type "^0.1.0" + glob "^7.1.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-processinfo "^2.0.2" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + spawn-wrap "^2.0.0" + test-exclude "^6.0.0" + yargs "^15.0.2" + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -4826,7 +6522,7 @@ optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" -os-browserify@^0.3.0: +os-browserify@^0.3.0, os-browserify@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -4847,6 +6543,13 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= +outpipe@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/outpipe/-/outpipe-1.1.1.tgz#50cf8616365e87e031e29a5ec9339a3da4725fa2" + integrity sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I= + dependencies: + shell-quote "^1.4.2" + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -4871,6 +6574,13 @@ p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0: dependencies: p-try "^2.0.0" +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -4889,6 +6599,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-map@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" @@ -4908,6 +6625,16 @@ p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" @@ -4926,6 +6653,13 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parents@^1.0.0, parents@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" + integrity sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E= + dependencies: + path-platform "~0.11.15" + parse-asn1@^5.0.0: version "5.1.5" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" @@ -4978,7 +6712,7 @@ pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" -path-browserify@0.0.1: +path-browserify@0.0.1, path-browserify@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" @@ -4994,7 +6728,7 @@ path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" -path-is-absolute@^1.0.0: +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -5011,6 +6745,11 @@ path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" +path-platform@~0.11.15: + version "0.11.15" + resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" + integrity sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I= + path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -5041,7 +6780,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.5, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" @@ -5745,7 +7484,14 @@ process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" -process@^0.11.10: +process-on-spawn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" + integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg== + dependencies: + fromentries "^1.2.0" + +process@^0.11.10, process@~0.11.0: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -5803,7 +7549,7 @@ punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" -punycode@^1.2.4: +punycode@^1.2.4, punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -5820,7 +7566,7 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -querystring-es3@^0.2.0: +querystring-es3@^0.2.0, querystring-es3@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -5832,12 +7578,12 @@ quick-lru@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" -ramda@~0.26.1: - version "0.26.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" - integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== +ramda@~0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" + integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" dependencies: @@ -5863,6 +7609,13 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +read-only-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" + integrity sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A= + dependencies: + readable-stream "^2.0.2" + read-pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" @@ -5916,7 +7669,7 @@ readable-stream@1.1: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^3.1.1: +readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" dependencies: @@ -5932,6 +7685,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== + dependencies: + picomatch "^2.2.1" + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -5949,6 +7709,11 @@ regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== + regenerator-runtime@^0.13.4: version "0.13.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" @@ -5986,6 +7751,18 @@ regexpu-core@^4.7.0: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.2.0" +regexpu-core@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" + integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + regextras@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.7.0.tgz#2298bef8cfb92b1b7e3b9b12aa8f69547b7d71e4" @@ -6000,6 +7777,13 @@ regjsparser@^0.6.4: dependencies: jsesc "~0.5.0" +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= + dependencies: + es6-error "^4.0.1" + remark-parse@^8.0.0: version "8.0.2" resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.2.tgz#5999bc0b9c2e3edc038800a64ff103d0890b318b" @@ -6108,6 +7892,19 @@ resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + +resolve@^1.1.4, resolve@^1.17.0, resolve@^1.4.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.8.1: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" @@ -6262,6 +8059,13 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" +serialize-javascript@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" @@ -6287,13 +8091,28 @@ setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" -sha.js@^2.4.0, sha.js@^2.4.8: +sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" +shasum-object@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shasum-object/-/shasum-object-1.0.0.tgz#0b7b74ff5b66ecf9035475522fa05090ac47e29e" + integrity sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg== + dependencies: + fast-safe-stringify "^2.0.7" + +shasum@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" + integrity sha1-5wEjENj0F/TetXEhUOVni4euVl8= + dependencies: + json-stable-stringify "~0.0.0" + sha.js "~2.4.4" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -6316,10 +8135,20 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shell-quote@^1.4.2, shell-quote@^1.6.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -6395,7 +8224,7 @@ source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" -source-map@^0.5.0, source-map@^0.5.6: +source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -6403,6 +8232,18 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" +spawn-wrap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== + dependencies: + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + which "^2.0.1" + spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -6482,13 +8323,21 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -stream-browserify@^2.0.1: +stream-browserify@^2.0.0, stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" dependencies: inherits "~2.0.1" readable-stream "^2.0.2" +stream-combiner2@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" + integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4= + dependencies: + duplexer2 "~0.1.0" + readable-stream "^2.0.2" + stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -6496,7 +8345,7 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" -stream-http@^2.7.2: +stream-http@^2.0.0, stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" dependencies: @@ -6506,10 +8355,28 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-http@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.1.1.tgz#0370a8017cf8d050b9a8554afe608f043eaff564" + integrity sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.4" + readable-stream "^3.6.0" + xtend "^4.0.2" + stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" +stream-splicer@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.1.tgz#0b13b7ee2b5ac7e0609a7463d83899589a363fcd" + integrity sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg== + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.2" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -6519,7 +8386,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -6629,6 +8496,11 @@ strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -6644,6 +8516,11 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" +strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strip-json-comments@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" @@ -6713,6 +8590,13 @@ stylelint@^13.3.3: v8-compile-cache "^2.1.0" write-file-atomic "^3.0.3" +subarg@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" + integrity sha1-9izxdYHplrSPyWVpn1TAauJouNI= + dependencies: + minimist "^1.1.0" + sugarss@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d" @@ -6725,6 +8609,13 @@ supports-color@6.1.0, supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -6785,6 +8676,13 @@ symbol-observable@^1.1.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== +syntax-error@^1.1.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" + integrity sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w== + dependencies: + acorn-node "^1.2.0" + table@^5.2.3, table@^5.4.6: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -6834,6 +8732,15 @@ terser@^4.1.2, terser@^4.6.12: source-map "~0.6.1" source-map-support "~0.5.12" +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -6850,10 +8757,17 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@^2.3.6: +"through@>=2.2.7 <3", through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +timers-browserify@^1.0.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" + integrity sha1-ycWLV1voQHN1y14kYtrO50NZ9B0= + dependencies: + process "~0.11.0" + timers-browserify@^2.0.4: version "2.0.11" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" @@ -6985,6 +8899,11 @@ tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" +tty-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" + integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -7011,7 +8930,7 @@ type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" -type-fest@^0.8.1: +type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -7029,6 +8948,22 @@ typescript@3.8.3, typescript@^3.7.3: version "3.8.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" +umd@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" + integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== + +undeclared-identifiers@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz#9254c1d37bdac0ac2b52de4b6722792d2a91e30f" + integrity sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw== + dependencies: + acorn-node "^1.3.0" + dash-ast "^1.0.0" + get-assigned-identifiers "^1.2.0" + simple-concat "^1.0.0" + xtend "^4.0.1" + unherit@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" @@ -7171,7 +9106,7 @@ urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" -url@^0.11.0: +url@^0.11.0, url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" dependencies: @@ -7207,7 +9142,14 @@ util@^0.11.0: dependencies: inherits "2.0.3" -uuid@^3.3.2: +util@~0.10.1: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + +uuid@^3.3.2, uuid@^3.3.3: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -7261,10 +9203,23 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -vm-browserify@^1.0.1: +vm-browserify@^1.0.0, vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" +watchify@3.11.1: + version "3.11.1" + resolved "https://registry.yarnpkg.com/watchify/-/watchify-3.11.1.tgz#8e4665871fff1ef64c0430d1a2c9d084d9721881" + integrity sha512-WwnUClyFNRMB2NIiHgJU9RQPQNqVeFk7OmZaWf5dC5EnNa0Mgr7imBydbaJ7tGTuPM2hz1Cb4uiBvK9NVxMfog== + dependencies: + anymatch "^2.0.0" + browserify "^16.1.0" + chokidar "^2.1.1" + defined "^1.0.0" + outpipe "^1.1.0" + through2 "^2.0.0" + xtend "^4.0.0" + watchpack@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2" @@ -7328,18 +9283,25 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" +which@2.0.2, which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" dependencies: isexe "^2.0.0" -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== +wide-align@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== dependencies: - isexe "^2.0.0" + string-width "^1.0.2 || 2" word-wrap@~1.2.3: version "1.2.3" @@ -7351,6 +9313,11 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" +workerpool@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" + integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== + wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" @@ -7367,11 +9334,29 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -write-file-atomic@^3.0.3: +write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" dependencies: @@ -7386,13 +9371,19 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +y18n@^5.0.5: + version "5.0.6" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.6.tgz#8236b05cfc5af6a409f41326a4847c68989bb04f" + integrity sha512-PlVX4Y0lDTN6E2V4ES2tEdyvXkeKzxa8c/vo0pxPr/TqbztddTP0yn7zZylIyiAuxerqj0Q5GhpJ1YJCP8LaZQ== yallist@^3.0.2: version "3.1.1" @@ -7408,6 +9399,11 @@ yaml@^1.7.2: dependencies: "@babel/runtime" "^7.9.2" +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + yargs-parser@^13.1.0: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" @@ -7415,13 +9411,28 @@ yargs-parser@^13.1.0: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.1: +yargs-parser@^18.1.1, yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" dependencies: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^20.2.2: + version "20.2.7" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" + integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + yargs@13.2.4: version "13.2.4" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" @@ -7438,6 +9449,36 @@ yargs@13.2.4: y18n "^4.0.0" yargs-parser "^13.1.0" +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^15.0.2: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" @@ -7445,3 +9486,8 @@ yauzl@^2.10.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==