From d27427e2da48d9433b58d9c31be87a09c28d6eb8 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Mon, 22 Mar 2021 13:04:47 +0300 Subject: [PATCH 01/35] chore(example): add nested list to the example page (#1593) * chore(example): add nested list to the example page * Update nested-list * Update example-dev.html * Update example-dev.html * Update example-dev.html --- .gitmodules | 3 +++ example/example-dev.html | 20 +++++++++++++++----- example/tools/nested-list | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) create mode 160000 example/tools/nested-list diff --git a/.gitmodules b/.gitmodules index 8e40c5e6..2041e602 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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/example/example-dev.html b/example/example-dev.html index 84553b66..2ae6f1d2 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' }, @@ -218,9 +219,18 @@ type : 'list', 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' } diff --git a/example/tools/nested-list b/example/tools/nested-list new file mode 160000 index 00000000..64566ce5 --- /dev/null +++ b/example/tools/nested-list @@ -0,0 +1 @@ +Subproject commit 64566ce553fc90e20a3721ec1bf24c959d1efefd From 51a1b48abbd76b1fa50ab1d7ac1787f9d5d9781e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Mar 2021 18:18:47 +0300 Subject: [PATCH 02/35] Bump elliptic from 6.5.3 to 6.5.4 (#1586) Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4. - [Release notes](https://github.com/indutny/elliptic/releases) - [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0a5b9f99..dc468281 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1379,10 +1379,10 @@ bluebird@^3.5.5, 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" @@ -1416,7 +1416,7 @@ braces@^3.0.1: 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= @@ -2502,17 +2502,17 @@ elegant-spinner@^1.0.1: 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" @@ -3435,7 +3435,7 @@ 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= @@ -3606,7 +3606,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== @@ -4491,7 +4491,7 @@ 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= From 4cfcb656a81dc9ae6daf0841ac094f68c4368e15 Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Wed, 31 Mar 2021 23:29:41 +0300 Subject: [PATCH 03/35] [Refactoring] Tools (#1595) * Add internal wrappers for tools classes * FIx lint * Change tools collections to map * Apply some more refactoring * Make tool instance private field * Add some docs * Fix eslint * Review changes * Fix * Fixes after review * Readonly fix --- .gitmodules | 4 +- src/components/block/index.ts | 87 ++-- src/components/modules/blockEvents.ts | 13 +- src/components/modules/blockManager.ts | 15 +- src/components/modules/caret.ts | 4 +- src/components/modules/paste.ts | 121 +++-- src/components/modules/readonly.ts | 12 +- src/components/modules/renderer.ts | 12 +- src/components/modules/sanitizer.ts | 34 +- .../modules/toolbar/blockSettings.ts | 6 +- src/components/modules/toolbar/conversion.ts | 58 +-- src/components/modules/toolbar/inline.ts | 150 +++--- src/components/modules/toolbar/toolbox.ts | 103 ++--- src/components/modules/tools.ts | 428 ++++++------------ src/components/modules/ui.ts | 2 +- src/components/tools/base.ts | 235 ++++++++++ src/components/tools/block.ts | 92 ++++ src/components/tools/factory.ts | 84 ++++ src/components/tools/inline.ts | 31 ++ src/components/tools/tune.ts | 21 + src/{components => }/tools/paragraph | 0 src/{components => }/tools/stub/index.ts | 4 +- types/tools/inline-tool.d.ts | 2 +- 23 files changed, 875 insertions(+), 643 deletions(-) create mode 100644 src/components/tools/base.ts create mode 100644 src/components/tools/block.ts create mode 100644 src/components/tools/factory.ts create mode 100644 src/components/tools/inline.ts create mode 100644 src/components/tools/tune.ts rename src/{components => }/tools/paragraph (100%) rename src/{components => }/tools/stub/index.ts (94%) diff --git a/.gitmodules b/.gitmodules index 2041e602..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 diff --git a/src/components/block/index.ts b/src/components/block/index.ts index fef14126..9bdaaf08 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -1,6 +1,6 @@ import { BlockAPI as BlockAPIInterface, - BlockTool, + BlockTool as IBlockTool, BlockToolConstructable, BlockToolData, BlockTune, @@ -16,12 +16,13 @@ import * as _ from '../utils'; import ApiModules from '../modules/api'; import BlockAPI from './api'; import { ToolType } from '../modules/tools'; +import SelectionUtils from '../selection'; +import BlockTool from '../tools/block'; /** 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'; /** * Interface describes Block class constructor argument @@ -38,14 +39,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 @@ -110,32 +106,27 @@ export default class Block { /** * 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: BlockTune[]; /** * Tool's user configuration @@ -149,6 +140,11 @@ export default class Block { */ private cachedInputs: HTMLElement[] = []; + /** + * Tool class instance + */ + private readonly toolInstance: IBlockTool; + /** * Editor`s API module */ @@ -209,27 +205,20 @@ export default class Block { constructor({ name, data, - Tool, - settings, + tool, api, readOnly, }: BlockConstructorOptions) { this.name = name; - this.class = Tool; - this.settings = settings; - this.config = settings.config || {}; + 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.instance(data, this.blockAPI, readOnly); this.holder = this.compose(); /** @@ -349,7 +338,7 @@ export default class Block { * @returns {object} */ public get sanitize(): SanitizerConfig { - return this.tool.sanitize; + return this.tool.sanitizeConfig; } /** @@ -359,7 +348,7 @@ export default class Block { * @returns {boolean} */ public get mergeable(): boolean { - return _.isFunction(this.tool.merge); + return _.isFunction(this.toolInstance.merge); } /** @@ -502,7 +491,7 @@ export default class Block { /** * call Tool's method with the instance context */ - if (this.tool[methodName] && this.tool[methodName] instanceof Function) { + if (this.toolInstance[methodName] && this.toolInstance[methodName] instanceof Function) { if (methodName === BlockToolAPI.APPEND_CALLBACK) { _.log( '`appendCallback` hook is deprecated and will be removed in the next major release. ' + @@ -513,7 +502,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 +515,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 +525,7 @@ 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); /** * Measuring execution time @@ -572,8 +561,8 @@ 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; @@ -672,6 +661,24 @@ export default class Block { this.removeInputEvents(); } + /** + * Call Tool instance destroy method + */ + public destroy(): void { + 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,7 +687,7 @@ 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); diff --git a/src/components/modules/blockEvents.ts b/src/components/modules/blockEvents.ts index a2b8d661..4fb43db7 100644 --- a/src/components/modules/blockEvents.ts +++ b/src/components/modules/blockEvents.ts @@ -125,7 +125,7 @@ export default class BlockEvents extends Module { 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; @@ -206,15 +206,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 +252,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 +275,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 +313,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..9027c0ea 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -12,6 +12,7 @@ import $ from '../dom'; import * as _ from '../utils'; import Blocks from '../blocks'; import { BlockToolConstructable, BlockToolData, PasteEvent } from '../../../types'; +import BlockTool from '../tools/block'; /** * @typedef {BlockManager} BlockManager @@ -219,15 +220,13 @@ export default class BlockManager extends Module { * * @returns {Block} */ - public composeBlock({ tool, data = {} }: {tool: string; data?: BlockToolData}): Block { + public composeBlock({ tool: name, data = {} }: {tool: string; data?: BlockToolData}): 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, + name, data, - Tool, - settings, + tool, api: this.Editor.API, readOnly, }); @@ -703,9 +702,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(); })); } diff --git a/src/components/modules/caret.ts b/src/components/modules/caret.ts index 2f38dc7c..f02bd9fe 100644 --- a/src/components/modules/caret.ts +++ b/src/components/modules/caret.ts @@ -333,7 +333,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(); @@ -409,7 +409,7 @@ export default class Caret extends Module { * 2. If there is a last block and it is non-default --> and caret not at the end <--, do nothing * (https://github.com/codex-team/editor.js/issues/1414) */ - if (Tools.isDefault(currentBlock.tool) || !isAtEnd) { + if (currentBlock.tool.isDefault || !isAtEnd) { return false; } diff --git a/src/components/modules/paste.ts b/src/components/modules/paste.ts index 884cbdba..1b5e3562 100644 --- a/src/components/modules/paste.ts +++ b/src/components/modules/paste.ts @@ -2,14 +2,13 @@ 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 BlockTool from '../tools/block'; /** * Tag substitute object. @@ -18,9 +17,8 @@ interface TagSubstitute { /** * Name of related Tool * - * @type {string} */ - tool: string; + tool: BlockTool; } /** @@ -29,24 +27,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; } /** @@ -247,7 +239,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 +271,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.instance({}, {} as BlockAPI, false); if (tool.pasteConfig === false) { - this.exceptionList.push(name); + this.exceptionList.push(tool.name); return; } @@ -304,11 +295,9 @@ 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`, @@ -321,17 +310,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 +327,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 +348,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 +369,7 @@ export default class Paste extends Module { }); } - this.toolsFiles[name] = { + this.toolsFiles[tool.name] = { extensions: extensions || [], mimeTypes: mimeTypes || [], }; @@ -391,15 +378,14 @@ 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( @@ -411,7 +397,7 @@ export default class Paste extends Module { this.toolsPatterns.push({ key, pattern, - tool: name, + tool, }); }); } @@ -462,9 +448,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 +459,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( @@ -530,7 +516,6 @@ export default class Paste extends Module { */ private processHTML(innerHTML: string): PasteData[] { const { Tools, Sanitizer } = this.Editor; - const initialTool = this.config.defaultBlock; const wrapper = $.make('DIV'); wrapper.innerHTML = innerHTML; @@ -539,7 +524,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,7 +544,7 @@ 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()] = {}; @@ -577,7 +562,7 @@ export default class Paste extends Module { return { content, isBlock, - tool, + tool: tool.name, event, }; }) @@ -627,7 +612,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 +623,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 +640,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, Sanitizer } = 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,7 +663,7 @@ 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 = Sanitizer.getInlineToolsConfig(BlockManager.currentBlock.tool); document.execCommand( 'insertHTML', @@ -719,7 +704,7 @@ export default class Paste extends Module { return { event, - tool: pattern.tool, + tool: pattern.tool.name, }; } @@ -755,15 +740,15 @@ export default class Paste extends Module { * * @returns {void} */ - private insertEditorJSData(blocks: Array>): void { - const { BlockManager, Caret, Sanitizer, Tools } = this.Editor; + private insertEditorJSData(blocks: Pick[]): void { + const { BlockManager, Caret, Sanitizer } = this.Editor; const sanitizedBlocks = Sanitizer.sanitizeBlocks(blocks); 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 +777,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/renderer.ts b/src/components/modules/renderer.ts index d94efb8b..6b68b2ba 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 @@ -66,7 +67,7 @@ export default class Renderer extends Module { const tool = item.type; const data = item.data; - if (tool in Tools.available) { + if (Tools.available.has(tool)) { try { BlockManager.insert({ tool, @@ -86,11 +87,10 @@ export default class Renderer extends Module { 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({ diff --git a/src/components/modules/sanitizer.ts b/src/components/modules/sanitizer.ts index f3bc086f..0892fb98 100644 --- a/src/components/modules/sanitizer.ts +++ b/src/components/modules/sanitizer.ts @@ -36,8 +36,10 @@ import * as _ from '../utils'; */ import HTMLJanitor from 'html-janitor'; -import { BlockToolData, InlineToolConstructable, SanitizerConfig } from '../../../types'; +import { BlockToolData, SanitizerConfig } from '../../../types'; import { SavedData } from '../../../types/data-formats'; +import InlineTool from '../tools/inline'; +import BlockTool from '../tools/block'; /** * @@ -61,8 +63,8 @@ export default class Sanitizer extends Module { * @param {Array<{tool, data: BlockToolData}>} blocksData - blocks' data to sanitize */ public sanitizeBlocks( - blocksData: Array> - ): Array> { + blocksData: Pick[] + ): Pick[] { return blocksData.map((block) => { const toolConfig = this.composeToolConfig(block.tool); @@ -150,18 +152,17 @@ export default class Sanitizer extends Module { 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); + const tool = this.Editor.Tools.available.get(toolName); + const baseConfig = this.getInlineToolsConfig(tool as BlockTool); /** * If Tools doesn't provide sanitizer config or it is empty */ - if (!toolClass.sanitize || (toolClass[sanitizeGetter] && _.isEmpty(toolClass[sanitizeGetter]))) { + if (!tool.sanitizeConfig || (tool.sanitizeConfig && _.isEmpty(tool.sanitizeConfig))) { return baseConfig; } - const toolRules = toolClass.sanitize; + const toolRules = tool.sanitizeConfig; const toolConfig = {} as SanitizerConfig; @@ -186,12 +187,11 @@ export default class Sanitizer extends Module { * When Tool's "inlineToolbar" value is True, get all sanitizer rules from all tools, * otherwise get only enabled * - * @param {string} name - Inline Tool name + * @param tool - BlockTool object */ - public getInlineToolsConfig(name: string): SanitizerConfig { + public getInlineToolsConfig(tool: BlockTool): SanitizerConfig { const { Tools } = this.Editor; - const toolsConfig = Tools.getToolSettings(name); - const enableInlineTools = toolsConfig.inlineToolbar || []; + const enableInlineTools = tool.enabledInlineTools || []; let config = {} as SanitizerConfig; @@ -207,7 +207,7 @@ export default class Sanitizer extends Module { (enableInlineTools as string[]).map((inlineToolName) => { config = Object.assign( config, - Tools.inline[inlineToolName][Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG] + Tools.inlineTools.get(inlineToolName).sanitizeConfig ) as SanitizerConfig; }); } @@ -233,9 +233,9 @@ export default class Sanitizer extends Module { const config: SanitizerConfig = {} as SanitizerConfig; - Object.entries(Tools.inline) - .forEach(([, inlineTool]: [string, InlineToolConstructable]) => { - Object.assign(config, inlineTool[Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG]); + Object.entries(Tools.inlineTools) + .forEach(([, inlineTool]: [string, InlineTool]) => { + Object.assign(config, inlineTool.sanitizeConfig); }); this.inlineToolsConfigCache = config; @@ -249,7 +249,7 @@ export default class Sanitizer extends Module { * @param {Array} array - [1, 2, {}, []] * @param {SanitizerConfig} ruleForItem - sanitizer config for array */ - private cleanArray(array: Array, ruleForItem: SanitizerConfig): Array { + private cleanArray(array: (object | string)[], ruleForItem: SanitizerConfig): (object | string)[] { return array.map((arrayItem) => this.deepSanitize(arrayItem, ruleForItem)); } diff --git a/src/components/modules/toolbar/blockSettings.ts b/src/components/modules/toolbar/blockSettings.ts index a3a61c54..8cdda140 100644 --- a/src/components/modules/toolbar/blockSettings.ts +++ b/src/components/modules/toolbar/blockSettings.ts @@ -229,8 +229,10 @@ 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); } } diff --git a/src/components/modules/toolbar/conversion.ts b/src/components/modules/toolbar/conversion.ts index a258bf2d..f49d86c1 100644 --- a/src/components/modules/toolbar/conversion.ts +++ b/src/components/modules/toolbar/conversion.ts @@ -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); @@ -229,7 +228,7 @@ export default class ConversionToolbar extends Module { */ const cleaned: string = this.Editor.Sanitizer.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/inline.ts b/src/components/modules/toolbar/inline.ts index 79630309..7c8c77cc 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 } 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 { ToolType } from '../tools'; +import InlineTool from '../../tools/inline'; +import { CommonInternalSettings } from '../../tools/base'; +import BlockTool from '../../tools/block'; /** * 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 @@ -89,38 +94,6 @@ export default class InlineToolbar extends Module { */ private flipper: Flipper = null; - /** - * Internal inline tools: Link, Bold, Italic - */ - private internalTools: {[name: string]: InlineToolConstructable} = {}; - - /** - * Editor modules setter - * - * @param {EditorModules} Editor - Editor's Modules - */ - 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; - }); - } - /** * Toggles read-only mode * @@ -310,16 +283,14 @@ export default class InlineToolbar extends Module { /** * Returns inline toolbar settings for a particular tool * - * @param {string} toolName - user specified name of tool + * @param tool - BlockTool object * @returns {string[] | boolean} array of ordered tool names or false */ - private getInlineToolbarSettings(toolName): string[] | boolean { - const toolSettings = this.Editor.Tools.getToolSettings(toolName); - + private getInlineToolbarSettings(tool: BlockTool): string[] | boolean { /** * InlineToolbar property of a particular tool */ - const settingsForTool = toolSettings[this.Editor.Tools.USER_SETTINGS.ENABLED_INLINE_TOOLS]; + const settingsForTool = tool.enabledInlineTools; /** * Whether to enable IT for a particular tool is the decision of the editor user. @@ -367,15 +338,7 @@ export default class InlineToolbar extends Module { * 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 Array.from(this.Editor.Tools.inlineTools.keys()); } return false; @@ -492,7 +455,7 @@ export default class InlineToolbar extends Module { /** * getInlineToolbarSettings could return an string[] (order of tools) or false (Inline Toolbar disabled). */ - const inlineToolbarSettings = this.getInlineToolbarSettings(currentBlock.name); + const inlineToolbarSettings = this.getInlineToolbarSettings(currentBlock.tool); return inlineToolbarSettings !== false; } @@ -548,13 +511,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 +527,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); } @@ -610,14 +570,12 @@ export default class InlineToolbar extends Module { * 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[]; + const inlineToolbarOrder = this.getInlineToolbarSettings(currentBlock.tool) 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); + const tool = this.Editor.Tools.inlineTools.get(toolName); - this.addTool(toolName, tool); - tool.checkState(SelectionUtils.get()); + this.addTool(tool); }); /** @@ -629,43 +587,42 @@ 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 { + private addTool(tool: InlineTool): void { const { - Tools, Tooltip, } = this.Editor; - const button = tool.render(); + const instance = tool.instance(); + 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 +632,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)); @@ -690,6 +647,8 @@ export default class InlineToolbar extends Module { placement: 'top', hidingDelay: 100, }); + + instance.checkState(SelectionUtils.get()); } /** @@ -704,21 +663,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.getInternal(ToolType.Inline); + + if (Array.from(internalTools.keys()).includes(toolName)) { + return this.inlineTools[toolName][CommonInternalSettings.Shortcut]; } + + return tool.shortcut; } /** @@ -727,7 +685,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 +705,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 +721,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 +741,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.instance(); + }); return result; } diff --git a/src/components/modules/toolbar/toolbox.ts b/src/components/modules/toolbar/toolbox.ts index add3e9e7..765696f1 100644 --- a/src/components/modules/toolbar/toolbox.ts +++ b/src/components/modules/toolbar/toolbox.ts @@ -1,12 +1,13 @@ import Module from '../../__module'; import $ from '../../dom'; import * as _ from '../../utils'; -import { BlockToolConstructable, ToolConstructable } from '../../../../types'; +import { BlockToolConstructable } 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 BlockTool from '../../tools/block'; /** * HTMLElements used for Toolbox UI @@ -116,9 +117,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 +161,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 +197,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 +211,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, { 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 +265,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 +284,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 +313,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..5c4842b6 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -1,21 +1,21 @@ -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, + Tool, 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 BaseTool from '../tools/base'; /** * @module Editor.js Tools Submodule @@ -23,6 +23,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 +49,7 @@ export default class Tools extends Module { * * @returns {object} */ - public get available(): { [name: string]: ToolConstructable } { + public get available(): Map { return this.toolsAvailable; } @@ -56,7 +58,7 @@ export default class Tools extends Module { * * @returns {Tool[]} */ - public get unavailable(): { [name: string]: ToolConstructable } { + public get unavailable(): Map { return this.toolsUnavailable; } @@ -65,48 +67,40 @@ export default class Tools extends Module { * * @returns {object} - object of Inline Tool's classes */ - public get inline(): { [name: string]: InlineToolConstructable } { + public get inlineTools(): Map { if (this._inlineTools) { return this._inlineTools; } - const tools = Object.entries(this.available).filter(([name, tool]) => { - if (!tool[this.INTERNAL_SETTINGS.IS_INLINE]) { - return false; - } + const tools = Array + .from(this.available.entries()) + .filter(([name, tool]: [string, BaseTool]) => { + if (tool.type !== ToolType.Inline) { + return false; + } + /** + * Some Tools validation + */ + const inlineToolRequiredMethods = ['render', 'surround', 'checkState']; + const notImplementedMethods = inlineToolRequiredMethods.filter((method) => !tool.instance()[method]); - /** - * 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 + ); - if (notImplementedMethods.length) { - _.log( - `Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`, - 'warn', - notImplementedMethods - ); + return false; + } - return false; - } - - return true; - }); - - /** - * collected inline tools with key of tool name - */ - const result = {}; - - tools.forEach(([name, tool]) => { - result[name] = tool; - }); + return true; + }); /** * Cache prepared Tools */ - this._inlineTools = result; + this._inlineTools = new Map(tools) as Map; return this._inlineTools; } @@ -114,79 +108,43 @@ export default class Tools extends Module { /** * 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]; - }); + public get blockTools(): Map { + if (this._blockTools) { + return this._blockTools; + } - /** - * collected block tools with key of tool name - */ - const result = {}; + const tools = Array + .from(this.available.entries()) + .filter(([, tool]) => { + return tool.type === ToolType.Block; + }); - tools.forEach(([name, tool]) => { - result[name] = tool; - }); + this._blockTools = new Map(tools) as Map; - return result; + return this._blockTools; } /** - * Constant for available Tools internal settings provided by Tool developer - * - * @returns {object} + * Returns default Tool object */ - 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 defaultTool(): BlockTool { + return this.blockTools.get(this.config.defaultBlock); } /** - * Constant for available Tools settings provided by user - * - * return {object} + * Tools objects factory */ - public get USER_SETTINGS(): { [name: string]: string } { - return { - SHORTCUT: 'shortcut', - TOOLBOX: 'toolbox', - ENABLED_INLINE_TOOLS: 'inlineToolbar', - CONFIG: 'config', - }; - } - - /** - * Map {name: Class, ...} where: - * name — block type name in JSON. Got from EditorConfig.tools keys - * - * @type {object} - */ - public readonly toolsClasses: { [name: string]: ToolConstructable } = {}; + private factory: ToolsFactory; /** * Tools` classes available to use */ - private readonly toolsAvailable: { [name: string]: ToolConstructable } = {}; + private readonly toolsAvailable: Map = new Map(); /** * Tools` classes not available to use because of preparation failure */ - private readonly toolsUnavailable: { [name: string]: ToolConstructable } = {}; - - /** - * Tools settings in a map {name: settings, ...} - * - * @type {object} - */ - private readonly toolsSettings: { [name: string]: ToolSettings } = {}; + private readonly toolsUnavailable: Map = new Map(); /** * Cache for the prepared inline tools @@ -194,41 +152,30 @@ export default class Tools extends Module { * @type {null|object} * @private */ - private _inlineTools: { [name: string]: ToolConstructable } = {}; + private _inlineTools: Map = null; /** - * @class - * - * @param {EditorConfig} config - Editor's configuration - * @param {EventsDispatcher} eventsDispatcher - Editor's event dispatcher + * Cache for the prepared block tools */ - constructor({ config, eventsDispatcher }: ModuleConfig) { - super({ - config, - eventsDispatcher, - }); + private _blockTools: Map = null; - this.toolsClasses = {}; + /** + * Returns internal tools + * + * @param type - if passed, Tools will be filtered by type + */ + public getInternal(type?: ToolType): Map { + let tools = Array + .from(this.available.entries()) + .filter(([, tool]) => { + return tool.isInternal; + }); - this.toolsSettings = {}; + if (type) { + tools = tools.filter(([, tool]) => tool.type === type); + } - /** - * Available tools list - * {name: Class, ...} - * - * @type {object} - */ - this.toolsAvailable = {}; - - /** - * Tools that rejected a prepare method - * {name: Class, ... } - * - * @type {object} - */ - this.toolsUnavailable = {}; - - this._inlineTools = null; + return new Map(tools); } /** @@ -248,54 +195,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 @@ -308,110 +215,42 @@ export default class Tools extends Module { * to see how it works {@link '../utils.ts#sequence'} */ return _.sequence(sequenceData, (data: { toolName: string }) => { - this.success(data); + this.toolPrepareMethodSuccess(data); }, (data: { toolName: string }) => { - this.fallback(data); + this.toolPrepareMethodFallback(data); }); } - /** - * Success callback - * - * @param {object} data - append tool to available list - */ - public success(data: { toolName: string }): void { - this.toolsAvailable[data.toolName] = this.toolsClasses[data.toolName]; - } - - /** - * Fail callback - * - * @param {object} data - append tool to unavailable list - */ - public fallback(data: { toolName: string }): void { - this.toolsUnavailable[data.toolName] = this.toolsClasses[data.toolName]; - } - - /** - * 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 } { + public get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings & { isInternal?: boolean } } { return { - bold: { class: BoldInlineTool }, - italic: { class: ItalicInlineTool }, - link: { class: LinkInlineTool }, + 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, }, - 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; - } - /** * Calls each Tool reset method to clean up anything set by Tool */ @@ -423,41 +262,50 @@ export default class Tools extends Module { }); } + /** + * Tool prepare method success callback + * + * @param {object} data - append tool to available list + */ + private toolPrepareMethodSuccess(data: { toolName: string }): void { + this.toolsAvailable.set(data.toolName, this.factory.get(data.toolName)); + } + + /** + * 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(): 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 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 }; + }[] = []; - 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]; - - /** - * 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. - */ + Object + .entries(config) + .forEach(([toolName, settings]) => { toolPreparationList.push({ // eslint-disable-next-line @typescript-eslint/no-empty-function - function: _.isFunction(toolClass.prepare) ? toolClass.prepare : (): void => { }, + function: _.isFunction(settings.class.prepare) ? settings.class.prepare : (): void => {}, data: { toolName, - config: toolConfig, }, }); - } - } + }); return toolPreparationList; } @@ -485,6 +333,30 @@ export default class Tools extends Module { } } } + + /** + * Unify tools config + */ + 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..bc81516c 100644 --- a/src/components/modules/ui.ts +++ b/src/components/modules/ui.ts @@ -702,7 +702,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(); diff --git a/src/components/tools/base.ts b/src/components/tools/base.ts new file mode 100644 index 00000000..179c44e6 --- /dev/null +++ b/src/components/tools/base.ts @@ -0,0 +1,235 @@ +import { ToolType } from '../modules/tools'; +import { Tool, ToolConstructable, ToolSettings } from '../../../types/tools'; +import { API, SanitizerConfig } from '../../../types'; +import * as _ from '../utils'; + +/** + * 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', + /** + * 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 name - Tool name + * @param constructable - Tool constructable blueprint + * @param config - user specified Tool config + * @param api - EditorJS API module + * @param defaultTool - default Tool name + * @param isInternal - is current Tool internal + * @param defaultPlaceholder - default user specified placeholder + */ + 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.settings[UserSettings.Shortcut]; + + return userShortcut || toolShortcut; + } + + /** + * Returns Tool's sanitizer configuration + */ + public get sanitizeConfig(): SanitizerConfig { + return this.constructable[CommonInternalSettings.SanitizeConfig]; + } + + /** + * Constructs new Tool instance from constructable blueprint + * + * @param args + */ + public abstract instance(...args: any[]): Type; +} diff --git a/src/components/tools/block.ts b/src/components/tools/block.ts new file mode 100644 index 00000000..3e20e98d --- /dev/null +++ b/src/components/tools/block.ts @@ -0,0 +1,92 @@ +import BaseTool, { InternalBlockToolSettings, UserSettings } from './base'; +import { ToolType } from '../modules/tools'; +import { + BlockAPI, + BlockTool as IBlockTool, + BlockToolData, + ConversionConfig, + PasteConfig, + ToolboxConfig +} from '../../../types'; +import * as _ from '../utils'; + +/** + * Class to work with Block tools constructables + */ +export default class BlockTool extends BaseTool { + /** + * Tool type — Block + */ + public type = ToolType.Block; + + /** + * 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 instance(data: BlockToolData, block: BlockAPI, readOnly: boolean): IBlockTool { + // eslint-disable-next-line new-cap + return new this.constructable({ + data, + block, + readOnly, + api: this.api, + 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.settings[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]; + } + + /** + * Returns Tool paste configuration + */ + public get pasteConfig(): PasteConfig { + return this.constructable[InternalBlockToolSettings.PasteConfig] || {}; + } +} diff --git a/src/components/tools/factory.ts b/src/components/tools/factory.ts new file mode 100644 index 00000000..d0d96a36 --- /dev/null +++ b/src/components/tools/factory.ts @@ -0,0 +1,84 @@ +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 { ToolType } from '../modules/tools'; +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, type] = this.getConstructor(constructable); + + return new Constructor({ + name, + constructable, + config, + api: this.api.getMethodsForTool(name, type), + 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, ToolType] { + switch (true) { + case constructable[InternalInlineToolSettings.IsInline]: + return [InlineTool, ToolType.Inline]; + case constructable[InternalTuneSettings.IsTune]: + return [BlockTune, ToolType.Tune]; + default: + return [BlockTool, ToolType.Block]; + } + } +} diff --git a/src/components/tools/inline.ts b/src/components/tools/inline.ts new file mode 100644 index 00000000..257866cc --- /dev/null +++ b/src/components/tools/inline.ts @@ -0,0 +1,31 @@ +import BaseTool, { InternalInlineToolSettings } from './base'; +import { ToolType } from '../modules/tools'; +import { InlineTool as IInlineTool } from '../../../types'; + +/** + * InlineTool object to work with Inline Tools constructables + */ +export default class InlineTool extends BaseTool { + /** + * Tool type — Inline + */ + public type = ToolType.Inline; + + /** + * 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 instance(): IInlineTool { + // eslint-disable-next-line new-cap + return new this.constructable({ + api: this.api, + 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..799dafe0 --- /dev/null +++ b/src/components/tools/tune.ts @@ -0,0 +1,21 @@ +import BaseTool from './base'; +import { ToolType } from '../modules/tools'; + +/** + * Stub class for BlockTunes + * + * @todo Implement + */ +export default class BlockTune extends BaseTool { + /** + * Tool type — Tune + */ + public type = ToolType.Tune; + + /** + * @todo implement + */ + public instance(): any { + return undefined; + } +} 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/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 */ From 2d89105670d9787511e0809d3a2dd448f698e5ae Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Sun, 4 Apr 2021 15:10:26 +0300 Subject: [PATCH 04/35] [Feature] Block Tunes API (#1596) * Add internal wrappers for tools classes * FIx lint * Change tools collections to map * Apply some more refactoring * Make tool instance private field * Add some docs * Fix eslint * Basic implementation for Block Tunes * Small fix for demo * Review changes * Fix * Add common tunes and ToolsCollection class * Fixes after review * Rename tools collections * Readonly fix * Some fixes after review * Apply suggestions from code review Co-authored-by: Peter Savchenko * Fixes after review * Add docs and changelog * Update docs/block-tunes.md Co-authored-by: Peter Savchenko * Apply suggestions from code review Co-authored-by: Peter Savchenko * Update src/components/block/index.ts Co-authored-by: Murod Khaydarov * [Dev] Tools utils tests (#1602) * Add tests for tools utils and coverage report * Fix eslint * Adjust test * Add more tests * Update after code review * Fix test & bump version Co-authored-by: Peter Savchenko Co-authored-by: Murod Khaydarov --- .babelrc | 7 +- .gitignore | 3 + cypress.json | 4 +- docs/CHANGELOG.md | 4 + docs/block-tunes.md | 168 ++ docs/tools.md | 1 + example/example-dev.html | 1 + package.json | 10 +- .../block-tunes/block-tune-delete.ts | 5 + .../block-tunes/block-tune-move-down.ts | 5 + .../block-tunes/block-tune-move-up.ts | 5 + src/components/block/index.ts | 166 +- src/components/modules/api/i18n.ts | 24 +- src/components/modules/api/index.ts | 11 +- src/components/modules/blockEvents.ts | 2 +- src/components/modules/blockManager.ts | 17 +- src/components/modules/paste.ts | 8 +- src/components/modules/renderer.ts | 4 +- src/components/modules/saver.ts | 12 +- .../modules/toolbar/blockSettings.ts | 11 +- src/components/modules/toolbar/inline.ts | 7 +- src/components/modules/tools.ts | 217 +- src/components/tools/base.ts | 57 +- src/components/tools/block.ts | 22 +- src/components/tools/collection.ts | 65 + src/components/tools/factory.ts | 13 +- src/components/tools/inline.ts | 14 +- src/components/tools/tune.ts | 27 +- test/cypress/.eslintrc | 8 +- test/cypress/plugins/index.ts | 37 +- test/cypress/support/index.ts | 2 + test/cypress/tests/initialization.spec.ts | 2 +- test/cypress/tests/modules/Tools.spec.ts | 215 ++ test/cypress/tests/tools/BlockTool.spec.ts | 379 ++++ test/cypress/tests/tools/BlockTune.spec.ts | 178 ++ test/cypress/tests/tools/InlineTool.spec.ts | 196 ++ .../tests/tools/ToolsCollection.spec.ts | 185 ++ test/cypress/tests/tools/ToolsFactory.spec.ts | 59 + test/cypress/tsconfig.json | 10 +- types/block-tunes/block-tune-data.d.ts | 1 + types/block-tunes/block-tune.d.ts | 50 +- types/configs/editor-config.d.ts | 5 + types/data-formats/output-data.d.ts | 6 + types/tools/index.d.ts | 10 +- types/tools/tool-settings.d.ts | 6 + yarn.lock | 1883 ++++++++++++++++- 46 files changed, 3798 insertions(+), 324 deletions(-) create mode 100644 docs/block-tunes.md create mode 100644 src/components/tools/collection.ts create mode 100644 test/cypress/tests/modules/Tools.spec.ts create mode 100644 test/cypress/tests/tools/BlockTool.spec.ts create mode 100644 test/cypress/tests/tools/BlockTune.spec.ts create mode 100644 test/cypress/tests/tools/InlineTool.spec.ts create mode 100644 test/cypress/tests/tools/ToolsCollection.spec.ts create mode 100644 test/cypress/tests/tools/ToolsFactory.spec.ts create mode 100644 types/block-tunes/block-tune-data.d.ts 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/.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/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 d7e4ad70..599322b7 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### 2.20.0 + +- `New` — [Block Tunes API](block-tunes.md) added + ### 2.19.3 - `Fix` — Ignore error raised by Shortcut module diff --git a/docs/block-tunes.md b/docs/block-tunes.md new file mode 100644 index 00000000..1e8c2ce6 --- /dev/null +++ b/docs/block-tunes.md @@ -0,0 +1,168 @@ +# 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 | +| settings | 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 + +--- + +#### 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/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 2ae6f1d2..824bc4f5 100644 --- a/example/example-dev.html +++ b/example/example-dev.html @@ -114,6 +114,7 @@ * Tools list */ tools: { + /** * Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md} */ diff --git a/package.json b/package.json index 19aff379..7d52a57c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.19.3", + "version": "2.20.0", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editor.js", "types": "./types/index.d.ts", @@ -37,21 +37,27 @@ "@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", "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", diff --git a/src/components/block-tunes/block-tune-delete.ts b/src/components/block-tunes/block-tune-delete.ts index 87a8cf85..b6e74333 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 * diff --git a/src/components/block-tunes/block-tune-move-down.ts b/src/components/block-tunes/block-tune-move-down.ts index f095f6db..f6faa7e1 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 * diff --git a/src/components/block-tunes/block-tune-move-up.ts b/src/components/block-tunes/block-tune-move-up.ts index 0005af91..4b0943b6 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 * diff --git a/src/components/block/index.ts b/src/components/block/index.ts index 9bdaaf08..62c32489 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -3,8 +3,7 @@ import { BlockTool as IBlockTool, BlockToolConstructable, BlockToolData, - BlockTune, - BlockTuneConstructable, + BlockTune as IBlockTune, SanitizerConfig, ToolConfig, ToolSettings @@ -15,24 +14,17 @@ import $ from '../dom'; import * as _ from '../utils'; import ApiModules from '../modules/api'; import BlockAPI from './api'; -import { ToolType } from '../modules/tools'; import SelectionUtils from '../selection'; import BlockTool from '../tools/block'; -/** 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 BlockTune from '../tools/tune'; +import { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; +import ToolsCollection from '../tools/collection'; /** * Interface describes Block class constructor argument */ interface BlockConstructorOptions { - /** - * Tool's name - */ - name: string; - /** * Initial Block data */ @@ -52,6 +44,16 @@ interface BlockConstructorOptions { * This flag indicates that the Block should be constructed in the read-only mode. */ readOnly: boolean; + + /** + * Tunes for current Block + */ + tunes: ToolsCollection; + + /** + * Tunes data for current Block + */ + tunesData: {[name: string]: BlockTuneData}; } /** @@ -126,7 +128,7 @@ export default class Block { /** * Tunes used by Tool */ - public readonly tunes: BlockTune[]; + public readonly tunes: ToolsCollection; /** * Tool's user configuration @@ -145,6 +147,22 @@ export default class Block { */ 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 */ @@ -195,7 +213,6 @@ export default class Block { /** * @param {object} options - block constructor options - * @param {string} options.name - Tool name that passed on initialization * @param {BlockToolData} options.data - Tool's initial data * @param {BlockToolConstructable} options.Tool — Tool's class * @param {ToolSettings} options.settings - default tool's config @@ -203,13 +220,14 @@ export default class Block { * @param {boolean} options.readOnly - Read-Only flag */ constructor({ - name, data, tool, api, readOnly, + tunes, + tunesData, }: BlockConstructorOptions) { - this.name = name; + this.name = tool.name; this.settings = tool.settings; this.config = tool.settings.config || {}; this.api = api; @@ -218,13 +236,16 @@ export default class Block { this.mutationObserver = new MutationObserver(this.didMutated); this.tool = tool; - this.toolInstance = tool.instance(data, this.blockAPI, readOnly); + this.toolInstance = tool.create(data, this.blockAPI, readOnly); - this.holder = this.compose(); /** * @type {BlockTune[]} */ - this.tunes = this.makeTunes(); + this.tunes = tunes; + + this.composeTunes(tunesData); + + this.holder = this.compose(); } /** @@ -526,6 +547,21 @@ export default class Block { */ public async save(): Promise { 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 @@ -541,6 +577,7 @@ export default class Block { return { tool: this.name, data: finishedExtraction, + tunes: tunesData, time: measuringEnd - measuringStart, }; }) @@ -568,50 +605,23 @@ export default class Block { 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]; } /** @@ -690,11 +700,57 @@ export default class Block { 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 */ diff --git a/src/components/modules/api/i18n.ts b/src/components/modules/api/i18n.ts index 6f702c60..ec406802 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/blockEvents.ts b/src/components/modules/blockEvents.ts index 4fb43db7..89cd5077 100644 --- a/src/components/modules/blockEvents.ts +++ b/src/components/modules/blockEvents.ts @@ -118,7 +118,7 @@ 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) { diff --git a/src/components/modules/blockManager.ts b/src/components/modules/blockManager.ts index 9027c0ea..d672d60c 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -11,8 +11,8 @@ import Module from '../__module'; import $ from '../dom'; import * as _ from '../utils'; import Blocks from '../blocks'; -import { BlockToolConstructable, BlockToolData, PasteEvent } from '../../../types'; -import BlockTool from '../tools/block'; +import { BlockToolData, PasteEvent } from '../../../types'; +import { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; /** * @typedef {BlockManager} BlockManager @@ -220,15 +220,21 @@ export default class BlockManager extends Module { * * @returns {Block} */ - public composeBlock({ tool: name, data = {} }: {tool: string; data?: BlockToolData}): Block { + public composeBlock({ + tool: name, + data = {}, + tunes: tunesData = {}, + }: {tool: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}}): Block { const readOnly = this.Editor.ReadOnly.isEnabled; const tool = this.Editor.Tools.blockTools.get(name); + const tunes = this.Editor.Tools.getTunesForTool(tool); const block = new Block({ - name, data, tool, api: this.Editor.API, readOnly, + tunes, + tunesData, }); if (!readOnly) { @@ -256,12 +262,14 @@ export default class BlockManager extends Module { index, needToFocus = true, replace = false, + tunes = {}, }: { tool?: string; data?: BlockToolData; index?: number; needToFocus?: boolean; replace?: boolean; + tunes?: {[name: string]: BlockTuneData}; } = {}): Block { let newIndex = index; @@ -272,6 +280,7 @@ export default class BlockManager extends Module { const block = this.composeBlock({ tool, data, + tunes, }); this._blocks.insert(newIndex, block, replace); diff --git a/src/components/modules/paste.ts b/src/components/modules/paste.ts index 1b5e3562..fea1bd33 100644 --- a/src/components/modules/paste.ts +++ b/src/components/modules/paste.ts @@ -222,7 +222,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) { @@ -283,7 +283,7 @@ export default class Paste extends Module { */ private processTool = (tool: BlockTool): void => { try { - const toolInstance = tool.instance({}, {} as BlockAPI, false); + const toolInstance = tool.create({}, {} as BlockAPI, false); if (tool.pasteConfig === false) { this.exceptionList.push(tool.name); @@ -300,7 +300,7 @@ export default class Paste extends Module { 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 ); @@ -389,7 +389,7 @@ export default class Paste extends Module { /** 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' ); } diff --git a/src/components/modules/renderer.ts b/src/components/modules/renderer.ts index 6b68b2ba..b9651b01 100644 --- a/src/components/modules/renderer.ts +++ b/src/components/modules/renderer.ts @@ -64,14 +64,14 @@ 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 } = item; if (Tools.available.has(tool)) { try { BlockManager.insert({ tool, data, + tunes, }); } catch (error) { _.log(`Block «${tool}» skipped because of plugins error`, 'warn', data); diff --git a/src/components/modules/saver.ts b/src/components/modules/saver.ts index 3c0879b0..d32ff1d3 100644 --- a/src/components/modules/saver.ts +++ b/src/components/modules/saver.ts @@ -78,7 +78,7 @@ export default class Saver extends Module { _.log('[Editor.js saving]:', 'groupCollapsed'); - allExtractedData.forEach(({ tool, data, time, isValid }) => { + allExtractedData.forEach(({ tool, data, tunes, time, isValid }) => { totalTime += time; /** @@ -104,10 +104,16 @@ export default class Saver extends Module { return; } - blocks.push({ + const output: any = { type: tool, data, - }); + }; + + if (!_.isEmpty(tunes)) { + output.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 8cdda140..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); @@ -237,10 +237,13 @@ export default class BlockSettings extends Module { } /** - * 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/inline.ts b/src/components/modules/toolbar/inline.ts index 7c8c77cc..3d89aa47 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -7,7 +7,6 @@ import Flipper from '../../flipper'; import I18n from '../../i18n'; import { I18nInternalNS } from '../../i18n/namespace-internal'; import Shortcuts from '../../utils/shortcuts'; -import { ToolType } from '../tools'; import InlineTool from '../../tools/inline'; import { CommonInternalSettings } from '../../tools/base'; import BlockTool from '../../tools/block'; @@ -594,7 +593,7 @@ export default class InlineToolbar extends Module { Tooltip, } = this.Editor; - const instance = tool.instance(); + const instance = tool.create(); const button = instance.render(); if (!button) { @@ -670,7 +669,7 @@ export default class InlineToolbar extends Module { * 2) For external tools, check tool's settings * 3) If shortcut is not set in settings, check Tool's public property */ - const internalTools = Tools.getInternal(ToolType.Inline); + const internalTools = Tools.internal.inlineTools; if (Array.from(internalTools.keys()).includes(toolName)) { return this.inlineTools[toolName][CommonInternalSettings.Shortcut]; @@ -747,7 +746,7 @@ export default class InlineToolbar extends Module { Array .from(this.Editor.Tools.inlineTools.entries()) .forEach(([name, tool]) => { - result[name] = tool.instance(); + result[name] = tool.create(); }); return result; diff --git a/src/components/modules/tools.ts b/src/components/modules/tools.ts index 5c4842b6..856b5a22 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -15,7 +15,10 @@ import ToolsFactory from '../tools/factory'; import InlineTool from '../tools/inline'; import BlockTool from '../tools/block'; import BlockTune from '../tools/tune'; -import BaseTool from '../tools/base'; +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 @@ -49,7 +52,7 @@ export default class Tools extends Module { * * @returns {object} */ - public get available(): Map { + public get available(): ToolsCollection { return this.toolsAvailable; } @@ -58,7 +61,7 @@ export default class Tools extends Module { * * @returns {Tool[]} */ - public get unavailable(): Map { + public get unavailable(): ToolsCollection { return this.toolsUnavailable; } @@ -67,61 +70,24 @@ export default class Tools extends Module { * * @returns {object} - object of Inline Tool's classes */ - public get inlineTools(): Map { - if (this._inlineTools) { - return this._inlineTools; - } - - const tools = Array - .from(this.available.entries()) - .filter(([name, tool]: [string, BaseTool]) => { - if (tool.type !== ToolType.Inline) { - return false; - } - /** - * Some Tools validation - */ - const inlineToolRequiredMethods = ['render', 'surround', 'checkState']; - const notImplementedMethods = inlineToolRequiredMethods.filter((method) => !tool.instance()[method]); - - if (notImplementedMethods.length) { - _.log( - `Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`, - 'warn', - notImplementedMethods - ); - - return false; - } - - return true; - }); - - /** - * Cache prepared Tools - */ - this._inlineTools = new Map(tools) as Map; - - return this._inlineTools; + public get inlineTools(): ToolsCollection { + return this.available.inlineTools; } /** * Return editor block tools */ - public get blockTools(): Map { - if (this._blockTools) { - return this._blockTools; - } + public get blockTools(): ToolsCollection { + return this.available.blockTools; + } - const tools = Array - .from(this.available.entries()) - .filter(([, tool]) => { - return tool.type === ToolType.Block; - }); - - this._blockTools = new Map(tools) as Map; - - return this._blockTools; + /** + * Return available Block Tunes + * + * @returns {object} - object of Inline Tool's classes + */ + public get blockTunes(): ToolsCollection { + return this.available.blockTunes; } /** @@ -139,43 +105,18 @@ export default class Tools extends Module { /** * Tools` classes available to use */ - private readonly toolsAvailable: Map = new Map(); + private readonly toolsAvailable: ToolsCollection = new ToolsCollection(); /** * Tools` classes not available to use because of preparation failure */ - private readonly toolsUnavailable: Map = new Map(); - - /** - * Cache for the prepared inline tools - * - * @type {null|object} - * @private - */ - private _inlineTools: Map = null; - - /** - * Cache for the prepared block tools - */ - private _blockTools: Map = null; + private readonly toolsUnavailable: ToolsCollection = new ToolsCollection(); /** * Returns internal tools - * - * @param type - if passed, Tools will be filtered by type */ - public getInternal(type?: ToolType): Map { - let tools = Array - .from(this.available.entries()) - .filter(([, tool]) => { - return tool.isInternal; - }); - - if (type) { - tools = tools.filter(([, tool]) => tool.type === type); - } - - return new Map(tools); + public get internal(): ToolsCollection { + return this.available.internalTools; } /** @@ -221,11 +162,57 @@ export default class Tools extends Module { }); } + /** + * Returns Block Tunes for passed Tool + * + * @param tool - Tool object + */ + public getTunesForTool(tool: BlockTool): ToolsCollection { + const names = tool.enabledBlockTunes; + + if (names === false) { + return new ToolsCollection(); + } + + if (Array.isArray(names)) { + return new ToolsCollection( + Array + .from(this.blockTunes.entries()) + .filter(([, tune]) => names.includes(tune.name)) + .concat([ ...this.blockTunes.internalTools.entries() ]) + ); + } + + const defaultTuneNames = this.config.tunes; + + if (Array.isArray(defaultTuneNames)) { + return new ToolsCollection( + Array + .from(this.blockTunes.entries()) + .filter(([, tune]) => defaultTuneNames.includes(tune.name)) + .concat([ ...this.blockTunes.internalTools.entries() ]) + ); + } + + return this.blockTunes.internalTools; + } + + /** + * Calls each Tool reset method to clean up anything set by Tool + */ + public destroy(): void { + Object.values(this.available).forEach(async tool => { + if (_.isFunction(tool.reset)) { + await tool.reset(); + } + }); + } + /** * Returns internal tools * Includes Bold, Italic, Link and Paragraph */ - public get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings & { isInternal?: boolean } } { + private get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings & { isInternal?: boolean } } { return { bold: { class: BoldInlineTool, @@ -248,27 +235,50 @@ export default class Tools extends Module { class: Stub, isInternal: true, }, + moveUpTune: { + class: MoveUpTune, + isInternal: true, + }, + deleteTune: { + class: DeleteTune, + isInternal: true, + }, + moveDownTune: { + class: MoveDownTune, + isInternal: true, + }, }; } - /** - * Calls each Tool reset method to clean up anything set by Tool - */ - public destroy(): void { - Object.values(this.available).forEach(async tool => { - if (_.isFunction(tool.reset)) { - await tool.reset(); - } - }); - } - /** * Tool prepare method success callback * * @param {object} data - append tool to available list */ private toolPrepareMethodSuccess(data: { toolName: string }): void { - this.toolsAvailable.set(data.toolName, this.factory.get(data.toolName)); + const tool = this.factory.get(data.toolName); + + 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); } /** @@ -358,22 +368,3 @@ export default class Tools extends Module { return config; } } - -/** - * What kind of plugins developers can create - */ -export enum ToolType { - /** - * Block tool - */ - Block, - /** - * Inline tool - */ - Inline, - - /** - * Block tune - */ - Tune, -} diff --git a/src/components/tools/base.ts b/src/components/tools/base.ts index 179c44e6..862a2afb 100644 --- a/src/components/tools/base.ts +++ b/src/components/tools/base.ts @@ -1,7 +1,29 @@ -import { ToolType } from '../modules/tools'; import { Tool, ToolConstructable, ToolSettings } from '../../../types/tools'; -import { API, SanitizerConfig } from '../../../types'; +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 @@ -19,6 +41,10 @@ export enum UserSettings { * Enabled Inline Tools for Block Tool */ EnabledInlineTools = 'inlineToolbar', + /** + * Enabled Block Tunes for Block Tool + */ + EnabledBlockTunes = 'tunes', /** * Tool configuration */ @@ -105,7 +131,7 @@ interface ConstructorOptions { /** * Base abstract class for Tools */ -export default abstract class BaseTool { +export default abstract class BaseTool { /** * Tool type: Block, Inline or Tune */ @@ -214,7 +240,7 @@ export default abstract class BaseTool { */ public get shortcut(): string | undefined { const toolShortcut = this.constructable[CommonInternalSettings.Shortcut]; - const userShortcut = this.settings[UserSettings.Shortcut]; + const userShortcut = this.config[UserSettings.Shortcut]; return userShortcut || toolShortcut; } @@ -226,10 +252,31 @@ export default abstract class BaseTool { 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 */ - public abstract instance(...args: any[]): Type; + public abstract create(...args: any[]): Type; } diff --git a/src/components/tools/block.ts b/src/components/tools/block.ts index 3e20e98d..610e6748 100644 --- a/src/components/tools/block.ts +++ b/src/components/tools/block.ts @@ -1,8 +1,8 @@ -import BaseTool, { InternalBlockToolSettings, UserSettings } from './base'; -import { ToolType } from '../modules/tools'; +import BaseTool, { InternalBlockToolSettings, ToolType, UserSettings } from './base'; import { BlockAPI, BlockTool as IBlockTool, + BlockToolConstructable, BlockToolData, ConversionConfig, PasteConfig, @@ -19,6 +19,11 @@ export default class BlockTool extends BaseTool { */ public type = ToolType.Block; + /** + * Tool's constructable blueprint + */ + protected constructable: BlockToolConstructable; + /** * Creates new Tool instance * @@ -26,13 +31,13 @@ export default class BlockTool extends BaseTool { * @param block - BlockAPI for current Block * @param readOnly - True if Editor is in read-only mode */ - public instance(data: BlockToolData, block: BlockAPI, readOnly: boolean): IBlockTool { + 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, + api: this.api.getMethodsForTool(this), config: this.settings, }) as IBlockTool; } @@ -56,7 +61,7 @@ export default class BlockTool extends BaseTool { */ public get toolbox(): ToolboxConfig { const toolToolboxSettings = this.constructable[InternalBlockToolSettings.Toolbox] as ToolboxConfig; - const userToolboxSettings = this.settings[UserSettings.Toolbox]; + const userToolboxSettings = this.config[UserSettings.Toolbox]; if (_.isEmpty(toolToolboxSettings)) { return; @@ -83,6 +88,13 @@ export default class BlockTool extends BaseTool { return this.config[UserSettings.EnabledInlineTools]; } + /** + * Returns enabled tunes for Tool + */ + public get enabledBlockTunes(): boolean | string[] { + return this.config[UserSettings.EnabledBlockTunes]; + } + /** * Returns Tool paste configuration */ 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 index d0d96a36..f994d396 100644 --- a/src/components/tools/factory.ts +++ b/src/components/tools/factory.ts @@ -4,7 +4,6 @@ import InlineTool from './inline'; import BlockTune from './tune'; import BlockTool from './block'; import API from '../modules/api'; -import { ToolType } from '../modules/tools'; import { EditorConfig } from '../../../types/configs'; type ToolConstructor = typeof InlineTool | typeof BlockTool | typeof BlockTune; @@ -53,13 +52,13 @@ export default class ToolsFactory { public get(name: string): InlineTool | BlockTool | BlockTune { const { class: constructable, isInternal = false, ...config } = this.config[name]; - const [Constructor, type] = this.getConstructor(constructable); + const Constructor = this.getConstructor(constructable); return new Constructor({ name, constructable, config, - api: this.api.getMethodsForTool(name, type), + api: this.api, isDefault: name === this.editorConfig.defaultBlock, defaultPlaceholder: this.editorConfig.placeholder, isInternal, @@ -71,14 +70,14 @@ export default class ToolsFactory { * * @param constructable - Tools constructable */ - private getConstructor(constructable: ToolConstructable): [ToolConstructor, ToolType] { + private getConstructor(constructable: ToolConstructable): ToolConstructor { switch (true) { case constructable[InternalInlineToolSettings.IsInline]: - return [InlineTool, ToolType.Inline]; + return InlineTool; case constructable[InternalTuneSettings.IsTune]: - return [BlockTune, ToolType.Tune]; + return BlockTune; default: - return [BlockTool, ToolType.Block]; + return BlockTool; } } } diff --git a/src/components/tools/inline.ts b/src/components/tools/inline.ts index 257866cc..281d0d7d 100644 --- a/src/components/tools/inline.ts +++ b/src/components/tools/inline.ts @@ -1,6 +1,5 @@ -import BaseTool, { InternalInlineToolSettings } from './base'; -import { ToolType } from '../modules/tools'; -import { InlineTool as IInlineTool } from '../../../types'; +import BaseTool, { InternalInlineToolSettings, ToolType } from './base'; +import { InlineTool as IInlineTool, InlineToolConstructable } from '../../../types'; /** * InlineTool object to work with Inline Tools constructables @@ -11,6 +10,11 @@ export default class InlineTool extends BaseTool { */ public type = ToolType.Inline; + /** + * Tool's constructable blueprint + */ + protected constructable: InlineToolConstructable; + /** * Returns title for Inline Tool if specified by user */ @@ -21,10 +25,10 @@ export default class InlineTool extends BaseTool { /** * Constructs new InlineTool instance from constructable */ - public instance(): IInlineTool { + public create(): IInlineTool { // eslint-disable-next-line new-cap return new this.constructable({ - api: this.api, + api: this.api.getMethodsForTool(this), config: this.settings, }) as IInlineTool; } diff --git a/src/components/tools/tune.ts b/src/components/tools/tune.ts index 799dafe0..d52f32a6 100644 --- a/src/components/tools/tune.ts +++ b/src/components/tools/tune.ts @@ -1,21 +1,36 @@ -import BaseTool from './base'; -import { ToolType } from '../modules/tools'; +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 { +export default class BlockTune extends BaseTool { /** * Tool type — Tune */ public type = ToolType.Tune; /** - * @todo implement + * Tool's constructable blueprint */ - public instance(): any { - return undefined; + 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), + settings: this.settings, + block, + data, + }); } } 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..06d4b245 100644 --- a/test/cypress/plugins/index.ts +++ b/test/cypress/plugins/index.ts @@ -1,5 +1,38 @@ +/* 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): any => { + /** + * 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/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/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..4361fb33 --- /dev/null +++ b/test/cypress/tests/modules/Tools.spec.ts @@ -0,0 +1,215 @@ +/* tslint:disable:max-classes-per-file */ +/* eslint-disable @typescript-eslint/ban-ts-ignore */ +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 { + // eslint-disable-next-line @typescript-eslint/no-empty-function + public static prepare(): void {} + } as any, + withFailedPrepare: class { + public static prepare(): void { + throw new Error(); + } + } as any, + withoutPrepare: 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, + /** + * 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, + unavailableBlockTune: class { + public static isTune = true; + + public static prepare(): void { + throw new Error(); + } + } as any, + }, + }); + + 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; + }); + }); + + 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/tools/BlockTool.spec.ts b/test/cypress/tests/tools/BlockTool.spec.ts new file mode 100644 index 00000000..89853ff7 --- /dev/null +++ b/test/cypress/tests/tools/BlockTool.spec.ts @@ -0,0 +1,379 @@ +/* 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'; + +describe('BlockTool', () => { + /** + * Mock for BlockTool constructor options + */ + const options = { + name: 'blockTool', + constructable: class { + public static sanitize = { + rule1: 'rule1', + } + + 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); + }); + }); + + it('.sanitizeConfig should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.sanitizeConfig).to.be.deep.eq(options.constructable.sanitize); + }); + + 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); + }); + + it('.enabledInlineTools should return correct value', () => { + const tool = new BlockTool(options as any); + + expect(tool.enabledInlineTools).to.be.deep.eq(options.config.inlineToolbar); + }); + + 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..f922a254 --- /dev/null +++ b/test/cypress/tests/tools/BlockTune.spec.ts @@ -0,0 +1,178 @@ +/* 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 settings: ToolSettings; + public data: BlockTuneData; + public block: object; + + /** + * + */ + constructor({ api, settings, block, data }) { + this.api = api; + this.settings = settings; + 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.settings).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..41c56d9b --- /dev/null +++ b/test/cypress/tests/tools/InlineTool.spec.ts @@ -0,0 +1,196 @@ +/* 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..1cc62dd8 --- /dev/null +++ b/test/cypress/tests/tools/ToolsCollection.spec.ts @@ -0,0 +1,185 @@ +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..84d2ff56 --- /dev/null +++ b/test/cypress/tests/tools/ToolsFactory.spec.ts @@ -0,0 +1,59 @@ +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..1a73982a 100644 --- a/test/cypress/tsconfig.json +++ b/test/cypress/tsconfig.json @@ -1,10 +1,12 @@ { "compilerOptions": { "target": "es2017", - "lib": ["es2017", "dom"], - "types": ["cypress"] + "lib": ["dom", "es2017", "es2018"], + "moduleResolution": "node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true }, "include": [ - "**/*.ts" + "../../**/*.ts" ] - } \ No newline at end of file + } 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..c4ec7589 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, ToolConfig} from '../index'; +import { BlockTuneData } from './block-tune-data'; /** * Describes BLockTune blueprint @@ -10,11 +11,56 @@ 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; + + /** + * @constructor + * + * @param config - Block Tune config + */ + new(config: { + api: API, + settings?: 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..dcb213e7 100644 --- a/types/configs/editor-config.d.ts +++ b/types/configs/editor-config.d.ts @@ -95,4 +95,9 @@ export interface EditorConfig { * 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/output-data.d.ts b/types/data-formats/output-data.d.ts index 1e2c0a64..05961e1f 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 @@ -15,6 +16,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/tool-settings.d.ts b/types/tools/tool-settings.d.ts index 53b8f8c1..dd5af253 100644 --- a/types/tools/tool-settings.d.ts +++ b/types/tools/tool-settings.d.ts @@ -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 */ diff --git a/yarn.lock b/yarn.lock index dc468281..efbfc224 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" @@ -1037,14 +1835,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" @@ -1136,6 +1961,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 +1987,13 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +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,6 +2003,11 @@ 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" @@ -1229,7 +2073,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 +2153,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 +2163,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 +2197,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" @@ -1375,7 +2240,12 @@ 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" @@ -1421,6 +2291,32 @@ brorand@^1.0.1, brorand@^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" + 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 +2364,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 +2487,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 +2519,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 +2600,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" @@ -1619,6 +2665,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 +2755,7 @@ cheerio@^0.19.0: htmlparser2 "~3.8.1" lodash "^3.2.0" -chokidar@^2.0.4, chokidar@^2.1.8: +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 +2858,15 @@ 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" + clone-regexp@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" @@ -1839,6 +2899,19 @@ codex-tooltip@^1.0.1: resolved "https://registry.yarnpkg.com/codex-tooltip/-/codex-tooltip-1.0.1.tgz#f6e4f39d81507f9c455b667f1287746d14ee8056" integrity sha512-1xLb1NZbxguNtf02xBRhDphq/EXvMMeEbY0ievjQTHqf8UjXsD41evGk9rqcbjpl+JOjNgtwnp1OaU/X/h6fhQ== +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" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" @@ -1884,11 +2957,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 +2988,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 +3014,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 +3027,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 +3035,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 +3061,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 +3154,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 +3346,15 @@ 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@^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 +3364,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 +3381,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 +3392,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 +3409,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 +3447,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" @@ -2339,6 +3466,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 +3498,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 +3529,15 @@ 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@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -2425,7 +3583,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 +3633,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,6 +3661,11 @@ 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" @@ -2600,10 +3770,20 @@ 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@^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 +3830,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 +4010,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 +4026,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 +4198,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 +4274,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: @@ -3127,6 +4337,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 +4372,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" @@ -3210,10 +4443,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" +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: 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 +4510,7 @@ 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@^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 +4570,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" @@ -3431,6 +4691,14 @@ 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" + 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" @@ -3466,6 +4734,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 +4747,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 +4803,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" @@ -3624,6 +4907,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 +4932,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 +5007,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.5: +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 +5037,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" @@ -3954,6 +5267,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 +5335,23 @@ 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@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -4010,6 +5397,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 +5415,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 +5443,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 +5487,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" @@ -4220,10 +5639,25 @@ 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 +5693,11 @@ 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@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -4317,7 +5756,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: @@ -4509,7 +5948,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 +5998,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 +6014,28 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment@^2.27.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,7 +6055,7 @@ 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" @@ -4662,10 +6127,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" @@ -4725,6 +6202,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 +6336,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 +6357,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" @@ -4908,6 +6425,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 +6453,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 +6512,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 +6528,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 +6545,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" @@ -5745,7 +7284,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 +7349,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 +7366,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,10 +7378,10 @@ 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: version "2.1.0" @@ -5863,6 +7409,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 +7469,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: @@ -5949,6 +7502,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 +7544,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 +7570,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 +7685,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" @@ -6287,13 +7877,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 +7921,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 +8010,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 +8018,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 +8109,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 +8131,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 +8141,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" @@ -6629,6 +8282,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" @@ -6713,6 +8371,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" @@ -6785,6 +8450,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 +8506,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 +8531,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 +8673,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 +8704,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 +8722,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 +8880,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 +8916,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 +8977,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" @@ -7367,11 +9096,20 @@ 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" + 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,7 +9124,7 @@ 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" @@ -7415,7 +9153,7 @@ 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: @@ -7438,6 +9176,23 @@ yargs@13.2.4: y18n "^4.0.0" yargs-parser "^13.1.0" +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" From 63e438a72de3144b960ec4e4b435c56dd72090b6 Mon Sep 17 00:00:00 2001 From: "Umang G. Patel" <23169768+robonetphy@users.noreply.github.com> Date: Thu, 8 Apr 2021 22:17:46 +0530 Subject: [PATCH 05/35] refactoring(modules): Tooltip module is util now (#1601) * remove tooltip module and create tooltip class * change log added * constructor added with tooltip * linting added * js-docs improved * linting added --- docs/CHANGELOG.md | 5 +++ src/components/__module.ts | 6 ++-- src/components/modules/api/tooltip.ts | 34 ++++++++++++++++---- src/components/modules/caret.ts | 2 +- src/components/modules/toolbar/index.ts | 26 +++++++++++++-- src/components/modules/toolbar/inline.ts | 31 ++++++++++++++---- src/components/modules/toolbar/toolbox.ts | 27 ++++++++++++++-- src/components/{modules => utils}/tooltip.ts | 4 +-- src/types-internal/editor-modules.d.ts | 2 -- 9 files changed, 110 insertions(+), 27 deletions(-) rename src/components/{modules => utils}/tooltip.ts (94%) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 599322b7..4e99e4d6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,9 +1,14 @@ # Changelog +### 2.20.1 + +- `Refactoring` - Tooltip module is util now. + ### 2.20.0 - `New` — [Block Tunes API](block-tunes.md) added + ### 2.19.3 - `Fix` — Ignore error raised by Shortcut module diff --git a/src/components/__module.ts b/src/components/__module.ts index 877ecd88..569470f5 100644 --- a/src/components/__module.ts +++ b/src/components/__module.ts @@ -92,9 +92,9 @@ export default class Module { /** * @class - * - * @param {EditorConfig} config - Editor's config - * @param {EventsDispatcher} eventsDispatcher - Editor's event dispatcher + * @param {object} moduleConfiguration - Module Configuration + * @param {EditorConfig} moduleConfiguration.config - Editor's config + * @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher */ constructor({ config, eventsDispatcher }: ModuleConfig) { if (new.target === Module) { diff --git a/src/components/modules/api/tooltip.ts b/src/components/modules/api/tooltip.ts index 4094f4e8..74796baa 100644 --- a/src/components/modules/api/tooltip.ts +++ b/src/components/modules/api/tooltip.ts @@ -1,16 +1,38 @@ -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(); + } + /** * Available methods */ - public get methods(): Tooltip { + public get methods(): ITooltip { return { show: (element: HTMLElement, content: TooltipContent, @@ -32,14 +54,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 +72,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/caret.ts b/src/components/modules/caret.ts index f02bd9fe..9fd4cfd5 100644 --- a/src/components/modules/caret.ts +++ b/src/components/modules/caret.ts @@ -391,7 +391,7 @@ export default class Caret extends Module { * @returns {boolean} */ public navigateNext(): boolean { - const { BlockManager, Tools } = this.Editor; + const { BlockManager } = this.Editor; const { currentBlock, nextContentfulBlock } = BlockManager; const { nextInput } = currentBlock; const isAtEnd = this.isAtEnd; diff --git a/src/components/modules/toolbar/index.ts b/src/components/modules/toolbar/index.ts index ea8e6f29..7ed3e28c 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 EventsDispatcher from '../../utils/events'; +import { EditorConfig } from '../../../../types'; /** * 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'), { diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index 3d89aa47..8bf0731e 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -2,11 +2,14 @@ import Module from '../../__module'; import $ from '../../dom'; import SelectionUtils from '../../selection'; import * as _ from '../../utils'; -import { InlineTool as IInlineTool } 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 Tooltip from '../../utils/tooltip'; +import { ModuleConfig } from '../../../types-internal/module-config'; +import EventsDispatcher from '../../utils/events'; import InlineTool from '../../tools/inline'; import { CommonInternalSettings } from '../../tools/base'; import BlockTool from '../../tools/block'; @@ -93,6 +96,24 @@ export default class InlineToolbar 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(); + } + /** * Toggles read-only mode * @@ -500,7 +521,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, }); @@ -589,10 +610,6 @@ export default class InlineToolbar extends Module { * @param {InlineTool} tool - InlineTool object */ private addTool(tool: InlineTool): void { - const { - Tooltip, - } = this.Editor; - const instance = tool.create(); const button = instance.render(); @@ -642,7 +659,7 @@ export default class InlineToolbar extends Module { })); } - Tooltip.onHover(button, tooltipContent, { + this.tooltip.onHover(button, tooltipContent, { placement: 'top', hidingDelay: 100, }); diff --git a/src/components/modules/toolbar/toolbox.ts b/src/components/modules/toolbar/toolbox.ts index 765696f1..db49be81 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 } from '../../../../types'; +import { BlockToolConstructable, EditorConfig } 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'; /** @@ -41,7 +44,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', @@ -84,6 +87,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 */ @@ -219,7 +240,7 @@ export default class Toolbox extends Module { */ const tooltipContent = this.drawTooltip(tool); - this.Editor.Tooltip.onHover(button, tooltipContent, { + this.tooltip.onHover(button, tooltipContent, { placement: 'bottom', hidingDelay: 200, }); diff --git a/src/components/modules/tooltip.ts b/src/components/utils/tooltip.ts similarity index 94% rename from src/components/modules/tooltip.ts rename to src/components/utils/tooltip.ts index f3ff6711..010039d1 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 * diff --git a/src/types-internal/editor-modules.d.ts b/src/types-internal/editor-modules.d.ts index 14ad7e30..64751505 100644 --- a/src/types-internal/editor-modules.d.ts +++ b/src/types-internal/editor-modules.d.ts @@ -6,7 +6,6 @@ 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'; @@ -56,7 +55,6 @@ export interface EditorModules { Caret: Caret; Saver: Saver; Notifier: Notifier; - Tooltip: Tooltip; BlockManager: BlockManager; BlocksAPI: BlocksAPI; CaretAPI: CaretAPI; From d02c3e3679d60c14029ec96ad866e72b0888ef48 Mon Sep 17 00:00:00 2001 From: "Umang G. Patel" <23169768+robonetphy@users.noreply.github.com> Date: Thu, 8 Apr 2021 22:19:11 +0530 Subject: [PATCH 06/35] minor test case for onReady onChange data readOnly and i18n added (#1566) --- test/testcases.md | 50 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 12 deletions(-) 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 From a88dc8e30ba64dd854aaaf769a37074dff01449d Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Thu, 8 Apr 2021 21:17:23 +0300 Subject: [PATCH 07/35] refactoring(modules): sanitizer module is util now (#1574) * refactoring(modules): sanitizer module is util now * Remove Sanitizer from Editor modules signature * Bind context to config composition method * Make sanitizer singletone * Make sanitizer a module instead of class * Fix * Add test cases for default values * Fix inline tools default value * Move inline tools and block tunes to BlockTool instance * Fixes after review & some test cases for sanitisation * Upgrade test case Co-authored-by: Peter Savchenko --- docs/CHANGELOG.md | 6 +- package.json | 1 + src/components/block/index.ts | 8 +- src/components/modules/api/sanitizer.ts | 7 +- src/components/modules/blockManager.ts | 2 - src/components/modules/blockSelection.ts | 3 +- src/components/modules/paste.ts | 27 +- src/components/modules/sanitizer.ts | 333 ------------------- src/components/modules/saver.ts | 7 +- src/components/modules/toolbar/conversion.ts | 3 +- src/components/modules/toolbar/inline.ts | 84 +---- src/components/modules/tools.ts | 125 +++++-- src/components/tools/base.ts | 2 +- src/components/tools/block.ts | 65 +++- src/components/utils.ts | 46 +++ src/components/utils/sanitizer.ts | 191 +++++++++++ src/types-internal/editor-modules.d.ts | 2 - test/cypress/support/commands.ts | 26 ++ test/cypress/support/index.d.ts | 10 + test/cypress/tests/modules/Tools.spec.ts | 76 ++++- test/cypress/tests/sanitisation.spec.ts | 65 ++++ test/cypress/tests/tools/BlockTool.spec.ts | 111 ++++++- test/cypress/tsconfig.json | 3 +- tsconfig.json | 3 +- yarn.lock | 329 ++++++++++++++++-- 25 files changed, 1016 insertions(+), 519 deletions(-) delete mode 100644 src/components/modules/sanitizer.ts create mode 100644 src/components/utils/sanitizer.ts create mode 100644 test/cypress/tests/sanitisation.spec.ts diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4e99e4d6..9d14d38f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,9 @@ ### 2.20.1 + +- `Fix` — Fix sanitisation problem with Inline Tools [#1631](https://github.com/codex-team/editor.js/issues/1631) +- `Refactoring` - The Sanitizer module is util now. - `Refactoring` - Tooltip module is util now. ### 2.20.0 @@ -15,7 +18,7 @@ ### 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). @@ -33,6 +36,7 @@ - `Refactoring` - Shortcuts module is util now. - `Fix` - Fix bubbling on BlockManagers' listener [#1433](https://github.com/codex-team/editor.js/issues/1433). + ### 2.19.1 - `Improvements` - The [Cypress](https://www.cypress.io) was integrated as the end-to-end testing framework diff --git a/package.json b/package.json index 7d52a57c..484e0b05 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "css-loader": "^3.5.3", "cssnano": "^4.1.10", "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", diff --git a/src/components/block/index.ts b/src/components/block/index.ts index 62c32489..15a95b61 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -45,11 +45,6 @@ interface BlockConstructorOptions { */ readOnly: boolean; - /** - * Tunes for current Block - */ - tunes: ToolsCollection; - /** * Tunes data for current Block */ @@ -224,7 +219,6 @@ export default class Block { tool, api, readOnly, - tunes, tunesData, }: BlockConstructorOptions) { this.name = tool.name; @@ -241,7 +235,7 @@ export default class Block { /** * @type {BlockTune[]} */ - this.tunes = tunes; + this.tunes = tool.tunes; this.composeTunes(tunesData); diff --git a/src/components/modules/api/sanitizer.ts b/src/components/modules/api/sanitizer.ts index 01ab2be2..b0ce0ef0 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 @@ -12,7 +13,7 @@ export default class SanitizerAPI extends Module { * * @returns {Sanitizer} */ - 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/blockManager.ts b/src/components/modules/blockManager.ts index d672d60c..5614aa83 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -227,13 +227,11 @@ export default class BlockManager extends Module { }: {tool: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}}): Block { const readOnly = this.Editor.ReadOnly.isEnabled; const tool = this.Editor.Tools.blockTools.get(name); - const tunes = this.Editor.Tools.getTunesForTool(tool); const block = new Block({ data, tool, api: this.Editor.API, readOnly, - tunes, tunesData, }); diff --git a/src/components/modules/blockSelection.ts b/src/components/modules/blockSelection.ts index 1c05d239..96ada11b 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'; /** * @@ -297,7 +298,7 @@ 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; diff --git a/src/components/modules/paste.ts b/src/components/modules/paste.ts index fea1bd33..a9280bd0 100644 --- a/src/components/modules/paste.ts +++ b/src/components/modules/paste.ts @@ -8,6 +8,7 @@ import { } from '../../../types'; import Block from '../block'; import { SavedData } from '../../../types/data-formats'; +import { clean, sanitizeBlocks } from '../utils/sanitizer'; import BlockTool from '../tools/block'; /** @@ -158,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; /** @@ -203,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)) { @@ -515,7 +514,7 @@ export default class Paste extends Module { * @returns {PasteData[]} */ private processHTML(innerHTML: string): PasteData[] { - const { Tools, Sanitizer } = this.Editor; + const { Tools } = this.Editor; const wrapper = $.make('DIV'); wrapper.innerHTML = innerHTML; @@ -551,9 +550,9 @@ export default class Paste extends Module { 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, @@ -640,7 +639,7 @@ export default class Paste extends Module { * @param {PasteData} dataToInsert - data of Block to insert */ private async processInlinePaste(dataToInsert: PasteData): Promise { - const { BlockManager, Caret, Sanitizer } = this.Editor; + const { BlockManager, Caret, Tools } = this.Editor; const { content } = dataToInsert; const currentBlockIsDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault; @@ -663,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.tool); + const currentToolSanitizeConfig = BlockManager.currentBlock.tool.sanitizeConfig; document.execCommand( 'insertHTML', false, - Sanitizer.clean(content.innerHTML, currentToolSanitizeConfig) + clean(content.innerHTML, currentToolSanitizeConfig) ); } else { this.insertBlock(dataToInsert); @@ -741,8 +740,10 @@ export default class Paste extends Module { * @returns {void} */ private insertEditorJSData(blocks: Pick[]): void { - const { BlockManager, Caret, Sanitizer } = this.Editor; - const sanitizedBlocks = Sanitizer.sanitizeBlocks(blocks); + const { BlockManager, Caret, Tools } = this.Editor; + const sanitizedBlocks = sanitizeBlocks(blocks, (name) => + Tools.blockTools.get(name).sanitizeConfig + ); sanitizedBlocks.forEach(({ tool, data }, i) => { let needToReplaceCurrentBlock = false; diff --git a/src/components/modules/sanitizer.ts b/src/components/modules/sanitizer.ts deleted file mode 100644 index 0892fb98..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, SanitizerConfig } from '../../../types'; -import { SavedData } from '../../../types/data-formats'; -import InlineTool from '../tools/inline'; -import BlockTool from '../tools/block'; - -/** - * - */ -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: Pick[] - ): Pick[] { - 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 tool = this.Editor.Tools.available.get(toolName); - const baseConfig = this.getInlineToolsConfig(tool as BlockTool); - - /** - * If Tools doesn't provide sanitizer config or it is empty - */ - if (!tool.sanitizeConfig || (tool.sanitizeConfig && _.isEmpty(tool.sanitizeConfig))) { - return baseConfig; - } - - const toolRules = tool.sanitizeConfig; - - 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 tool - BlockTool object - */ - public getInlineToolsConfig(tool: BlockTool): SanitizerConfig { - const { Tools } = this.Editor; - const enableInlineTools = tool.enabledInlineTools || []; - - 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.inlineTools.get(inlineToolName).sanitizeConfig - ) 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.inlineTools) - .forEach(([, inlineTool]: [string, InlineTool]) => { - Object.assign(config, inlineTool.sanitizeConfig); - }); - - this.inlineToolsConfigCache = config; - - return this.inlineToolsConfigCache; - } - - /** - * Clean array - * - * @param {Array} array - [1, 2, {}, []] - * @param {SanitizerConfig} ruleForItem - sanitizer config for array - */ - private cleanArray(array: (object | string)[], ruleForItem: SanitizerConfig): (object | string)[] { - 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 d32ff1d3..1263562b 100644 --- a/src/components/modules/saver.ts +++ b/src/components/modules/saver.ts @@ -10,6 +10,7 @@ import { OutputData } from '../../../types'; import { ValidatedData } from '../../../types/data-formats'; import Block from '../block'; import * as _ from '../utils'; +import { sanitizeBlocks } from '../utils/sanitizer'; declare const VERSION: string; @@ -27,7 +28,7 @@ export default class Saver extends Module { * @returns {OutputData} */ public async save(): Promise { - const { BlockManager, Sanitizer, ModificationsObserver } = this.Editor; + const { BlockManager, ModificationsObserver, Tools } = this.Editor; const blocks = BlockManager.blocks, chainData = []; @@ -42,7 +43,9 @@ export default class Saver extends Module { }); const extractedData = await Promise.all(chainData); - const sanitizedData = await Sanitizer.sanitizeBlocks(extractedData); + const sanitizedData = await sanitizeBlocks(extractedData, (name) => { + return Tools.blockTools.get(name).sanitizeConfig; + }); return this.makeOutput(sanitizedData); } finally { diff --git a/src/components/modules/toolbar/conversion.ts b/src/components/modules/toolbar/conversion.ts index f49d86c1..44743fa5 100644 --- a/src/components/modules/toolbar/conversion.ts +++ b/src/components/modules/toolbar/conversion.ts @@ -6,6 +6,7 @@ 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 @@ -226,7 +227,7 @@ 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.sanitizeConfig ); diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index 8bf0731e..a100f15c 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -300,70 +300,6 @@ export default class InlineToolbar extends Module { this.removeAllNodes(); } - /** - * Returns inline toolbar settings for a particular tool - * - * @param tool - BlockTool object - * @returns {string[] | boolean} array of ordered tool names or false - */ - private getInlineToolbarSettings(tool: BlockTool): string[] | boolean { - /** - * InlineToolbar property of a particular tool - */ - const settingsForTool = tool.enabledInlineTools; - - /** - * 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) { - return Array.from(this.Editor.Tools.inlineTools.keys()); - } - - return false; - } - /** * Making DOM */ @@ -472,12 +408,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.tool); - - return inlineToolbarSettings !== false; + return currentBlock.tool.inlineTools.size !== 0; } /** @@ -583,18 +514,7 @@ 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.tool) as string[]; - - inlineToolbarOrder.forEach((toolName) => { - const tool = this.Editor.Tools.inlineTools.get(toolName); - + Array.from(currentBlock.tool.inlineTools.values()).forEach(tool => { this.addTool(tool); }); diff --git a/src/components/modules/tools.ts b/src/components/modules/tools.ts index 856b5a22..c1aa30ca 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -3,6 +3,7 @@ import Module from '../__module'; import * as _ from '../utils'; import { EditorConfig, + SanitizerConfig, Tool, ToolConstructable, ToolSettings @@ -124,7 +125,7 @@ export default class Tools extends Module { * * @returns {Promise} */ - public prepare(): Promise { + public async prepare(): Promise { this.validateTools(); /** @@ -155,46 +156,28 @@ export default class Tools extends Module { /** * to see how it works {@link '../utils.ts#sequence'} */ - return _.sequence(sequenceData, (data: { toolName: string }) => { + await _.sequence(sequenceData, (data: { toolName: string }) => { this.toolPrepareMethodSuccess(data); }, (data: { toolName: string }) => { this.toolPrepareMethodFallback(data); }); + + this.prepareBlockTools(); } /** - * Returns Block Tunes for passed Tool - * - * @param tool - Tool object + * Return general Sanitizer config for all inline tools */ - public getTunesForTool(tool: BlockTool): ToolsCollection { - const names = tool.enabledBlockTunes; + @_.cacheable + public getAllInlineToolsSanitizeConfig(): SanitizerConfig { + const config: SanitizerConfig = {} as SanitizerConfig; - if (names === false) { - return new ToolsCollection(); - } + Array.from(this.inlineTools.values()) + .forEach(inlineTool => { + Object.assign(config, inlineTool.sanitizeConfig); + }); - if (Array.isArray(names)) { - return new ToolsCollection( - Array - .from(this.blockTunes.entries()) - .filter(([, tune]) => names.includes(tune.name)) - .concat([ ...this.blockTunes.internalTools.entries() ]) - ); - } - - const defaultTuneNames = this.config.tunes; - - if (Array.isArray(defaultTuneNames)) { - return new ToolsCollection( - Array - .from(this.blockTunes.entries()) - .filter(([, tune]) => defaultTuneNames.includes(tune.name)) - .concat([ ...this.blockTunes.internalTools.entries() ]) - ); - } - - return this.blockTunes.internalTools; + return config; } /** @@ -320,6 +303,86 @@ export default class Tools extends Module { 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.enabledInlineTools === false) { + return; + } + + if (Array.isArray(tool.enabledBlockTunes)) { + tool.tunes = new ToolsCollection( + tool.enabledBlockTunes.map(name => [name, this.blockTunes.get(name)]) + ); + + return; + } + + if (Array.isArray(this.config.tunes)) { + tool.tunes = new ToolsCollection( + this.config.tunes.map(name => [name, this.blockTunes.get(name)]) + ); + + return; + } + + tool.tunes = this.blockTunes.internalTools; + } + /** * Validate Tools configuration objects and throw Error for user if it is invalid */ diff --git a/src/components/tools/base.ts b/src/components/tools/base.ts index 862a2afb..1f36ff37 100644 --- a/src/components/tools/base.ts +++ b/src/components/tools/base.ts @@ -249,7 +249,7 @@ export default abstract class BaseTool { * Returns Tool's sanitizer configuration */ public get sanitizeConfig(): SanitizerConfig { - return this.constructable[CommonInternalSettings.SanitizeConfig]; + return this.constructable[CommonInternalSettings.SanitizeConfig] || {}; } /** diff --git a/src/components/tools/block.ts b/src/components/tools/block.ts index 610e6748..7da7315c 100644 --- a/src/components/tools/block.ts +++ b/src/components/tools/block.ts @@ -5,10 +5,13 @@ import { BlockToolConstructable, BlockToolData, ConversionConfig, - PasteConfig, + 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 @@ -19,6 +22,16 @@ export default class BlockTool extends BaseTool { */ 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 */ @@ -85,7 +98,7 @@ export default class BlockTool extends BaseTool { * Returns enabled inline tools for Tool */ public get enabledInlineTools(): boolean | string[] { - return this.config[UserSettings.EnabledInlineTools]; + return this.config[UserSettings.EnabledInlineTools] || false; } /** @@ -101,4 +114,52 @@ export default class BlockTool extends BaseTool { public get pasteConfig(): PasteConfig { return this.constructable[InternalBlockToolSettings.PasteConfig] || {}; } + + /** + * Returns sanitize configuration for Block Tool including conifgs from Inline Tools + */ + @_.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)); + + return baseConfig; + } } diff --git a/src/components/utils.ts b/src/components/utils.ts index 4634e590..8e17379d 100644 --- a/src/components/utils.ts +++ b/src/components/utils.ts @@ -642,3 +642,49 @@ 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 + */ + 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 + */ + if (propertyToOverride === 'get' && descriptor.set) { + const originalSet = descriptor.set; + + descriptor.set = function (value: any): void { + delete target[cacheKey]; + + originalSet.apply(this, value); + }; + } + + return descriptor; +}; diff --git a/src/components/utils/sanitizer.ts b/src/components/utils/sanitizer.ts new file mode 100644 index 00000000..1eb42b58 --- /dev/null +++ b/src/components/utils/sanitizer.ts @@ -0,0 +1,191 @@ +/** + * 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/types-internal/editor-modules.d.ts b/src/types-internal/editor-modules.d.ts index 64751505..3449adea 100644 --- a/src/types-internal/editor-modules.d.ts +++ b/src/types-internal/editor-modules.d.ts @@ -9,7 +9,6 @@ import Notifier from '../components/modules/notifier'; 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'; @@ -49,7 +48,6 @@ export interface EditorModules { DragNDrop: DragNDrop; ModificationsObserver: ModificationsObserver; Renderer: Renderer; - Sanitizer: Sanitizer; Tools: Tools; API: API; Caret: Caret; diff --git a/test/cypress/support/commands.ts b/test/cypress/support/commands.ts index e23e8431..7b8f8da0 100644 --- a/test/cypress/support/commands.ts +++ b/test/cypress/support/commands.ts @@ -36,3 +36,29 @@ 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; +}); diff --git a/test/cypress/support/index.d.ts b/test/cypress/support/index.d.ts index 5a9e8631..f675962a 100644 --- a/test/cypress/support/index.d.ts +++ b/test/cypress/support/index.d.ts @@ -14,6 +14,16 @@ 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 } interface ApplicationWindow { diff --git a/test/cypress/tests/modules/Tools.spec.ts b/test/cypress/tests/modules/Tools.spec.ts index 4361fb33..24a884f8 100644 --- a/test/cypress/tests/modules/Tools.spec.ts +++ b/test/cypress/tests/modules/Tools.spec.ts @@ -81,17 +81,28 @@ describe('Tools module', () => { module = constructModule({ defaultBlock: 'withoutPrepare', tools: { - withSuccessfulPrepare: class { - // eslint-disable-next-line @typescript-eslint/no-empty-function - public static prepare(): void {} - } as any, + 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 { - } as any, + withoutPrepare: { + class: class {} as any, + inlineToolbar: false, + tunes: false, + }, + blockTool: { + class: class {} as any, + inlineToolbar: true, + }, inlineTool: class { public static isInline = true @@ -104,6 +115,18 @@ describe('Tools module', () => { // 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 */ @@ -113,6 +136,9 @@ describe('Tools module', () => { blockTune: class { public static isTune = true; } as any, + blockTune2: class { + public static isTune = true; + } as any, unavailableBlockTune: class { public static isTune = true; @@ -121,6 +147,8 @@ describe('Tools module', () => { } } as any, }, + inlineToolbar: ['inlineTool2', 'inlineTool'], + tunes: ['blockTune2', 'blockTune'], }); await module.prepare(); @@ -175,6 +203,42 @@ describe('Tools module', () => { 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 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']); + + 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', () => { diff --git a/test/cypress/tests/sanitisation.spec.ts b/test/cypress/tests/sanitisation.spec.ts new file mode 100644 index 00000000..00808964 --- /dev/null +++ b/test/cypress/tests/sanitisation.spec.ts @@ -0,0 +1,65 @@ +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.eq('This text should be bold.'); + }); + }); + + 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/tools/BlockTool.spec.ts b/test/cypress/tests/tools/BlockTool.spec.ts index 89853ff7..da0b33bf 100644 --- a/test/cypress/tests/tools/BlockTool.spec.ts +++ b/test/cypress/tests/tools/BlockTool.spec.ts @@ -2,8 +2,10 @@ 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', () => { +describe.only('BlockTool', () => { /** * Mock for BlockTool constructor options */ @@ -11,7 +13,9 @@ describe('BlockTool', () => { name: 'blockTool', constructable: class { public static sanitize = { - rule1: 'rule1', + rule1: { + div: true, + }, } public static toolbox = { @@ -131,10 +135,87 @@ describe('BlockTool', () => { }); }); - it('.sanitizeConfig should return correct value', () => { - const tool = new BlockTool(options as any); + context('.sanitizeConfig', () => { + it('should return correct value', () => { + const tool = new BlockTool(options as any); - expect(tool.sanitizeConfig).to.be.deep.eq(options.constructable.sanitize); + 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', () => { @@ -179,10 +260,24 @@ describe('BlockTool', () => { expect(tool.pasteConfig).to.be.deep.eq(options.constructable.pasteConfig); }); - it('.enabledInlineTools should return correct value', () => { - const tool = new BlockTool(options as any); + context('.enabledInlineTools', () => { + it('should return correct value', () => { + const tool = new BlockTool(options as any); - expect(tool.enabledInlineTools).to.be.deep.eq(options.config.inlineToolbar); + 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', () => { diff --git a/test/cypress/tsconfig.json b/test/cypress/tsconfig.json index 1a73982a..a996c048 100644 --- a/test/cypress/tsconfig.json +++ b/test/cypress/tsconfig.json @@ -4,7 +4,8 @@ "lib": ["dom", "es2017", "es2018"], "moduleResolution": "node", "resolveJsonModule": true, - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true }, "include": [ "../../**/*.ts" 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/yarn.lock b/yarn.lock index efbfc224..7fb92268 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1700,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" @@ -1921,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" @@ -1987,6 +1997,14 @@ 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" @@ -2014,6 +2032,11 @@ argparse@^1.0.7: 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" @@ -2229,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" @@ -2280,7 +2308,7 @@ 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: @@ -2317,6 +2345,11 @@ browser-resolve@^2.0.0: 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" @@ -2652,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" @@ -2755,6 +2793,21 @@ cheerio@^0.19.0: htmlparser2 "~3.8.1" lodash "^3.2.0" +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" @@ -2867,6 +2920,15 @@ cliui@^6.0.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" @@ -3346,6 +3408,13 @@ cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" +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" @@ -3458,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" @@ -3538,6 +3612,11 @@ detective@^5.2.0: 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" @@ -3784,6 +3863,11 @@ escalade@^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" @@ -4282,6 +4366,14 @@ find-cache-dir@^3.2.0, 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" @@ -4318,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" @@ -4431,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" @@ -4453,7 +4555,7 @@ get-assigned-identifiers@^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.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" @@ -4510,7 +4612,14 @@ glob-parent@^5.0.0, glob-parent@^5.1.0: dependencies: is-glob "^4.0.1" -glob@^7.1.0, 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: @@ -4607,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" @@ -4699,6 +4813,11 @@ hasha@^5.0.0: 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" @@ -5007,6 +5126,13 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" +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" @@ -5119,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: @@ -5167,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" @@ -5352,6 +5478,13 @@ js-yaml@3.14.1: 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" @@ -5635,6 +5768,13 @@ 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" @@ -5698,6 +5838,13 @@ lodash@^4.17.20: 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" @@ -5717,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" @@ -5935,7 +6075,7 @@ minimalistic-crypto-utils@^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: @@ -6014,6 +6154,37 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +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" @@ -6059,6 +6230,11 @@ 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" @@ -6067,6 +6243,11 @@ 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== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -6158,7 +6339,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" @@ -6388,6 +6569,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" @@ -6406,6 +6594,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" @@ -6580,7 +6775,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" @@ -7383,7 +7578,7 @@ ramda@~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: @@ -7485,6 +7680,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" @@ -7852,6 +8054,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" @@ -8172,7 +8381,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== @@ -8302,6 +8511,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" @@ -8390,6 +8604,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" @@ -9057,18 +9278,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" @@ -9080,6 +9308,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" @@ -9105,6 +9338,15 @@ wrap-ansi@^6.2.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" @@ -9132,6 +9374,11 @@ y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" +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" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -9146,6 +9393,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" @@ -9160,6 +9412,21 @@ yargs-parser@^18.1.1, yargs-parser@^18.1.2: 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" @@ -9176,6 +9443,19 @@ 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" @@ -9200,3 +9480,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== From 9cdda110dac04c70cf137ef324085e5326c448ec Mon Sep 17 00:00:00 2001 From: Tomoyuki Hata <7702653+hata6502@users.noreply.github.com> Date: Fri, 9 Apr 2021 03:32:49 +0900 Subject: [PATCH 08/35] Refactoring based on LGTM (#1635) * Refactoring based on LGTM * Fixed jsdoc/no-undefined-types Co-authored-by: George Berezhnoy --- .eslintrc | 9 ++++++++- docs/CHANGELOG.md | 2 +- src/components/block/index.ts | 4 +--- src/components/inline-tools/inline-tool-link.ts | 2 +- src/components/modules/caret.ts | 2 +- src/components/modules/toolbar/conversion.ts | 1 - src/components/modules/toolbar/toolbox.ts | 1 - src/components/modules/tools.ts | 8 +------- 8 files changed, 13 insertions(+), 16 deletions(-) diff --git a/.eslintrc b/.eslintrc index 3e2802ac..332b8021 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,7 +7,14 @@ * 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": [ + "API", + "BlockToolConstructable", + "EditorConfig", + "Tool", + "ToolSettings" + ]}] }, "settings": { "jsdoc": { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9d14d38f..32363be5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,10 +2,10 @@ ### 2.20.1 - - `Fix` — Fix sanitisation problem with Inline Tools [#1631](https://github.com/codex-team/editor.js/issues/1631) - `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). ### 2.20.0 diff --git a/src/components/block/index.ts b/src/components/block/index.ts index 15a95b61..7afedca2 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -1,12 +1,10 @@ import { BlockAPI as BlockAPIInterface, BlockTool as IBlockTool, - BlockToolConstructable, BlockToolData, BlockTune as IBlockTune, SanitizerConfig, - ToolConfig, - ToolSettings + ToolConfig } from '../../../types'; import { SavedData } from '../../../types/data-formats'; 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/caret.ts b/src/components/modules/caret.ts index 9fd4cfd5..94b54711 100644 --- a/src/components/modules/caret.ts +++ b/src/components/modules/caret.ts @@ -531,7 +531,7 @@ export default class Caret extends Module { * If there is no child node, append empty one */ if (fragment.childNodes.length === 0) { - fragment.appendChild(new Text('')); + fragment.appendChild(new Text()); } const lastChild = fragment.lastChild; diff --git a/src/components/modules/toolbar/conversion.ts b/src/components/modules/toolbar/conversion.ts index 44743fa5..c40aed18 100644 --- a/src/components/modules/toolbar/conversion.ts +++ b/src/components/modules/toolbar/conversion.ts @@ -1,6 +1,5 @@ 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'; diff --git a/src/components/modules/toolbar/toolbox.ts b/src/components/modules/toolbar/toolbox.ts index db49be81..62916d87 100644 --- a/src/components/modules/toolbar/toolbox.ts +++ b/src/components/modules/toolbar/toolbox.ts @@ -1,7 +1,6 @@ import Module from '../../__module'; import $ from '../../dom'; import * as _ from '../../utils'; -import { BlockToolConstructable, EditorConfig } from '../../../../types'; import Flipper from '../../flipper'; import { BlockToolAPI } from '../../block'; import I18n from '../../i18n'; diff --git a/src/components/modules/tools.ts b/src/components/modules/tools.ts index c1aa30ca..fdd7c12e 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -1,13 +1,7 @@ import Paragraph from '../../tools/paragraph/dist/bundle'; import Module from '../__module'; import * as _ from '../utils'; -import { - EditorConfig, - SanitizerConfig, - Tool, - 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'; From aa5a3d05f3cd7adc3ab7f1aa719113d31716b362 Mon Sep 17 00:00:00 2001 From: Tomoyuki Hata <7702653+hata6502@users.noreply.github.com> Date: Fri, 9 Apr 2021 03:38:08 +0900 Subject: [PATCH 09/35] Create a new block when clicked at the bottom (#1605) * Create a new block when clicked at the bottom * Fix for 2.20.0 Co-authored-by: George Berezhnoy --- docs/CHANGELOG.md | 1 + src/components/modules/ui.ts | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 32363be5..ceeb9684 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,7 @@ ### 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) - `Refactoring` - The Sanitizer module is util now. - `Refactoring` - Tooltip module is util now. diff --git a/src/components/modules/ui.ts b/src/components/modules/ui.ts index bc81516c..8719d6a9 100644 --- a/src/components/modules/ui.ts +++ b/src/components/modules/ui.ts @@ -691,10 +691,28 @@ 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 (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(); } /** From d5aaa56ca00403c2b776eb39963958240163241a Mon Sep 17 00:00:00 2001 From: Liam Martens Date: Thu, 8 Apr 2021 21:13:00 +0200 Subject: [PATCH 10/35] [Bug] n.closest() not a function #1317 (#1600) * Release 2.19.2 (#1597) Save 2.19.2 changes * 1317 The selection node sometimes ends up being the ShadowRoot which does not have the closest method. In case it is the shadowroot - I believe we can assume the selection is not at the editor zone Co-authored-by: Peter Savchenko Co-authored-by: George Berezhnoy --- src/components/selection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/selection.ts b/src/components/selection.ts index 32c12ee0..6a6ea6a3 100644 --- a/src/components/selection.ts +++ b/src/components/selection.ts @@ -125,7 +125,7 @@ export default class SelectionUtils { let editorZone = null; - if (selectedNode) { + if (selectedNode && selectedNode instanceof Element) { editorZone = selectedNode.closest(`.${SelectionUtils.CSS.editorZone}`); } From 14acef136c40b05c5771c26119f6d94742b969ad Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Thu, 8 Apr 2021 22:19:49 +0300 Subject: [PATCH 11/35] Fix copy in FireFox (#1632) * Fix copy in FireFox * Add test cases * Eslint fix * Improve readability * fix eslint --- docs/CHANGELOG.md | 1 + src/components/block/index.ts | 2 +- src/components/modules/blockEvents.ts | 21 +- src/components/modules/blockSelection.ts | 15 +- test/cypress/support/commands.ts | 52 +++++ test/cypress/support/index.d.ts | 16 ++ test/cypress/tests/copy-paste.spec.ts | 274 +++++++++++++++++++++++ 7 files changed, 368 insertions(+), 13 deletions(-) create mode 100644 test/cypress/tests/copy-paste.spec.ts diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ceeb9684..16da6f8f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,7 @@ - `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). diff --git a/src/components/block/index.ts b/src/components/block/index.ts index 7afedca2..4d65bdc4 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -504,7 +504,7 @@ export default class Block { /** * call Tool's method with the instance context */ - if (this.toolInstance[methodName] && this.toolInstance[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. ' + diff --git a/src/components/modules/blockEvents.ts b/src/components/modules/blockEvents.ts index 89cd5077..e7f9336c 100644 --- a/src/components/modules/blockEvents.ts +++ b/src/components/modules/blockEvents.ts @@ -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); + }); } /** diff --git a/src/components/modules/blockSelection.ts b/src/components/modules/blockSelection.ts index 96ada11b..682b7aeb 100644 --- a/src/components/modules/blockSelection.ts +++ b/src/components/modules/blockSelection.ts @@ -286,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 */ @@ -305,15 +305,22 @@ export default class BlockSelection extends Module { 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/test/cypress/support/commands.ts b/test/cypress/support/commands.ts index 7b8f8da0..fc829c09 100644 --- a/test/cypress/support/commands.ts +++ b/test/cypress/support/commands.ts @@ -62,3 +62,55 @@ Cypress.Commands.add('paste', { 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; +}); diff --git a/test/cypress/support/index.d.ts b/test/cypress/support/index.d.ts index f675962a..7d83933e 100644 --- a/test/cypress/support/index.d.ts +++ b/test/cypress/support/index.d.ts @@ -24,6 +24,22 @@ declare global { * @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 }>; } interface ApplicationWindow { diff --git a/test/cypress/tests/copy-paste.spec.ts b/test/cypress/tests/copy-paste.spec.ts new file mode 100644 index 00000000..97f6a0ec --- /dev/null +++ b/test/cypress/tests/copy-paste.spec.ts @@ -0,0 +1,274 @@ +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', () => { + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .click() + .paste({ + 'text/plain': 'Some plain text', + }) + .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]') + .get('img') + .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.eq('

First block

Second block

'); + 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).to.deep.eq({ text: 'First block' }); + expect(data[1].tool).to.eq('paragraph'); + expect(data[1].data).to.deep.eq({ text: '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.eq('

First block

Second block

'); + 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).to.deep.eq({ text: 'First block' }); + expect(data[1].tool).to.eq('paragraph'); + expect(data[1].data).to.deep.eq({ text: '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); + }); + }); + }); + }); +}); From d5217067a6ae0dac0550594651e1c0b50e344bdc Mon Sep 17 00:00:00 2001 From: Tomoyuki Hata <7702653+hata6502@users.noreply.github.com> Date: Fri, 9 Apr 2021 04:53:11 +0900 Subject: [PATCH 12/35] Refactoring based on ESLint (#1637) * Refactoring based on ESLint * Update .eslintrc Co-authored-by: George Berezhnoy --- .eslintrc | 1 + docs/CHANGELOG.md | 1 + src/components/__module.ts | 5 ++--- src/components/modules/saver.ts | 9 ++++----- src/components/tools/base.ts | 9 ++------- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.eslintrc b/.eslintrc index 332b8021..6704e3b7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,6 +9,7 @@ "import/no-duplicates": ["warn"], "@typescript-eslint/triple-slash-reference": ["off"], "jsdoc/no-undefined-types": ["warn", {"definedTypes": [ + "ConstructorOptions", "API", "BlockToolConstructable", "EditorConfig", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 16da6f8f..14571e87 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,7 @@ - `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 diff --git a/src/components/__module.ts b/src/components/__module.ts index 569470f5..f18de55f 100644 --- a/src/components/__module.ts +++ b/src/components/__module.ts @@ -92,9 +92,8 @@ export default class Module { /** * @class - * @param {object} moduleConfiguration - Module Configuration - * @param {EditorConfig} moduleConfiguration.config - Editor's config - * @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher + * + * @param {ModuleConfig} - Module config */ constructor({ config, eventsDispatcher }: ModuleConfig) { if (new.target === Module) { diff --git a/src/components/modules/saver.ts b/src/components/modules/saver.ts index 1263562b..ba3cfd8c 100644 --- a/src/components/modules/saver.ts +++ b/src/components/modules/saver.ts @@ -107,15 +107,14 @@ export default class Saver extends Module { return; } - const output: any = { + const output = { type: tool, data, + ...!_.isEmpty(tunes) && { + tunes, + }, }; - if (!_.isEmpty(tunes)) { - output.tunes = tunes; - } - blocks.push(output); }); diff --git a/src/components/tools/base.ts b/src/components/tools/base.ts index 1f36ff37..a4147979 100644 --- a/src/components/tools/base.ts +++ b/src/components/tools/base.ts @@ -175,13 +175,7 @@ export default abstract class BaseTool { /** * @class * - * @param name - Tool name - * @param constructable - Tool constructable blueprint - * @param config - user specified Tool config - * @param api - EditorJS API module - * @param defaultTool - default Tool name - * @param isInternal - is current Tool internal - * @param defaultPlaceholder - default user specified placeholder + * @param {ConstructorOptions} - Constructor options */ constructor({ name, @@ -278,5 +272,6 @@ export default abstract class BaseTool { * * @param args */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any public abstract create(...args: any[]): Type; } From 3b20c4a139faf46a91db84f7f1998ea1e2a9b511 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Apr 2021 22:58:15 +0300 Subject: [PATCH 13/35] Bump y18n from 4.0.0 to 4.0.3 (#1639) Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.3. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/y18n-v4.0.3/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/compare/v4.0.0...y18n-v4.0.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: George Berezhnoy --- yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7fb92268..590d15eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9371,8 +9371,9 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: 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" From 2c77edb4571f581104b6896d169c0940210b6124 Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Thu, 8 Apr 2021 23:15:42 +0300 Subject: [PATCH 14/35] Add cypress workflow (#1638) * Add cypress workflow * Setup different browsers * Pull tools * Update ff image * Fix test * Adopt tests * Fix eslint --- .github/workflows/cypress.yml | 33 ++++++++++++++++++++++ test/cypress/tests/copy-paste.spec.ts | 14 +++++---- test/cypress/tests/sanitisation.spec.ts | 2 +- test/cypress/tests/tools/BlockTool.spec.ts | 2 +- 4 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/cypress.yml 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/test/cypress/tests/copy-paste.spec.ts b/test/cypress/tests/copy-paste.spec.ts index 97f6a0ec..beae0177 100644 --- a/test/cypress/tests/copy-paste.spec.ts +++ b/test/cypress/tests/copy-paste.spec.ts @@ -18,12 +18,14 @@ describe('Copy pasting from Editor', () => { 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'); }); @@ -161,7 +163,7 @@ describe('Copy pasting from Editor', () => { }) .copy() .then(clipboardData => { - expect(clipboardData['text/html']).to.eq('

First block

Second block

'); + expect(clipboardData['text/html']).to.match(/

First block(
)?<\/p>

Second block(
)?<\/p>/); expect(clipboardData['text/plain']).to.eq(`First block\n\nSecond block`); /** @@ -174,9 +176,9 @@ describe('Copy pasting from Editor', () => { const data = JSON.parse(clipboardData['application/x-editor-js']); expect(data[0].tool).to.eq('paragraph'); - expect(data[0].data).to.deep.eq({ text: 'First block' }); + expect(data[0].data.text).to.match(/First block(
)?/); expect(data[1].tool).to.eq('paragraph'); - expect(data[1].data).to.deep.eq({ text: 'Second block' }); + expect(data[1].data.text).to.match(/Second block(
)?/); }); }); }); @@ -214,7 +216,7 @@ describe('Copy pasting from Editor', () => { }) .cut() .then(clipboardData => { - expect(clipboardData['text/html']).to.eq('

First block

Second block

'); + expect(clipboardData['text/html']).to.match(/

First block(
)?<\/p>

Second block(
)?<\/p>/); expect(clipboardData['text/plain']).to.eq(`First block\n\nSecond block`); /** @@ -227,9 +229,9 @@ describe('Copy pasting from Editor', () => { const data = JSON.parse(clipboardData['application/x-editor-js']); expect(data[0].tool).to.eq('paragraph'); - expect(data[0].data).to.deep.eq({ text: 'First block' }); + expect(data[0].data.text).to.match(/First block(
)?/); expect(data[1].tool).to.eq('paragraph'); - expect(data[1].data).to.deep.eq({ text: 'Second block' }); + expect(data[1].data.text).to.match(/Second block(
)?/); }); }); diff --git a/test/cypress/tests/sanitisation.spec.ts b/test/cypress/tests/sanitisation.spec.ts index 00808964..2c40206f 100644 --- a/test/cypress/tests/sanitisation.spec.ts +++ b/test/cypress/tests/sanitisation.spec.ts @@ -44,7 +44,7 @@ describe('Output sanitisation', () => { const text = output.blocks[0].data.text; - expect(text).to.eq('This text should be bold.'); + expect(text).to.match(/This text should be bold\.(
)?<\/b>/); }); }); diff --git a/test/cypress/tests/tools/BlockTool.spec.ts b/test/cypress/tests/tools/BlockTool.spec.ts index da0b33bf..e6306a18 100644 --- a/test/cypress/tests/tools/BlockTool.spec.ts +++ b/test/cypress/tests/tools/BlockTool.spec.ts @@ -5,7 +5,7 @@ import BlockTool from '../../../../src/components/tools/block'; import InlineTool from '../../../../src/components/tools/inline'; import ToolsCollection from '../../../../src/components/tools/collection'; -describe.only('BlockTool', () => { +describe('BlockTool', () => { /** * Mock for BlockTool constructor options */ From 32ac52a62fb1e36dddaad1f9fe224e46a931f056 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Apr 2021 13:30:51 +0300 Subject: [PATCH 15/35] Bump version up to 2.20.1-rc.0 (#1608) * Bump version * Bump version * Bump to 2.20.1 Co-authored-by: github-actions Co-authored-by: Georgy Berezhnoy --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 484e0b05..31fb793d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.20.0", + "version": "2.20.1", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editor.js", "types": "./types/index.d.ts", From 04749ed098428c1b3e97807d5a6d36bb8a00af12 Mon Sep 17 00:00:00 2001 From: Tomoyuki Hata <7702653+hata6502@users.noreply.github.com> Date: Wed, 14 Apr 2021 23:49:22 +0900 Subject: [PATCH 16/35] Prevent the leak of codex-tooltip when Editor.js is destroyed (#1590) * Prevent the leak of codex-tooltip when Editor.js is destroyed * Cover destroy() function Thanks, @hata6502 --- docs/CHANGELOG.md | 5 ++++- package.json | 2 +- src/components/modules/api/tooltip.ts | 7 +++++++ src/components/modules/toolbar/index.ts | 1 + src/components/modules/toolbar/inline.ts | 1 + src/components/modules/toolbar/toolbox.ts | 1 + src/components/utils.ts | 4 ++++ src/components/utils/sanitizer.ts | 4 ---- src/components/utils/tooltip.ts | 7 +++++++ yarn.lock | 8 ++++---- 10 files changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 14571e87..bf3b9198 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### 2.20.2 + +- `Fix` - Prevent the leak of codex-tooltip when Editor.js is destroyed [#1475](https://github.com/codex-team/editor.js/issues/1475). + ### 2.20.1 - `Fix` - Create a new block when clicked at the bottom [#1588](https://github.com/codex-team/editor.js/issues/1588). @@ -14,7 +18,6 @@ - `New` — [Block Tunes API](block-tunes.md) added - ### 2.19.3 - `Fix` — Ignore error raised by Shortcut module diff --git a/package.json b/package.json index 31fb793d..5b0d6708 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,6 @@ }, "dependencies": { "codex-notifier": "^1.1.2", - "codex-tooltip": "^1.0.1" + "codex-tooltip": "^1.0.2" } } diff --git a/src/components/modules/api/tooltip.ts b/src/components/modules/api/tooltip.ts index 74796baa..30bb6585 100644 --- a/src/components/modules/api/tooltip.ts +++ b/src/components/modules/api/tooltip.ts @@ -29,6 +29,13 @@ export default class TooltipAPI extends Module { this.tooltip = new Tooltip(); } + /** + * Destroy Module + */ + public destroy(): void { + this.tooltip.destroy(); + } + /** * Available methods */ diff --git a/src/components/modules/toolbar/index.ts b/src/components/modules/toolbar/index.ts index 7ed3e28c..56953d91 100644 --- a/src/components/modules/toolbar/index.ts +++ b/src/components/modules/toolbar/index.ts @@ -406,5 +406,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 a100f15c..4c3d29b3 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -298,6 +298,7 @@ export default class InlineToolbar extends Module { } this.removeAllNodes(); + this.tooltip.destroy(); } /** diff --git a/src/components/modules/toolbar/toolbox.ts b/src/components/modules/toolbar/toolbox.ts index 62916d87..4c9eb175 100644 --- a/src/components/modules/toolbar/toolbox.ts +++ b/src/components/modules/toolbar/toolbox.ts @@ -128,6 +128,7 @@ export default class Toolbox extends Module { this.removeAllNodes(); this.removeAllShortcuts(); + this.tooltip.destroy(); } /** diff --git a/src/components/utils.ts b/src/components/utils.ts index 8e17379d..5a82dec7 100644 --- a/src/components/utils.ts +++ b/src/components/utils.ts @@ -661,6 +661,8 @@ export function cacheable( /** * Override get or value descriptor property to cache return value + * + * @param args */ descriptor[propertyToOverride] = function (...args: Arguments): Value { /** @@ -675,6 +677,8 @@ export function cacheable( /** * If get accessor has been overridden, we need to override set accessor to clear cache + * + * @param value */ if (propertyToOverride === 'get' && descriptor.set) { const originalSet = descriptor.set; diff --git a/src/components/utils/sanitizer.ts b/src/components/utils/sanitizer.ts index 1eb42b58..692c05b5 100644 --- a/src/components/utils/sanitizer.ts +++ b/src/components/utils/sanitizer.ts @@ -117,7 +117,6 @@ function deepSanitize(dataToSanitize: object | string, rules: SanitizerConfig): } } - /** * Clean array * @@ -186,6 +185,3 @@ function cleanOneItem(taintString: string, rule: SanitizerConfig|boolean): strin function isRule(config: SanitizerConfig): boolean { return _.isObject(config) || _.isBoolean(config) || _.isFunction(config); } - - - diff --git a/src/components/utils/tooltip.ts b/src/components/utils/tooltip.ts index 010039d1..a0ace016 100644 --- a/src/components/utils/tooltip.ts +++ b/src/components/utils/tooltip.ts @@ -17,6 +17,13 @@ export default class Tooltip { */ 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/yarn.lock b/yarn.lock index 590d15eb..d578f65d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2956,10 +2956,10 @@ 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" From 2996affb0a0e07184bcf261e93734dce64a88afb Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Wed, 14 Apr 2021 22:07:16 +0300 Subject: [PATCH 17/35] Fix #1640 (#1649) * Fix #1640 * Update changelog * Fix typo --- docs/CHANGELOG.md | 1 + src/components/modules/tools.ts | 10 +++++++--- test/cypress/tests/modules/Tools.spec.ts | 19 ++++++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bf3b9198..e15bb176 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,7 @@ ### 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). ### 2.20.1 diff --git a/src/components/modules/tools.ts b/src/components/modules/tools.ts index fdd7c12e..29c497f2 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -354,23 +354,27 @@ export default class Tools extends Module { * @param tool — Block Tool */ private assignBlockTunesToBlockTool(tool: BlockTool): void { - if (tool.enabledInlineTools === false) { + if (tool.enabledBlockTunes === false) { return; } if (Array.isArray(tool.enabledBlockTunes)) { - tool.tunes = new ToolsCollection( + 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)) { - tool.tunes = new ToolsCollection( + const userTunes = new ToolsCollection( this.config.tunes.map(name => [name, this.blockTunes.get(name)]) ); + tool.tunes = new ToolsCollection([...userTunes, ...this.blockTunes.internalTools]); + return; } diff --git a/test/cypress/tests/modules/Tools.spec.ts b/test/cypress/tests/modules/Tools.spec.ts index 24a884f8..e6719825 100644 --- a/test/cypress/tests/modules/Tools.spec.ts +++ b/test/cypress/tests/modules/Tools.spec.ts @@ -103,6 +103,7 @@ describe('Tools module', () => { class: class {} as any, inlineToolbar: true, }, + blockToolWithoutSettings: class {} as any, inlineTool: class { public static isInline = true @@ -204,12 +205,28 @@ describe('Tools module', () => { 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('deleteTune')).to.be.true; + expect(tool.tunes.has('moveUpTune')).to.be.true; + expect(tool.tunes.has('moveDownTune')).to.be.true; + }); + + it('Block Tools should contain default tunes', () => { + const tool = module.blockTools.get('blockTool'); + + expect(tool.tunes.has('deleteTune')).to.be.true; + expect(tool.tunes.has('moveUpTune')).to.be.true; + expect(tool.tunes.has('moveDownTune')).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']); + expect(Array.from(tool.tunes.keys())).to.be.deep.eq(['blockTune2', 'blockTune', 'moveUpTune', 'deleteTune', 'moveDownTune']); tool = module.blockTools.get('withSuccessfulPrepare'); From 472d950b0b8a84638591c79e0db067559614c823 Mon Sep 17 00:00:00 2001 From: Taly Date: Thu, 15 Apr 2021 20:20:21 +0300 Subject: [PATCH 18/35] refactoring: notifier is util now (#1628) * Notifier is util now * Update CHANGELOG.md * Update notifier.ts * Update cypress.yml * Revert "Update cypress.yml" This reverts commit a2c8331314e3139c89d23590041f05d876869840. * Update CHANGELOG.md --- docs/CHANGELOG.md | 1 + src/components/modules/api/notifier.ts | 29 +++++++++++++++++-- src/components/{modules => utils}/notifier.ts | 6 ++-- src/types-internal/editor-modules.d.ts | 2 -- 4 files changed, 29 insertions(+), 9 deletions(-) rename src/components/{modules => utils}/notifier.ts (83%) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e15bb176..37e47f7a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,7 @@ - `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 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/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/types-internal/editor-modules.d.ts b/src/types-internal/editor-modules.d.ts index 3449adea..8d882f4e 100644 --- a/src/types-internal/editor-modules.d.ts +++ b/src/types-internal/editor-modules.d.ts @@ -5,7 +5,6 @@ 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 DragNDrop from '../components/modules/dragNDrop'; import ModificationsObserver from '../components/modules/modificationsObserver'; import Renderer from '../components/modules/renderer'; @@ -52,7 +51,6 @@ export interface EditorModules { API: API; Caret: Caret; Saver: Saver; - Notifier: Notifier; BlockManager: BlockManager; BlocksAPI: BlocksAPI; CaretAPI: CaretAPI; From 6ac58c3935958c74f3e49c67280c7ab589318f80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Apr 2021 21:32:55 +0300 Subject: [PATCH 19/35] Bump version up to 2.20.2 (#1655) * Bump version * Update version Co-authored-by: github-actions Co-authored-by: Georgy Berezhnoy --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5b0d6708..d877489b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.20.1", + "version": "2.20.2", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editor.js", "types": "./types/index.d.ts", From ef0c7d76a7eff1a3dfe82f16b87f1dbe35274595 Mon Sep 17 00:00:00 2001 From: Taly Date: Wed, 21 Apr 2021 19:21:26 +0300 Subject: [PATCH 20/35] Publish the latest version with a next tag too (#1668) resolves #1659 --- .github/workflows/publish-package-to-npm.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-package-to-npm.yml b/.github/workflows/publish-package-to-npm.yml index 008c90f7..61195862 100644 --- a/.github/workflows/publish-package-to-npm.yml +++ b/.github/workflows/publish-package-to-npm.yml @@ -28,8 +28,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: Publish the package with a LATEST tag if this is not a prerelease version + if: !github.event.release.prerelease + run: yarn publish --access=public --tag=latest env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From de364175eb85c6358cc524c0a897b736d46c67b0 Mon Sep 17 00:00:00 2001 From: Jean-Gab Date: Tue, 27 Apr 2021 09:33:00 -0400 Subject: [PATCH 21/35] Unique ids in blocks with nanoid (#1667) * feat: Add unique ids for each block * fix: Improve code based on code review * feat(block ids): Use nanoid library for block id generation * Remove unused files * Add tests * Fix lint & test * fix: Remove unnecessary id generation, use nanoid(10) to shorten the id, add changelog and some documentation Also improved some documentation along the lines and fixed linting * Update copy-paste.spec.ts * fix id generation, add api method * Update blocks.spec.ts * update tests Co-authored-by: cobb Co-authored-by: George Berezhnoy Co-authored-by: Georgy Berezhnoy Co-authored-by: Peter Savchenko --- docs/CHANGELOG.md | 3 + example/example-dev.html | 14 +++ package.json | 5 +- src/components/block/api.ts | 8 ++ src/components/block/index.ts | 17 +++- src/components/modules/api/blocks.ts | 18 ++++ src/components/modules/blockManager.ts | 30 ++++-- src/components/modules/paste.ts | 2 +- src/components/modules/renderer.ts | 7 +- src/components/modules/saver.ts | 3 +- src/components/utils.ts | 10 ++ test/cypress/support/commands.ts | 13 ++- test/cypress/support/index.d.ts | 9 +- test/cypress/tests/api/blocks.spec.ts | 53 ++++++++++ test/cypress/tests/block-ids.spec.ts | 130 +++++++++++++++++++++++++ test/cypress/tests/copy-paste.spec.ts | 3 +- types/api/block.d.ts | 5 + types/api/blocks.d.ts | 6 ++ types/data-formats/block-data.d.ts | 2 + types/data-formats/output-data.d.ts | 8 +- yarn.lock | 5 + 21 files changed, 333 insertions(+), 18 deletions(-) create mode 100644 test/cypress/tests/api/blocks.spec.ts create mode 100644 test/cypress/tests/block-ids.spec.ts diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 37e47f7a..3d567102 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### 2.21.0 +- `New` - Blocks now have a unique ID [#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) diff --git a/example/example-dev.html b/example/example-dev.html index 824bc4f5..cc9123f7 100644 --- a/example/example-dev.html +++ b/example/example-dev.html @@ -197,6 +197,7 @@ data: { blocks: [ { + id: "zcKCF1S7X8", type: "header", data: { text: "Editor.js", @@ -205,12 +206,14 @@ }, { type : 'paragraph', + id: "b6ji-DvaKb", 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 @@ -218,6 +221,7 @@ }, { type : 'list', + id: "SSBSguGvP7", data : { items : [ { @@ -238,6 +242,7 @@ }, { type: "header", + id: "QZFox1m_ul", data: { text: "What does it mean «block-styled editor»", level: 3 @@ -245,18 +250,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 @@ -264,34 +272,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: '', diff --git a/package.json b/package.json index d877489b..431ce100 100644 --- a/package.json +++ b/package.json @@ -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", @@ -87,6 +87,7 @@ }, "dependencies": { "codex-notifier": "^1.1.2", - "codex-tooltip": "^1.0.2" + "codex-tooltip": "^1.0.2", + "nanoid": "^3.1.22" } } 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 4d65bdc4..18439c9d 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -23,6 +23,11 @@ import ToolsCollection from '../tools/collection'; * Interface describes Block class constructor argument */ interface BlockConstructorOptions { + /** + * Block's id. Should be passed for existed block, and omitted for a new one. + */ + id?: string; + /** * Initial Block data */ @@ -98,6 +103,11 @@ export default class Block { }; } + /** + * Block unique identifier + */ + public id: string; + /** * Block Tool`s name */ @@ -206,13 +216,14 @@ export default class Block { /** * @param {object} options - block constructor options + * @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({ + id = _.generateBlockId(), data, tool, api, @@ -220,6 +231,7 @@ export default class Block { tunesData, }: BlockConstructorOptions) { this.name = tool.name; + this.id = id; this.settings = tool.settings; this.config = tool.settings.config || {}; this.api = api; @@ -567,6 +579,7 @@ export default class Block { measuringEnd = window.performance.now(); return { + id: this.id, tool: this.name, data: finishedExtraction, tunes: tunesData, diff --git a/src/components/modules/api/blocks.ts b/src/components/modules/api/blocks.ts index a65db8b0..7da432e4 100644 --- a/src/components/modules/api/blocks.ts +++ b/src/components/modules/api/blocks.ts @@ -23,6 +23,7 @@ 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), @@ -66,6 +67,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 * diff --git a/src/components/modules/blockManager.ts b/src/components/modules/blockManager.ts index 5614aa83..e82ba86f 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -216,6 +216,7 @@ 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} @@ -223,11 +224,13 @@ export default class BlockManager extends Module { public composeBlock({ tool: name, data = {}, + id = undefined, tunes: tunesData = {}, - }: {tool: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}}): Block { + }: {tool: string; id?: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}}): Block { const readOnly = this.Editor.ReadOnly.isEnabled; const tool = this.Editor.Tools.blockTools.get(name); const block = new Block({ + id, data, tool, api: this.Editor.API, @@ -246,15 +249,17 @@ 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, @@ -262,6 +267,7 @@ export default class BlockManager extends Module { replace = false, tunes = {}, }: { + id?: string; tool?: string; data?: BlockToolData; index?: number; @@ -276,6 +282,7 @@ export default class BlockManager extends Module { } const block = this.composeBlock({ + id, tool, data, tunes, @@ -514,6 +521,17 @@ export default class BlockManager extends Module { return this._blocks[index]; } + /** + * Returns the Block by passed id + * + * @param id - id of block to get + * + * @returns {Block} + */ + public getBlockById(id): Block { + return this._blocks.array.find(block => block.id === id); + } + /** * Get Block instance by html element * diff --git a/src/components/modules/paste.ts b/src/components/modules/paste.ts index a9280bd0..61f794af 100644 --- a/src/components/modules/paste.ts +++ b/src/components/modules/paste.ts @@ -739,7 +739,7 @@ export default class Paste extends Module { * * @returns {void} */ - private insertEditorJSData(blocks: Pick[]): void { + private insertEditorJSData(blocks: Pick[]): void { const { BlockManager, Caret, Tools } = this.Editor; const sanitizedBlocks = sanitizeBlocks(blocks, (name) => Tools.blockTools.get(name).sanitizeConfig diff --git a/src/components/modules/renderer.ts b/src/components/modules/renderer.ts index b9651b01..0e87e751 100644 --- a/src/components/modules/renderer.ts +++ b/src/components/modules/renderer.ts @@ -23,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!' @@ -64,11 +66,12 @@ export default class Renderer extends Module { */ public async insertBlock(item: OutputBlockData): Promise { const { Tools, BlockManager } = this.Editor; - const { type: tool, data, tunes } = item; + const { type: tool, data, tunes, id } = item; if (Tools.available.has(tool)) { try { BlockManager.insert({ + id, tool, data, tunes, @@ -81,6 +84,7 @@ export default class Renderer extends Module { /** If Tool is unavailable, create stub Block for it */ const stubData = { savedData: { + id, type: tool, data, }, @@ -94,6 +98,7 @@ export default class Renderer extends Module { } const stub = BlockManager.insert({ + id, tool: Tools.stubTool, data: stubData, }); diff --git a/src/components/modules/saver.ts b/src/components/modules/saver.ts index ba3cfd8c..521dbda5 100644 --- a/src/components/modules/saver.ts +++ b/src/components/modules/saver.ts @@ -81,7 +81,7 @@ export default class Saver extends Module { _.log('[Editor.js saving]:', 'groupCollapsed'); - allExtractedData.forEach(({ tool, data, tunes, time, isValid }) => { + allExtractedData.forEach(({ id, tool, data, tunes, time, isValid }) => { totalTime += time; /** @@ -108,6 +108,7 @@ export default class Saver extends Module { } const output = { + id, type: tool, data, ...!_.isEmpty(tunes) && { diff --git a/src/components/utils.ts b/src/components/utils.ts index 5a82dec7..4cc92a31 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'; /** @@ -607,6 +608,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 * diff --git a/test/cypress/support/commands.ts b/test/cypress/support/commands.ts index fc829c09..fe250e4a 100644 --- a/test/cypress/support/commands.ts +++ b/test/cypress/support/commands.ts @@ -5,7 +5,7 @@ * -------------------------------------------------- */ -import type { EditorConfig } from './../../../types/index'; +import type { EditorConfig, OutputData } from './../../../types/index'; import type EditorJS from '../../../types/index'; import Chainable = Cypress.Chainable; @@ -114,3 +114,14 @@ Cypress.Commands.add('cut', { prevSubject: true }, async (subject) => { 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 7d83933e..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 { @@ -40,6 +40,13 @@ declare global { * 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/tests/api/blocks.spec.ts b/test/cypress/tests/api/blocks.spec.ts new file mode 100644 index 00000000..db14d9a2 --- /dev/null +++ b/test/cypress/tests/api/blocks.spec.ts @@ -0,0 +1,53 @@ +/** + * 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; + }); + }); + }); +}); diff --git a/test/cypress/tests/block-ids.spec.ts b/test/cypress/tests/block-ids.spec.ts new file mode 100644 index 00000000..3486f789 --- /dev/null +++ b/test/cypress/tests/block-ids.spec.ts @@ -0,0 +1,130 @@ +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 index beae0177..5ec8e703 100644 --- a/test/cypress/tests/copy-paste.spec.ts +++ b/test/cypress/tests/copy-paste.spec.ts @@ -126,7 +126,8 @@ describe('Copy pasting from Editor', () => { }); cy.get('[data-cy=editorjs]') - .get('img') + // 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'); }); }); 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..fa4c11db 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} 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 05961e1f..07f296e1 100644 --- a/types/data-formats/output-data.d.ts +++ b/types/data-formats/output-data.d.ts @@ -1,5 +1,5 @@ import {BlockToolData} from '../tools'; -import {BlockTuneData} from "../block-tunes/block-tune-data"; +import {BlockTuneData} from '../block-tunes/block-tune-data'; /** * Output of one Tool @@ -8,6 +8,10 @@ import {BlockTuneData} from "../block-tunes/block-tune-data"; * @template Data - the structure describing a data object supported by the tool */ export interface OutputBlockData { + /** + * Unique Id of the block + */ + id?: string; /** * Tool type */ @@ -18,7 +22,7 @@ export interface OutputBlockData; /** - * Block Tunes data + * Block Tunes data */ tunes?: {[name: string]: BlockTuneData}; } diff --git a/yarn.lock b/yarn.lock index d578f65d..cbaba152 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6248,6 +6248,11 @@ nanoid@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" From 231d122949f7f00febfa379604ebb3d5ce7379b7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Apr 2021 22:04:26 +0300 Subject: [PATCH 22/35] Bump version up to 2.21.0 (#1670) * Bump version * Update CHANGELOG.md * Update package.json Co-authored-by: github-actions Co-authored-by: Peter Savchenko --- docs/CHANGELOG.md | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3d567102..2eaae7b2 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog ### 2.21.0 -- `New` - Blocks now have a unique ID [#873](https://github.com/codex-team/editor.js/issues/873) + +- `New` - Blocks now have unique ids [#873](https://github.com/codex-team/editor.js/issues/873) ### 2.20.2 diff --git a/package.json b/package.json index 431ce100..58867de4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.20.2", + "version": "2.21.0", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editor.js", "types": "./types/index.d.ts", From 9d8998e9e7ea86e0edbb7fac0e013f2ff0b1089d Mon Sep 17 00:00:00 2001 From: Taly Date: Wed, 28 Apr 2021 12:43:16 +0300 Subject: [PATCH 23/35] Fix workflow code (#1672) `The workflow is not valid. .github/workflows/publish-package-to-npm.yml: Unexpected tag '!github.event.release.prerelease'` --- .github/workflows/publish-package-to-npm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-package-to-npm.yml b/.github/workflows/publish-package-to-npm.yml index 61195862..14b1380e 100644 --- a/.github/workflows/publish-package-to-npm.yml +++ b/.github/workflows/publish-package-to-npm.yml @@ -34,7 +34,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Publish the package with a LATEST tag if this is not a prerelease version - if: !github.event.release.prerelease + if: github.event.release.prerelease != true run: yarn publish --access=public --tag=latest env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 92b023d56655a9c66db22629ef9d18f5e387ac65 Mon Sep 17 00:00:00 2001 From: Taly Date: Wed, 28 Apr 2021 13:26:42 +0300 Subject: [PATCH 24/35] yarn tag add (#1673) Add LATEST tag for the published package if this is not a prerelease version --- .github/workflows/publish-package-to-npm.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-package-to-npm.yml b/.github/workflows/publish-package-to-npm.yml index 14b1380e..39f85fc9 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: @@ -33,9 +37,9 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Publish the package with a LATEST tag if this is not a prerelease version + - name: Add LATEST tag for the published package if this is not a prerelease version if: github.event.release.prerelease != true - run: yarn publish --access=public --tag=latest + run: yarn tag add ${{ steps.package.outputs.name }}@${{ steps.package.outputs.version }} latest env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 51d94e1a1103dcf15cf91d8bb9f2596a4c8883e4 Mon Sep 17 00:00:00 2001 From: Junghyun Park Date: Sun, 9 May 2021 22:00:44 +0900 Subject: [PATCH 25/35] Add Slid logo as a sponsor in README.md (#1677) As a Sir Supporter of Editor.js, our Slid team like to be added in Editor.js' README.md. :) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a3e24b55..d8f40a60 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ If you like Editor.js you can support project improvements and development of ne 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)] + ### Backers From 4e7b33c2b89d184726fab7c7ca9c3f784183773f Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Wed, 26 May 2021 18:59:32 +0300 Subject: [PATCH 26/35] onChange improvements (#1678) * onChange improvements * Return modifications observer module * Fix lint * Fix tests --- docs/CHANGELOG.md | 4 + docs/installation.md | 2 +- example/example-dev.html | 4 +- example/example.html | 4 +- src/components/block/index.ts | 25 ++- src/components/modules/api/sanitizer.ts | 2 +- src/components/modules/blockManager.ts | 36 ++++ .../modules/modificationsObserver.ts | 179 ++---------------- src/components/modules/paste.ts | 2 +- src/components/modules/renderer.ts | 7 + src/components/modules/saver.ts | 18 +- src/components/modules/toolbar/inline.ts | 2 - src/components/utils.ts | 8 +- src/components/utils/events.ts | 10 +- src/components/utils/sanitizer.ts | 1 + src/types-internal/editor-modules.d.ts | 4 +- test/cypress/plugins/index.ts | 3 +- test/cypress/support/commands.ts | 7 +- test/cypress/tests/api/blocks.spec.ts | 1 + test/cypress/tests/block-ids.spec.ts | 1 + test/cypress/tests/modules/Tools.spec.ts | 6 +- test/cypress/tests/onchange.spec.ts | 127 +++++++++++++ test/cypress/tests/sanitisation.spec.ts | 1 + test/cypress/tests/tools/BlockTool.spec.ts | 1 + test/cypress/tests/tools/BlockTune.spec.ts | 1 + test/cypress/tests/tools/InlineTool.spec.ts | 1 + .../tests/tools/ToolsCollection.spec.ts | 1 + test/cypress/tests/tools/ToolsFactory.spec.ts | 1 + types/configs/editor-config.d.ts | 5 +- 29 files changed, 259 insertions(+), 205 deletions(-) create mode 100644 test/cypress/tests/onchange.spec.ts diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2eaae7b2..a60ea506 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### 2.22.0 + +- `New` - `onChange` callback now receive Block API object of affected block + ### 2.21.0 - `New` - Blocks now have unique ids [#873](https://github.com/codex-team/editor.js/issues/873) 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/example/example-dev.html b/example/example-dev.html index cc9123f7..4801722f 100644 --- a/example/example-dev.html +++ b/example/example-dev.html @@ -319,8 +319,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/src/components/block/index.ts b/src/components/block/index.ts index 18439c9d..fcb8c412 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -18,6 +18,7 @@ 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 @@ -79,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 * @@ -86,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 * @@ -207,6 +213,8 @@ export default class Block { this.updateCurrentInput(); this.call(BlockToolAPI.UPDATED); + + this.emit('didMutated', this); }, this.modificationDebounceTimer); /** @@ -230,6 +238,8 @@ export default class Block { readOnly, tunesData, }: BlockConstructorOptions) { + super(); + this.name = tool.name; this.id = id; this.settings = tool.settings; @@ -680,6 +690,8 @@ export default class Block { * Call Tool instance destroy method */ public destroy(): void { + super.destroy(); + if (_.isFunction(this.toolInstance.destroy)) { this.toolInstance.destroy(); } @@ -777,6 +789,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); + } }); } @@ -786,6 +805,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/modules/api/sanitizer.ts b/src/components/modules/api/sanitizer.ts index b0ce0ef0..79c91656 100644 --- a/src/components/modules/api/sanitizer.ts +++ b/src/components/modules/api/sanitizer.ts @@ -11,7 +11,7 @@ export default class SanitizerAPI extends Module { /** * Available methods * - * @returns {Sanitizer} + * @returns {SanitizerConfig} */ public get methods(): ISanitizer { return { diff --git a/src/components/modules/blockManager.ts b/src/components/modules/blockManager.ts index e82ba86f..cb2e1fc7 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -13,6 +13,7 @@ import * as _ from '../utils'; import Blocks from '../blocks'; import { BlockToolData, PasteEvent } from '../../../types'; import { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; +import BlockAPI from '../block/api'; /** * @typedef {BlockManager} BlockManager @@ -290,6 +291,11 @@ export default class BlockManager extends Module { 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) { @@ -361,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) { @@ -426,8 +437,15 @@ export default class BlockManager extends Module { throw new Error('Can\'t find a Block to remove'); } + const blockToRemove = this._blocks[index]; + this._blocks.remove(index); + /** + * Force call of didMutated event on Block removal + */ + this.blockDidMutated(blockToRemove); + if (this.currentBlockIndex >= index) { this.currentBlockIndex--; } @@ -689,6 +707,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); } /** @@ -754,6 +777,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)); } /** @@ -789,4 +814,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/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 61f794af..178e3cc1 100644 --- a/src/components/modules/paste.ts +++ b/src/components/modules/paste.ts @@ -639,7 +639,7 @@ export default class Paste extends Module { * @param {PasteData} dataToInsert - data of Block to insert */ private async processInlinePaste(dataToInsert: PasteData): Promise { - const { BlockManager, Caret, Tools } = this.Editor; + const { BlockManager, Caret } = this.Editor; const { content } = dataToInsert; const currentBlockIsDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault; diff --git a/src/components/modules/renderer.ts b/src/components/modules/renderer.ts index 0e87e751..9aad79ff 100644 --- a/src/components/modules/renderer.ts +++ b/src/components/modules/renderer.ts @@ -48,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; diff --git a/src/components/modules/saver.ts b/src/components/modules/saver.ts index 521dbda5..59725f65 100644 --- a/src/components/modules/saver.ts +++ b/src/components/modules/saver.ts @@ -7,7 +7,7 @@ */ 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'; @@ -28,26 +28,28 @@ export default class Saver extends Module { * @returns {OutputData} */ public async save(): Promise { - const { BlockManager, ModificationsObserver, Tools } = 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 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(); } diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index 4c3d29b3..9bc788ee 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -9,10 +9,8 @@ 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 InlineTool from '../../tools/inline'; import { CommonInternalSettings } from '../../tools/base'; -import BlockTool from '../../tools/block'; /** * Inline Toolbar elements diff --git a/src/components/utils.ts b/src/components/utils.ts index 4cc92a31..091bd54a 100644 --- a/src/components/utils.ts +++ b/src/components/utils.ts @@ -660,7 +660,7 @@ export function deprecationAssert(condition: boolean, oldProperty: string, newPr * @param propertyKey - method or accessor name * @param descriptor - property descriptor */ -export function cacheable( +export function cacheable( target: Target, propertyKey: string, descriptor: PropertyDescriptor @@ -672,7 +672,7 @@ export function cacheable( /** * Override get or value descriptor property to cache return value * - * @param args + * @param args - method args */ descriptor[propertyToOverride] = function (...args: Arguments): Value { /** @@ -688,12 +688,12 @@ export function cacheable( /** * If get accessor has been overridden, we need to override set accessor to clear cache * - * @param value + * @param value - value to set */ if (propertyToOverride === 'get' && descriptor.set) { const originalSet = descriptor.set; - descriptor.set = function (value: any): void { + descriptor.set = function (value: unknown): void { delete target[cacheKey]; originalSet.apply(this, value); diff --git a/src/components/utils/events.ts b/src/components/utils/events.ts index 10d501ec..2db6c68a 100644 --- a/src/components/utils/events.ts +++ b/src/components/utils/events.ts @@ -11,7 +11,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 +25,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 +40,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,7 +67,7 @@ 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 { + public emit(eventName: Events, data?: object): void { if (!this.subscribers[eventName]) { return; } @@ -85,7 +85,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/utils/sanitizer.ts b/src/components/utils/sanitizer.ts index 692c05b5..5b4bc8a2 100644 --- a/src/components/utils/sanitizer.ts +++ b/src/components/utils/sanitizer.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ /** * CodeX Sanitizer * diff --git a/src/types-internal/editor-modules.d.ts b/src/types-internal/editor-modules.d.ts index 8d882f4e..95a255cb 100644 --- a/src/types-internal/editor-modules.d.ts +++ b/src/types-internal/editor-modules.d.ts @@ -6,7 +6,6 @@ import Toolbox from '../components/modules/toolbar/toolbox'; import BlockSettings from '../components/modules/toolbar/blockSettings'; import Paste from '../components/modules/paste'; import DragNDrop from '../components/modules/dragNDrop'; -import ModificationsObserver from '../components/modules/modificationsObserver'; import Renderer from '../components/modules/renderer'; import Tools from '../components/modules/tools'; import API from '../components/modules/api/index'; @@ -32,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; @@ -45,7 +45,6 @@ export interface EditorModules { ConversionToolbar: ConversionToolbar; Paste: Paste; DragNDrop: DragNDrop; - ModificationsObserver: ModificationsObserver; Renderer: Renderer; Tools: Tools; API: API; @@ -68,4 +67,5 @@ export interface EditorModules { ReadOnly: ReadOnly; ReadOnlyAPI: ReadOnlyAPI; I18nAPI: I18nAPI; + ModificationsObserver: ModificationsObserver; } diff --git a/test/cypress/plugins/index.ts b/test/cypress/plugins/index.ts index 06d4b245..e3a3c4e3 100644 --- a/test/cypress/plugins/index.ts +++ b/test/cypress/plugins/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ /* tslint:disable:no-var-requires */ /** * This file contains connection of Cypres plugins @@ -6,7 +7,7 @@ const webpackConfig = require('../../../webpack.config.js'); const preprocessor = require('@cypress/webpack-preprocessor'); const codeCoverageTask = require('@cypress/code-coverage/task'); -module.exports = (on, config): any => { +module.exports = (on, config): unknown => { /** * Add Cypress task to get code coverage */ diff --git a/test/cypress/support/commands.ts b/test/cypress/support/commands.ts index fe250e4a..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. @@ -40,7 +41,7 @@ Cypress.Commands.add('createEditor', (editorConfig: EditorConfig = {}): Chainabl /** * Paste command to dispatch paste event * - * @usage + * Usage * cy.get('div').paste({'text/plain': 'Text', 'text/html': 'Text'}) * * @param data - map with MIME type as a key and data as value @@ -66,7 +67,7 @@ Cypress.Commands.add('paste', { /** * Copy command to dispatch copy event on subject * - * @usage + * Usage: * cy.get('div').copy().then(data => {}) */ Cypress.Commands.add('copy', { prevSubject: true }, async (subject) => { @@ -92,7 +93,7 @@ Cypress.Commands.add('copy', { prevSubject: true }, async (subject) => { /** * Cut command to dispatch cut event on subject * - * @usage + * Usage: * cy.get('div').cut().then(data => {}) */ Cypress.Commands.add('cut', { prevSubject: true }, async (subject) => { diff --git a/test/cypress/tests/api/blocks.spec.ts b/test/cypress/tests/api/blocks.spec.ts index db14d9a2..f17325fd 100644 --- a/test/cypress/tests/api/blocks.spec.ts +++ b/test/cypress/tests/api/blocks.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /** * There will be described test cases of 'blocks.*' API */ diff --git a/test/cypress/tests/block-ids.spec.ts b/test/cypress/tests/block-ids.spec.ts index 3486f789..4faccaec 100644 --- a/test/cypress/tests/block-ids.spec.ts +++ b/test/cypress/tests/block-ids.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import Header from '../../../example/tools/header'; import { nanoid } from 'nanoid'; diff --git a/test/cypress/tests/modules/Tools.spec.ts b/test/cypress/tests/modules/Tools.spec.ts index e6719825..d3c43d68 100644 --- a/test/cypress/tests/modules/Tools.spec.ts +++ b/test/cypress/tests/modules/Tools.spec.ts @@ -1,5 +1,5 @@ /* tslint:disable:max-classes-per-file */ -/* eslint-disable @typescript-eslint/ban-ts-ignore */ +/* 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'; @@ -86,8 +86,8 @@ describe('Tools module', () => { // eslint-disable-next-line @typescript-eslint/no-empty-function public static prepare(): void {} } as any, - inlineToolbar: ['inlineTool2'], - tunes: ['blockTune2'] + inlineToolbar: [ 'inlineTool2' ], + tunes: [ 'blockTune2' ], }, withFailedPrepare: class { public static prepare(): void { 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 index 2c40206f..73326f69 100644 --- a/test/cypress/tests/sanitisation.spec.ts +++ b/test/cypress/tests/sanitisation.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ describe('Output sanitisation', () => { beforeEach(() => { if (this && this.editorInstance) { diff --git a/test/cypress/tests/tools/BlockTool.spec.ts b/test/cypress/tests/tools/BlockTool.spec.ts index e6306a18..114b820e 100644 --- a/test/cypress/tests/tools/BlockTool.spec.ts +++ b/test/cypress/tests/tools/BlockTool.spec.ts @@ -1,3 +1,4 @@ +/* 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'; diff --git a/test/cypress/tests/tools/BlockTune.spec.ts b/test/cypress/tests/tools/BlockTune.spec.ts index f922a254..9704a5dc 100644 --- a/test/cypress/tests/tools/BlockTune.spec.ts +++ b/test/cypress/tests/tools/BlockTune.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* tslint:disable:max-classes-per-file */ import { ToolSettings } from '../../../../types'; import { ToolType } from '../../../../src/components/tools/base'; diff --git a/test/cypress/tests/tools/InlineTool.spec.ts b/test/cypress/tests/tools/InlineTool.spec.ts index 41c56d9b..70bcbc37 100644 --- a/test/cypress/tests/tools/InlineTool.spec.ts +++ b/test/cypress/tests/tools/InlineTool.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* tslint:disable:max-classes-per-file */ import { ToolSettings } from '../../../../types'; import { ToolType } from '../../../../src/components/tools/base'; diff --git a/test/cypress/tests/tools/ToolsCollection.spec.ts b/test/cypress/tests/tools/ToolsCollection.spec.ts index 1cc62dd8..12609324 100644 --- a/test/cypress/tests/tools/ToolsCollection.spec.ts +++ b/test/cypress/tests/tools/ToolsCollection.spec.ts @@ -1,3 +1,4 @@ +/* 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'; diff --git a/test/cypress/tests/tools/ToolsFactory.spec.ts b/test/cypress/tests/tools/ToolsFactory.spec.ts index 84d2ff56..4aff4c41 100644 --- a/test/cypress/tests/tools/ToolsFactory.spec.ts +++ b/test/cypress/tests/tools/ToolsFactory.spec.ts @@ -1,3 +1,4 @@ +/* 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'; diff --git a/types/configs/editor-config.d.ts b/types/configs/editor-config.d.ts index dcb213e7..787ed2e5 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'; @@ -88,8 +88,9 @@ 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. From a4ffb1a7fc645334851aad21709e597a1e0ad1cf Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Wed, 26 May 2021 20:39:25 +0300 Subject: [PATCH 27/35] feature(api): blocks update method added (#1687) --- docs/CHANGELOG.md | 1 + docs/api.md | 2 + src/components/modules/api/blocks.ts | 29 +++++++++++ src/components/modules/blockManager.ts | 11 ++++- test/cypress/tests/api/blocks.spec.ts | 66 ++++++++++++++++++++++++++ types/api/blocks.d.ts | 8 ++++ 6 files changed, 116 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a60ea506..a943a350 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,7 @@ ### 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 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/src/components/modules/api/blocks.ts b/src/components/modules/api/blocks.ts index 7da432e4..d5a64ad9 100644 --- a/src/components/modules/api/blocks.ts +++ b/src/components/modules/api/blocks.ts @@ -29,6 +29,7 @@ export default class BlocksAPI extends Module { stretchBlock: (index: number, status = true): void => this.stretchBlock(index, status), insertNewBlock: (): void => this.insertNewBlock(), insert: this.insert, + update: this.update, }; } @@ -247,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/blockManager.ts b/src/components/modules/blockManager.ts index cb2e1fc7..f1d88bf7 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -539,6 +539,15 @@ 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 * @@ -546,7 +555,7 @@ export default class BlockManager extends Module { * * @returns {Block} */ - public getBlockById(id): Block { + public getBlockById(id): Block | undefined { return this._blocks.array.find(block => block.id === id); } diff --git a/test/cypress/tests/api/blocks.spec.ts b/test/cypress/tests/api/blocks.spec.ts index f17325fd..7eb74869 100644 --- a/test/cypress/tests/api/blocks.spec.ts +++ b/test/cypress/tests/api/blocks.spec.ts @@ -51,4 +51,70 @@ describe('api.blocks', () => { }); }); }); + + /** + * 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/types/api/blocks.d.ts b/types/api/blocks.d.ts index fa4c11db..f5293158 100644 --- a/types/api/blocks.d.ts +++ b/types/api/blocks.d.ts @@ -105,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; } From 041470d171e5974a9fd27bbd66adde6094faa446 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 May 2021 21:29:56 +0300 Subject: [PATCH 28/35] Bump version up to 2.22.0 (#1690) * Bump version * Update package.json Co-authored-by: github-actions Co-authored-by: Peter Savchenko --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 58867de4..d132360f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.21.0", + "version": "2.22.0", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editor.js", "types": "./types/index.d.ts", From ce1d394f446c14ce42144e41168ac7da48e1ad17 Mon Sep 17 00:00:00 2001 From: Taly Date: Thu, 27 May 2021 17:12:23 +0300 Subject: [PATCH 29/35] Use "npm dist-tag add" instead of "yarn tag add" (#1693) * Use "npm dist-tag add" instead of "yarn tag add" `yarn tag add` somehow throws an error for existing versions, but `npm dist-tag add` works correctly. resolve #1692 * Remove space --- .github/workflows/publish-package-to-npm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-package-to-npm.yml b/.github/workflows/publish-package-to-npm.yml index 39f85fc9..69364cdf 100644 --- a/.github/workflows/publish-package-to-npm.yml +++ b/.github/workflows/publish-package-to-npm.yml @@ -39,7 +39,7 @@ jobs: - name: Add LATEST tag for the published package if this is not a prerelease version if: github.event.release.prerelease != true - run: yarn tag add ${{ steps.package.outputs.name }}@${{ steps.package.outputs.version }} latest + run: npm dist-tag add ${{ steps.package.outputs.name }}@${{ steps.package.outputs.version }} latest env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 9e25400bf82c3f9e39a401e6b8dcd5ec0dd32efd Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Mon, 31 May 2021 08:05:29 +0300 Subject: [PATCH 30/35] chore(readme): update logos of our sponsors --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d8f40a60..d4530ecc 100644 --- a/README.md +++ b/README.md @@ -20,10 +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 From cf494a7a28d73f1b224b498fee1e78b4e88d1944 Mon Sep 17 00:00:00 2001 From: Taly Date: Mon, 28 Jun 2021 13:06:35 +0300 Subject: [PATCH 31/35] Fix i18n for tunes and tools (#1711) * we need to call isTune param to get result * remove Tune from exported name * Update CHANGELOG.md * Bump version * Update Tools.spec.ts * Update Tools.spec.ts * prevent tooltip jumping * Update CHANGELOG.md * Delete table * Create table Co-authored-by: Peter Savchenko --- docs/CHANGELOG.md | 4 ++++ example/tools/table | 2 +- package.json | 2 +- src/components/block-tunes/block-tune-delete.ts | 4 +++- src/components/block-tunes/block-tune-move-down.ts | 4 +++- src/components/block-tunes/block-tune-move-up.ts | 4 +++- src/components/modules/api/i18n.ts | 2 +- src/components/modules/tools.ts | 6 +++--- test/cypress/tests/modules/Tools.spec.ts | 14 +++++++------- 9 files changed, 26 insertions(+), 16 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a943a350..d1a26467 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### 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 diff --git a/example/tools/table b/example/tools/table index 5c1a73a8..b8944944 160000 --- a/example/tools/table +++ b/example/tools/table @@ -1 +1 @@ -Subproject commit 5c1a73a8022c18ac1c15ee8d0134caae029bfbe9 +Subproject commit b8944944e3c159db367dea7d79c30d68f31e76b4 diff --git a/package.json b/package.json index d132360f..dcabe594 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.22.0", + "version": "2.22.1", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editor.js", "types": "./types/index.d.ts", diff --git a/src/components/block-tunes/block-tune-delete.ts b/src/components/block-tunes/block-tune-delete.ts index b6e74333..a9b4c08a 100644 --- a/src/components/block-tunes/block-tune-delete.ts +++ b/src/components/block-tunes/block-tune-delete.ts @@ -75,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 f6faa7e1..3f5f4867 100644 --- a/src/components/block-tunes/block-tune-move-down.ts +++ b/src/components/block-tunes/block-tune-move-down.ts @@ -63,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 4b0943b6..940d3da4 100644 --- a/src/components/block-tunes/block-tune-move-up.ts +++ b/src/components/block-tunes/block-tune-move-up.ts @@ -62,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/modules/api/i18n.ts b/src/components/modules/api/i18n.ts index ec406802..9ecb9ceb 100644 --- a/src/components/modules/api/i18n.ts +++ b/src/components/modules/api/i18n.ts @@ -14,7 +14,7 @@ export default class I18nAPI extends Module { * @param tool - tool object */ private static getNamespace(tool: ToolClass): string { - if (tool.isTune) { + if (tool.isTune()) { return `blockTunes.${tool.name}`; } diff --git a/src/components/modules/tools.ts b/src/components/modules/tools.ts index 29c497f2..2b4c9bcc 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -212,15 +212,15 @@ export default class Tools extends Module { class: Stub, isInternal: true, }, - moveUpTune: { + moveUp: { class: MoveUpTune, isInternal: true, }, - deleteTune: { + delete: { class: DeleteTune, isInternal: true, }, - moveDownTune: { + moveDown: { class: MoveDownTune, isInternal: true, }, diff --git a/test/cypress/tests/modules/Tools.spec.ts b/test/cypress/tests/modules/Tools.spec.ts index d3c43d68..7e15a34f 100644 --- a/test/cypress/tests/modules/Tools.spec.ts +++ b/test/cypress/tests/modules/Tools.spec.ts @@ -208,17 +208,17 @@ describe('Tools module', () => { it('Block Tools should contain default tunes if no settings is specified', () => { const tool = module.blockTools.get('blockToolWithoutSettings'); - expect(tool.tunes.has('deleteTune')).to.be.true; - expect(tool.tunes.has('moveUpTune')).to.be.true; - expect(tool.tunes.has('moveDownTune')).to.be.true; + 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('deleteTune')).to.be.true; - expect(tool.tunes.has('moveUpTune')).to.be.true; - expect(tool.tunes.has('moveDownTune')).to.be.true; + 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', () => { @@ -226,7 +226,7 @@ describe('Tools module', () => { 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', 'moveUpTune', 'deleteTune', 'moveDownTune']); + expect(Array.from(tool.tunes.keys())).to.be.deep.eq(['blockTune2', 'blockTune', 'moveUp', 'delete', 'moveDown']); tool = module.blockTools.get('withSuccessfulPrepare'); From 6f36707f67e498ec0933144df2c72ba07ab1899d Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Wed, 21 Jul 2021 21:33:09 +0300 Subject: [PATCH 32/35] Tunes improvements for inline actions (#1722) * Add tunes improvements * Allow to show Inline Toolbar at Block Tune Wrapper element * Add fake cursor on block selection * Fix lint * Update types * Fix bugs with selection * Remove selection observer * Update due to comments * Fix tests * Update docs/block-tunes.md Co-authored-by: Peter Savchenko * Move fake cursor to selection utils * Fix missing range for Safari * Fix data attribute value * Add comment * Update z-index for inline-toolbar * Add changelog * Remove fake cursor visibility for the core Co-authored-by: Peter Savchenko --- docs/CHANGELOG.md | 7 + docs/block-tunes.md | 21 ++- example/example-dev.html | 9 +- src/components/block/index.ts | 18 ++- src/components/dom.ts | 2 +- src/components/modules/api/readonly.ts | 8 ++ src/components/modules/rectangleSelection.ts | 11 +- src/components/modules/toolbar/index.ts | 15 +- src/components/modules/toolbar/inline.ts | 13 +- src/components/modules/ui.ts | 45 ++++-- src/components/selection.ts | 138 ++++++++++++++----- src/components/tools/block.ts | 6 +- src/components/tools/tune.ts | 2 +- src/components/utils.ts | 8 +- src/styles/inline-toolbar.css | 1 + test/cypress/tests/selection.spec.ts | 33 +++++ test/cypress/tests/tools/BlockTune.spec.ts | 8 +- types/api/readonly.d.ts | 5 + types/block-tunes/block-tune.d.ts | 9 +- types/configs/editor-config.d.ts | 4 +- types/tools/tool-settings.d.ts | 12 +- 21 files changed, 302 insertions(+), 73 deletions(-) create mode 100644 test/cypress/tests/selection.spec.ts diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d1a26467..0623fb5f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### 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 + ### 2.22.1 - `Fix` — I18n for internal Block Tunes [#1661](https://github.com/codex-team/editor.js/issues/1661) diff --git a/docs/block-tunes.md b/docs/block-tunes.md index 1e8c2ce6..6544bb95 100644 --- a/docs/block-tunes.md +++ b/docs/block-tunes.md @@ -22,7 +22,7 @@ At the constructor of Tune's class exemplar you will receive an object with foll | Parameter | Description | | --------- | ----------- | | api | Editor's [API](api.md) obejct | -| settings | Configuration of Block Tool Tune is connected to (might be useful in some cases) | +| 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 | @@ -145,7 +145,24 @@ No return value --- -#### Format +### 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: diff --git a/example/example-dev.html b/example/example-dev.html index 4801722f..4700e8e9 100644 --- a/example/example-dev.html +++ b/example/example-dev.html @@ -114,7 +114,6 @@ * Tools list */ tools: { - /** * Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md} */ @@ -205,10 +204,10 @@ } }, { - type : 'paragraph', - id: "b6ji-DvaKb", - 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." } }, { diff --git a/src/components/block/index.ts b/src/components/block/index.ts index fcb8c412..85771882 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -201,7 +201,19 @@ export default class Block extends EventsDispatcher { /** * 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 */ @@ -448,8 +460,12 @@ export default class Block extends EventsDispatcher { 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); } } 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/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/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/toolbar/index.ts b/src/components/modules/toolbar/index.ts index 56953d91..7f0e319d 100644 --- a/src/components/modules/toolbar/index.ts +++ b/src/components/modules/toolbar/index.ts @@ -5,8 +5,8 @@ import I18n from '../../i18n'; import { I18nInternalNS } from '../../i18n/namespace-internal'; import Tooltip from '../../utils/tooltip'; import { ModuleConfig } from '../../../types-internal/module-config'; -import EventsDispatcher from '../../utils/events'; import { EditorConfig } from '../../../../types'; +import SelectionUtils from '../../selection'; /** * HTML Elements used for Toolbar UI @@ -348,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); } /** diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index 9bc788ee..41ca2ddf 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -134,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(); @@ -147,7 +148,7 @@ export default class InlineToolbar extends Module { } this.move(); - this.open(); + this.open(needToShowConversionToolbar); this.Editor.Toolbar.close(); } @@ -233,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; } @@ -251,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 */ diff --git a/src/components/modules/ui.ts b/src/components/modules/ui.ts index 8719d6a9..c6b7e245 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); } /** @@ -754,10 +752,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 */ @@ -765,7 +781,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; + } } /** @@ -775,10 +800,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 6a6ea6a3..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 @@ -132,7 +171,35 @@ export default class SelectionUtils { /** * 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/block.ts b/src/components/tools/block.ts index 7da7315c..e410851d 100644 --- a/src/components/tools/block.ts +++ b/src/components/tools/block.ts @@ -116,7 +116,7 @@ export default class BlockTool extends BaseTool { } /** - * Returns sanitize configuration for Block Tool including conifgs from Inline Tools + * Returns sanitize configuration for Block Tool including configs from related Inline Tools and Block Tunes */ @_.cacheable public get sanitizeConfig(): SanitizerConfig { @@ -160,6 +160,10 @@ export default class BlockTool extends BaseTool { .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/tune.ts b/src/components/tools/tune.ts index d52f32a6..52230cbd 100644 --- a/src/components/tools/tune.ts +++ b/src/components/tools/tune.ts @@ -28,7 +28,7 @@ export default class BlockTune extends BaseTool { // eslint-disable-next-line new-cap return new this.constructable({ api: this.api.getMethodsForTool(this), - settings: this.settings, + config: this.settings, block, data, }); diff --git a/src/components/utils.ts b/src/components/utils.ts index 091bd54a..90c3c32f 100644 --- a/src/components/utils.ts +++ b/src/components/utils.ts @@ -429,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 = () => { 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/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/BlockTune.spec.ts b/test/cypress/tests/tools/BlockTune.spec.ts index 9704a5dc..89b47d85 100644 --- a/test/cypress/tests/tools/BlockTune.spec.ts +++ b/test/cypress/tests/tools/BlockTune.spec.ts @@ -16,16 +16,16 @@ describe('BlockTune', () => { public static prepare; public api: object; - public settings: ToolSettings; + public config: ToolSettings; public data: BlockTuneData; public block: object; /** * */ - constructor({ api, settings, block, data }) { + constructor({ api, config, block, data }) { this.api = api; - this.settings = settings; + this.config = config; this.block = block; this.data = data; } @@ -173,7 +173,7 @@ describe('BlockTune', () => { it('should return Tool instance with passed settings', () => { const instance = tool.create(data, blockAPI as any) as any; - expect(instance.settings).to.be.deep.eq(options.config.config); + expect(instance.config).to.be.deep.eq(options.config.config); }); }); }); 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.d.ts b/types/block-tunes/block-tune.d.ts index c4ec7589..f323fd82 100644 --- a/types/block-tunes/block-tune.d.ts +++ b/types/block-tunes/block-tune.d.ts @@ -1,4 +1,4 @@ -import {API, BlockAPI, ToolConfig} from '../index'; +import {API, BlockAPI, SanitizerConfig, ToolConfig} from '../index'; import { BlockTuneData } from './block-tune-data'; /** @@ -41,6 +41,11 @@ export interface BlockTuneConstructable { */ isTune: boolean; + /** + * Tune's sanitize configuration + */ + sanitize?: SanitizerConfig; + /** * @constructor * @@ -48,7 +53,7 @@ export interface BlockTuneConstructable { */ new(config: { api: API, - settings?: ToolConfig, + config?: ToolConfig, block: BlockAPI, data: BlockTuneData, }): BlockTune; diff --git a/types/configs/editor-config.d.ts b/types/configs/editor-config.d.ts index 787ed2e5..4e93f6ce 100644 --- a/types/configs/editor-config.d.ts +++ b/types/configs/editor-config.d.ts @@ -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 diff --git a/types/tools/tool-settings.d.ts b/types/tools/tool-settings.d.ts index dd5af253..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 @@ -56,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; From 9190824b1d00f6f7cc3e16ba56bed58d22bdb4f8 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Wed, 21 Jul 2021 21:50:16 +0300 Subject: [PATCH 33/35] fix(blocks): plugin's destroy() method will be called on blocks removing (#1719) * Plugin's destroy() method on blocks removing * Add changelog Co-authored-by: Georgy Berezhnoy Co-authored-by: George Berezhnoy --- docs/CHANGELOG.md | 1 + src/components/modules/blockManager.ts | 1 + src/components/utils/events.ts | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0623fb5f..7d33342e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,7 @@ - `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 ### 2.22.1 diff --git a/src/components/modules/blockManager.ts b/src/components/modules/blockManager.ts index f1d88bf7..41e18865 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -439,6 +439,7 @@ export default class BlockManager extends Module { const blockToRemove = this._blocks[index]; + blockToRemove.destroy(); this._blocks.remove(index); /** diff --git a/src/components/utils/events.ts b/src/components/utils/events.ts index 2db6c68a..63812282 100644 --- a/src/components/utils/events.ts +++ b/src/components/utils/events.ts @@ -1,3 +1,5 @@ +import { isEmpty } from '../utils'; + /** * @class EventDispatcher * @@ -68,7 +70,7 @@ export default class EventsDispatcher { * @param {object} data - subscribers get this data when they were fired */ public emit(eventName: Events, data?: object): void { - if (!this.subscribers[eventName]) { + if (isEmpty(this.subscribers) || !this.subscribers[eventName]) { return; } From d2d18f806152052b6e291483184f3e956ba88bbb Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Wed, 21 Jul 2021 22:12:13 +0300 Subject: [PATCH 34/35] Fix jump on CBS (#1732) * Fix jump on CBS * Fix lint --- docs/CHANGELOG.md | 1 + src/components/modules/ui.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7d33342e..4396c901 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,7 @@ - `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 diff --git a/src/components/modules/ui.ts b/src/components/modules/ui.ts index c6b7e245..c697e47a 100644 --- a/src/components/modules/ui.ts +++ b/src/components/modules/ui.ts @@ -662,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; } @@ -689,7 +691,12 @@ export default class UI extends Module { return; } - const isClickedBottom = event.target instanceof Element && event.target.isEqualNode(this.nodes.redactor); + 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(); From 145ece41495269df7fb56ec8aae678a43903a011 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Jul 2021 22:14:12 +0300 Subject: [PATCH 35/35] Bump version up to 2.22.2 (#1698) * Bump version * Update version * Bump version Co-authored-by: github-actions Co-authored-by: Georgy Berezhnoy --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dcabe594..6c13eb80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.22.1", + "version": "2.22.2", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editor.js", "types": "./types/index.d.ts",