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 <specc.dev@gmail.com>
This commit is contained in:
George Berezhnoy 2021-04-08 21:17:23 +03:00 committed by GitHub
parent d02c3e3679
commit a88dc8e30b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1016 additions and 519 deletions

View file

@ -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

View file

@ -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",

View file

@ -45,11 +45,6 @@ interface BlockConstructorOptions {
*/
readOnly: boolean;
/**
* Tunes for current Block
*/
tunes: ToolsCollection<BlockTune>;
/**
* 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);

View file

@ -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);
}
}

View file

@ -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,
});

View file

@ -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 <p> 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;

View file

@ -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<void> {
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<void> {
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<SavedData, 'data' | 'tool'>[]): 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;

View file

@ -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<SavedData, 'data' | 'tool'>[]
): Pick<SavedData, 'data' | 'tool'>[] {
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;
}
}

View file

@ -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<OutputData> {
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 {

View file

@ -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<ConversionToolbarNodes> {
/**
* Clean exported data with replacing sanitizer config
*/
const cleaned: string = this.Editor.Sanitizer.clean(
const cleaned: string = clean(
exportData,
replacingTool.sanitizeConfig
);

View file

@ -300,70 +300,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
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<InlineToolbarNodes> {
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<InlineToolbarNodes> {
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);
});

View file

@ -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<void>}
*/
public prepare(): Promise<void> {
public async prepare(): Promise<void> {
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<BlockTune> {
const names = tool.enabledBlockTunes;
@_.cacheable
public getAllInlineToolsSanitizeConfig(): SanitizerConfig {
const config: SanitizerConfig = {} as SanitizerConfig;
if (names === false) {
return new ToolsCollection<BlockTune>();
}
Array.from(this.inlineTools.values())
.forEach(inlineTool => {
Object.assign(config, inlineTool.sanitizeConfig);
});
if (Array.isArray(names)) {
return new ToolsCollection<BlockTune>(
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<BlockTune>(
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<InlineTool>(
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<InlineTool>(
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<BlockTune>(
tool.enabledBlockTunes.map(name => [name, this.blockTunes.get(name)])
);
return;
}
if (Array.isArray(this.config.tunes)) {
tool.tunes = new ToolsCollection<BlockTune>(
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
*/

View file

@ -249,7 +249,7 @@ export default abstract class BaseTool<Type extends Tool = Tool> {
* Returns Tool's sanitizer configuration
*/
public get sanitizeConfig(): SanitizerConfig {
return this.constructable[CommonInternalSettings.SanitizeConfig];
return this.constructable[CommonInternalSettings.SanitizeConfig] || {};
}
/**

View file

@ -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<IBlockTool> {
*/
public type = ToolType.Block;
/**
* InlineTool collection for current Block Tool
*/
public inlineTools: ToolsCollection<InlineTool> = new ToolsCollection<InlineTool>();
/**
* BlockTune collection for current Block Tool
*/
public tunes: ToolsCollection<BlockTune> = new ToolsCollection<BlockTune>();
/**
* Tool's constructable blueprint
*/
@ -85,7 +98,7 @@ export default class BlockTool extends BaseTool<IBlockTool> {
* 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<IBlockTool> {
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;
}
}

View file

@ -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, Value, Arguments extends any[] = any[]>(
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;
};

View file

@ -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<Pick<SavedData, 'data' | 'tool'>>,
sanitizeConfig: SanitizerConfig | ((toolName: string) => SanitizerConfig)
): Array<Pick<SavedData, 'data' | 'tool'>> {
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<object | string>, ruleForItem: SanitizerConfig): Array<object | string> {
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);
}

View file

@ -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;

View file

@ -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': '<b>Text</b>'})
*
* @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;
});

View file

@ -14,6 +14,16 @@ declare global {
* @example cy.createEditor({})
*/
createEditor(editorConfig: EditorConfig): Chainable<EditorJS>
/**
* Paste command to dispatch paste event
*
* @usage
* cy.get('div').paste({'text/plain': 'Text', 'text/html': '<b>Text</b>'})
*
* @param data - map with MIME type as a key and data as value
*/
paste(data: {[type: string]: string}): Chainable<Subject>
}
interface ApplicationWindow {

View file

@ -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', () => {

View file

@ -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: '<b>Bold text</b>' },
} ],
},
}).then(async editor => {
const output = await (editor as any).save();
const boldText = output.blocks[0].data.text;
expect(boldText).to.eq('<b>Bold text</b>');
});
});
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('<b>This text should be bold.</b>');
});
});
it('should save formatting for paragraph on paste', () => {
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.paste({ 'text/html': '<p>Text</p><p><b>Bold text</b></p>' });
cy.get('@editorInstance').then(async editorInstance => {
const output = await (editorInstance as any).save();
const boldText = output.blocks[1].data.text;
expect(boldText).to.eq('<b>Bold text</b>');
});
});
});
});

View file

@ -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', () => {

View file

@ -4,7 +4,8 @@
"lib": ["dom", "es2017", "es2018"],
"moduleResolution": "node",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true
},
"include": [
"../../**/*.ts"

View file

@ -10,6 +10,7 @@
"resolveJsonModule": true,
// allows to omit export default in .json files
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true
}
}

329
yarn.lock
View file

@ -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==