Ver código fonte

Merge pull request #7307 from ckeditor/i/5988

Feature (markdown-gfm): The markdown data processor has been totally revamped and updated to the most recent conversion libraries. Closes #5988.
Maciej 5 anos atrás
pai
commit
3881349eae
25 arquivos alterados com 858 adições e 2710 exclusões
  1. 4 1
      packages/ckeditor5-markdown-gfm/package.json
  2. 17 14
      packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js
  3. 93 0
      packages/ckeditor5-markdown-gfm/src/html2markdown/html2markdown.js
  4. 0 1286
      packages/ckeditor5-markdown-gfm/src/lib/marked/marked.js
  5. 0 175
      packages/ckeditor5-markdown-gfm/src/lib/marked/renderer.js
  6. 0 68
      packages/ckeditor5-markdown-gfm/src/lib/to-markdown/converters.js
  7. 0 785
      packages/ckeditor5-markdown-gfm/src/lib/to-markdown/to-markdown.js
  8. 32 0
      packages/ckeditor5-markdown-gfm/src/markdown2html/markdown2html.js
  9. 26 3
      packages/ckeditor5-markdown-gfm/tests/_utils/utils.js
  10. 9 9
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/blockquotes.js
  11. 54 18
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/code.js
  12. 41 7
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/escaping.js
  13. 12 2
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/headers.js
  14. 26 26
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/horizontal-rules.js
  15. 26 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/html.js
  16. 3 3
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/images.js
  17. 47 125
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/links.js
  18. 42 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/lists.js
  19. 6 3
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/manual/gfmdataprocessor.js
  20. 3 42
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/paragraphs.js
  21. 2 2
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strikethrough.js
  22. 12 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strong-emphasis.js
  23. 2 2
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/tables.js
  24. 50 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/text.js
  25. 351 139
      yarn.lock

+ 4 - 1
packages/ckeditor5-markdown-gfm/package.json

@@ -10,7 +10,10 @@
     "ckeditor5-plugin"
   ],
   "dependencies": {
-    "@ckeditor/ckeditor5-engine": "^21.0.0"
+    "@ckeditor/ckeditor5-engine": "^21.0.0",
+    "marked": "^0.7.0",
+    "turndown": "^6.0.0",
+    "turndown-plugin-gfm": "^1.0.2"
   },
   "engines": {
     "node": ">=12.0.0",

+ 17 - 14
packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js

@@ -7,11 +7,10 @@
  * @module markdown-gfm/gfmdataprocessor
  */
 
-import marked from './lib/marked/marked';
-import toMarkdown from './lib/to-markdown/to-markdown';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
-import GFMRenderer from './lib/marked/renderer';
-import converters from './lib/to-markdown/converters';
+
+import markdown2html from './markdown2html/markdown2html';
+import html2markdown, { turndownService } from './html2markdown/html2markdown';
 
 /**
  * This data processor implementation uses GitHub Flavored Markdown as input/output data.
@@ -37,20 +36,25 @@ export default class GFMDataProcessor {
 	}
 
 	/**
+	 * Keeps the specified element in the output as HTML. This is useful if the editor contains
+	 * features that produce HTML that are not part of the markdon standards.
+	 *
+	 * By default, all HTML tags are removed.
+	 *
+	 * @param element {String} The element name to be kept.
+	 */
+	keepHtml( element ) {
+		turndownService.keep( [ element ] );
+	}
+
+	/**
 	 * Converts the provided Markdown string to view tree.
 	 *
 	 * @param {String} data A Markdown string.
 	 * @returns {module:engine/view/documentfragment~DocumentFragment} The converted view element.
 	 */
 	toView( data ) {
-		const html = marked.parse( data, {
-			gfm: true,
-			breaks: true,
-			tables: true,
-			xhtml: true,
-			renderer: new GFMRenderer()
-		} );
-
+		const html = markdown2html( data );
 		return this._htmlDP.toView( html );
 	}
 
@@ -63,7 +67,6 @@ export default class GFMDataProcessor {
 	 */
 	toData( viewFragment ) {
 		const html = this._htmlDP.toData( viewFragment );
-
-		return toMarkdown( html, { gfm: true, converters } );
+		return html2markdown( html );
 	}
 }

+ 93 - 0
packages/ckeditor5-markdown-gfm/src/html2markdown/html2markdown.js

@@ -0,0 +1,93 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module markdown-gfm/html2markdown
+ */
+
+import TurndownService from 'turndown';
+import { gfm } from 'turndown-plugin-gfm';
+
+// Override the original escape method by not escaping links.
+const originalEscape = TurndownService.prototype.escape;
+
+function escape( string ) {
+	string = originalEscape( string );
+
+	// Escape "<".
+	string = string.replace( /</g, '\\<' );
+
+	return string;
+}
+
+// eslint-disable-next-line max-len
+const regex = /\b(?:https?:\/\/|www\.)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()[\]{};:'".,<>?«»“”‘’])/g;
+
+TurndownService.prototype.escape = function( string ) {
+	// Urls should not be escaped. Our strategy is using a regex to find them and escape everything
+	// which is out of the matches parts.
+
+	let escaped = '';
+	let lastLinkEnd = 0;
+
+	for ( const match of string.matchAll( regex ) ) {
+		const index = match.index;
+
+		// Append the substring between the last match and the current one (if anything).
+		if ( index > lastLinkEnd ) {
+			escaped += escape( string.substring( lastLinkEnd, index ) );
+		}
+
+		const matchedURL = match[ 0 ];
+
+		escaped += matchedURL;
+
+		lastLinkEnd = index + matchedURL.length;
+	}
+
+	// Add text after the last link or at the string start if no matches.
+	if ( lastLinkEnd < string.length ) {
+		escaped += escape( string.substring( lastLinkEnd, string.length ) );
+	}
+
+	return escaped;
+};
+
+const turndownService = new TurndownService( {
+	codeBlockStyle: 'fenced',
+	hr: '---',
+	headingStyle: 'atx'
+} );
+
+turndownService.use( [
+	gfm,
+	todoList
+] );
+
+/**
+ * Parses HTML to a markdown.
+ *
+ * @param {String} html
+ * @returns {String}
+ */
+export default function html2markdown( html ) {
+	return turndownService.turndown( html );
+}
+
+export { turndownService };
+
+// This is a copy of the original taskListItems rule from turdown-plugin-gfm, with minor changes.
+function todoList( turndownService ) {
+	turndownService.addRule( 'taskListItems', {
+		filter( node ) {
+			return node.type === 'checkbox' &&
+				// Changes here as CKEditor outputs a deeper structure.
+				( node.parentNode.nodeName === 'LI' || node.parentNode.parentNode.nodeName === 'LI' );
+		},
+		replacement( content, node ) {
+			return ( node.checked ? '[x]' : '[ ]' ) + ' ';
+		}
+	} );
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 1286
packages/ckeditor5-markdown-gfm/src/lib/marked/marked.js


+ 0 - 175
packages/ckeditor5-markdown-gfm/src/lib/marked/renderer.js

@@ -1,175 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/**
- * Original marked.js library renderer with fixes:
- * - No formatting for output HTML string &mdash; all newlines between tags are removed to create clean output.
- * - Changed long string concatenations to ES5 template strings.
- * - Changed code style.
- *
- * @see {@link https://github.com/chjj/marked#renderer} Methods description.
- * @param options
- * @constructor
- */
-function Renderer( options ) {
-	this.options = options || {};
-}
-
-Renderer.prototype.code = function( code, lang, escaped ) {
-	if ( this.options.highlight ) {
-		const out = this.options.highlight( code, lang );
-
-		if ( out !== null && out !== code ) {
-			escaped = true;
-			code = out;
-		}
-	}
-
-	if ( !lang ) {
-		return `<pre><code>${ escaped ? code : escape( code, true ) }</code></pre>`;
-	}
-
-	const cssClass = this.options.langPrefix + escape( lang, true );
-
-	return `<pre><code class="${ cssClass }">${ escaped ? code : escape( code, true ) }</code></pre>`;
-};
-
-Renderer.prototype.blockquote = function( quote ) {
-	return `<blockquote>${ quote }</blockquote>`;
-};
-
-Renderer.prototype.html = function( html ) {
-	return html;
-};
-
-Renderer.prototype.heading = function( text, level, raw ) {
-	return `<h${ level }>${ text }</h${ level }>`;
-};
-
-Renderer.prototype.hr = function() {
-	return this.options.xhtml ? '<hr/>' : '<hr>';
-};
-
-Renderer.prototype.list = function( body, ordered ) {
-	const type = ordered ? 'ol' : 'ul';
-
-	return `<${ type }>${ body }</${ type }>`;
-};
-
-Renderer.prototype.listitem = function( text ) {
-	return `<li>${ text }</li>`;
-};
-
-Renderer.prototype.paragraph = function( text ) {
-	return `<p>${ text }</p>`;
-};
-
-Renderer.prototype.table = function( header, body ) {
-	return `<table><thead>${ header }</thead><tbody>${ body }</tbody></table>`;
-};
-
-Renderer.prototype.tablerow = function( content ) {
-	return '<tr>' + content + '</tr>';
-};
-
-Renderer.prototype.tablecell = function( content, flags ) {
-	const type = flags.header ? 'th' : 'td';
-	const tag = flags.align ? `<${ type } align="${ flags.align }">` : `<${ type }>`;
-
-	return tag + content + `</${ type }>`;
-};
-
-// span level renderer
-Renderer.prototype.strong = function( text ) {
-	return `<strong>${ text }</strong>`;
-};
-
-Renderer.prototype.em = function( text ) {
-	return `<em>${ text }</em>`;
-};
-
-Renderer.prototype.codespan = function( text ) {
-	return `<code>${ text.trim() }</code>`;
-};
-
-Renderer.prototype.br = function() {
-	return this.options.xhtml ? '<br/>' : '<br>';
-};
-
-Renderer.prototype.del = function( text ) {
-	return `<del>${ text }</del>`;
-};
-
-Renderer.prototype.link = function( href, title, text ) {
-	if ( this.options.sanitize ) {
-		let prot;
-
-		try {
-			prot = decodeURIComponent( unescape( href ) )
-				.replace( /[^\w:]/g, '' )
-				.toLowerCase();
-		} catch ( e ) {
-			return '';
-		}
-
-		if ( prot.indexOf( 'javascript:' ) === 0 || prot.indexOf( 'vbscript:' ) === 0 ) { // jshint ignore:line
-			return '';
-		}
-	}
-
-	let out = '<a href="' + href + '"';
-
-	if ( title ) {
-		out += ' title="' + title + '"';
-	}
-	out += '>' + text + '</a>';
-
-	return out;
-};
-
-Renderer.prototype.image = function( href, title, text ) {
-	let out = '<img src="' + href + '" alt="' + text + '"';
-
-	if ( title ) {
-		out += ' title="' + title + '"';
-	}
-	out += this.options.xhtml ? '/>' : '>';
-
-	return out;
-};
-
-Renderer.prototype.text = function( text ) {
-	return text;
-};
-
-export default Renderer;
-
-function escape( html, encode ) {
-	return html
-		.replace( !encode ? /&(?!#?\w+;)/g : /&/g, '&amp;' )
-		.replace( /</g, '&lt;' )
-		.replace( />/g, '&gt;' )
-		.replace( /"/g, '&quot;' )
-		.replace( /'/g, '&#39;' );
-}
-
-function unescape( html ) {
-	// explicitly match decimal, hex, and named HTML entities
-	return html.replace( /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g, function( _, n ) {
-		n = n.toLowerCase();
-
-		if ( n === 'colon' ) {
-			return ':';
-		}
-
-		if ( n.charAt( 0 ) === '#' ) {
-			return n.charAt( 1 ) === 'x' ?
-				String.fromCharCode( parseInt( n.substring( 2 ), 16 ) ) :
-				String.fromCharCode( +n.substring( 1 ) ); // jscs:ignore
-		}
-
-		return '';
-	} );
-}

+ 0 - 68
packages/ckeditor5-markdown-gfm/src/lib/to-markdown/converters.js

@@ -1,68 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-// Exports an array with custom converters used by to-markdown library.
-export default [
-	// Converting code blocks with class name matching output from marked library.
-	{
-		filter: ( node ) =>  {
-			const regexp = /lang-(.+)/;
-
-			return node.nodeName === 'PRE' &&
-				node.firstChild &&
-				node.firstChild.nodeName === 'CODE' &&
-				regexp.test( node.firstChild.className );
-		},
-		replacement: ( content, node ) => {
-			const regexp = /lang-(.+)/;
-			const lang = regexp.exec( node.firstChild.className )[ 1 ];
-
-			return '\n\n``` ' + lang + '\n' + node.firstChild.textContent + '\n```\n\n';
-		}
-	},
-	// Converting empty links.
-	{
-		filter: ( node ) => {
-			return node.nodeName === 'A' && !node.getAttribute( 'href' );
-		},
-
-		replacement: ( content, node ) => {
-			const title = node.title ? `"${node.title}"` : '';
-
-			return `[${ content }](${ title })`;
-		}
-	},
-	// Headers - fixing newline at the beginning.
-	{
-		filter: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ],
-		replacement: ( content, node ) => {
-			const hLevel = node.nodeName.charAt( 1 );
-			let hPrefix = '';
-
-			for ( let i = 0; i < hLevel; i++ ) {
-				hPrefix += '#';
-			}
-
-			return hPrefix + ' ' + content + '\n';
-		}
-	},
-	// Inline code - fixing backticks inside code blocks.
-	{
-		filter: ( node ) => {
-			const hasSiblings = node.previousSibling || node.nextSibling;
-			const isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings;
-
-			return node.nodeName === 'CODE' && !isCodeBlock;
-		},
-		replacement: ( content ) => {
-			// If content starts or ends with backtick - use double backtick.
-			if ( content.indexOf( '`' ) > -1 ) {
-				return '`` ' + content + ' ``';
-			}
-
-			return '`' + content + '`';
-		}
-	}
-];

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 785
packages/ckeditor5-markdown-gfm/src/lib/to-markdown/to-markdown.js


+ 32 - 0
packages/ckeditor5-markdown-gfm/src/markdown2html/markdown2html.js

@@ -0,0 +1,32 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module markdown-gfm/markdown2html
+ */
+
+import marked from 'marked';
+
+/**
+ * Parses markdown string to an HTML.
+ *
+ * @param {String} markdown
+ * @returns {String}
+ */
+export default function markdown2html( markdown ) {
+	return marked.parse( markdown, {
+		gfm: true,
+		breaks: true,
+		tables: true,
+		xhtml: true,
+		headerIds: false
+	} );
+}
+
+export { marked };
+
+// Disable the autolink rule in the lexer (point it to a regex that always fail).
+marked.InlineLexer.rules.breaks.autolink = /^\b$/;
+marked.InlineLexer.rules.breaks.url = /^\b$/;

+ 26 - 3
packages/ckeditor5-markdown-gfm/tests/_utils/utils.js

@@ -14,18 +14,41 @@ import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
  * @param {String} markdown Markdown to be processed to view.
  * @param {String} viewString Expected view structure.
  * @param {String} [normalizedMarkdown] When converting back to the markdown it might be different than provided input
+ * @param {Object} [options] Additional options.
+ * @param {Function} [options.setup] A function that receives the data processor instance before its execution.
  * markdown string (which will be used if this parameter is not provided).
  */
-export function testDataProcessor( markdown, viewString, normalizedMarkdown ) {
+export function testDataProcessor( markdown, viewString, normalizedMarkdown, options ) {
 	const viewDocument = new ViewDocument( new StylesProcessor() );
+
 	const dataProcessor = new MarkdownDataProcessor( viewDocument );
+
+	if ( options && options.setup ) {
+		options.setup( dataProcessor );
+	}
 	const viewFragment = dataProcessor.toView( markdown );
 
+	const html = cleanHtml( stringify( viewFragment ) );
+
 	// Check if view has correct data.
-	expect( stringify( viewFragment ) ).to.equal( viewString );
+	expect( html ).to.equal( viewString );
 
 	// Check if converting back gives the same result.
 	const normalized = typeof normalizedMarkdown !== 'undefined' ? normalizedMarkdown : markdown;
 
-	expect( dataProcessor.toData( viewFragment ) ).to.equal( normalized );
+	expect( cleanMarkdown( dataProcessor.toData( viewFragment ) ) ).to.equal( normalized );
+}
+
+function cleanHtml( html ) {
+	// Space between table elements.
+	html = html.replace( /(th|td|tr)>\s+<(\/?(?:th|td|tr))/g, '$1><$2' );
+	return html;
+}
+
+function cleanMarkdown( markdown ) {
+	// Trim spaces at the end of the lines.
+	markdown = markdown.replace( / +$/gm, '' );
+	// Trim linebreak at the very beginning.
+	markdown = markdown.replace( /^\s+/g, '' );
+	return markdown;
 }

+ 9 - 9
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/blockquotes.js

@@ -23,9 +23,9 @@ describe( 'GFMDataProcessor', () => {
 		it( 'should process nested blockquotes', () => {
 			testDataProcessor(
 				'> foo\n' +
-				'> \n' +
+				'>\n' +
 				'> > bar\n' +
-				'> \n' +
+				'>\n' +
 				'> foo',
 
 				// GitHub is rendering as:
@@ -51,7 +51,7 @@ describe( 'GFMDataProcessor', () => {
 		it( 'should process list within a blockquote', () => {
 			testDataProcessor(
 				'> A list within a blockquote:\n' +
-				'> \n' +
+				'>\n' +
 				'> *   asterisk 1\n' +
 				'> *   asterisk 2\n' +
 				'> *   asterisk 3',
@@ -80,13 +80,13 @@ describe( 'GFMDataProcessor', () => {
 		it( 'should process blockquotes with code inside with ```', () => {
 			testDataProcessor(
 				'> Example 1:\n' +
-				'> \n' +
+				'>\n' +
 				'> ```\n' +
 				'> code 1\n' +
 				'> ```\n' +
-				'> \n' +
+				'>\n' +
 				'> Example 2:\n' +
-				'> \n' +
+				'>\n' +
 				'> ```\n' +
 				'> code 2\n' +
 				'> ```',
@@ -159,13 +159,13 @@ describe( 'GFMDataProcessor', () => {
 
 				// When converting back to data, DataProcessor will normalize tabs to ```.
 				'> Example 1:\n' +
-				'> \n' +
+				'>\n' +
 				'> ```\n' +
 				'> code 1\n' +
 				'> ```\n' +
-				'> \n' +
+				'>\n' +
 				'> Example 2:\n' +
-				'> \n' +
+				'>\n' +
 				'> ```\n' +
 				'> code 2\n' +
 				'> ```'

+ 54 - 18
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/code.js

@@ -45,7 +45,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should properly process backticks inside code spans #2', () => {
 			testDataProcessor(
-				'`` some `backticks` inside ``',
+				'``some `backticks` inside``',
 
 				'<p><code>some `backticks` inside</code></p>'
 			);
@@ -142,17 +142,17 @@ describe( 'GFMDataProcessor', () => {
 				'<pre><code>the lines in this block  \n' +
 				'all contain trailing spaces  </code></pre>',
 
-				// When converting back tabs are normalized to ```.
+				// When converting back tabs are normalized to ```, while the test function remove trailing spaces.
 				'```\n' +
-				'the lines in this block  \n' +
-				'all contain trailing spaces  \n' +
+				'the lines in this block\n' +
+				'all contain trailing spaces\n' +
 				'```'
 			);
 		} );
 
 		it( 'should process code block with language name', () => {
 			testDataProcessor(
-				'``` js\n' +
+				'```js\n' +
 				'var a = \'hello\';\n' +
 				'console.log(a + \' world\');\n' +
 				'```',
@@ -160,7 +160,7 @@ describe( 'GFMDataProcessor', () => {
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 
-				'<pre><code class="lang-js">var a = \'hello\';\n' +
+				'<pre><code class="language-js">var a = \'hello\';\n' +
 				'console.log(a + \' world\');</code></pre>'
 			);
 		} );
@@ -174,11 +174,11 @@ describe( 'GFMDataProcessor', () => {
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 
-				'<pre><code class="lang-bash">#!/bin/bash</code></pre>',
+				'<pre><code class="language-bash">#!/bin/bash</code></pre>',
 
 				// When converting back ~~~ are normalized to ```.
 
-				'``` bash\n' +
+				'```bash\n' +
 				'#!/bin/bash\n' +
 				'```'
 			);
@@ -186,7 +186,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process code block with language name and using ``````` as delimiter', () => {
 			testDataProcessor(
-				'``````` js\n' +
+				'```````js\n' +
 				'var a = \'hello\';\n' +
 				'console.log(a + \' world\');\n' +
 				'```````',
@@ -194,12 +194,12 @@ describe( 'GFMDataProcessor', () => {
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 
-				'<pre><code class="lang-js">var a = \'hello\';\n' +
+				'<pre><code class="language-js">var a = \'hello\';\n' +
 				'console.log(a + \' world\');</code></pre>',
 
 				// When converting back ``````` are normalized to ```.
 
-				'``` js\n' +
+				'```js\n' +
 				'var a = \'hello\';\n' +
 				'console.log(a + \' world\');\n' +
 				'```'
@@ -216,12 +216,12 @@ describe( 'GFMDataProcessor', () => {
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 
-				'<pre><code class="lang-js">var a = \'hello\';\n' +
+				'<pre><code class="language-js">var a = \'hello\';\n' +
 				'console.log(a + \' world\');</code></pre>',
 
 				// When converting back ~~~~~~~~~~ are normalized to ```.
 
-				'``` js\n' +
+				'```js\n' +
 				'var a = \'hello\';\n' +
 				'console.log(a + \' world\');\n' +
 				'```'
@@ -230,13 +230,13 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process empty code block', () => {
 			testDataProcessor(
-				'``` js\n' +
+				'```js\n' +
 				'```',
 
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 
-				'<pre><code class="lang-js"></code></pre>',
+				'<pre><code class="language-js"></code></pre>',
 
 				// When converting back, empty code blocks will be removed.
 				// This might be an issue when switching from source to editor
@@ -247,14 +247,14 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process code block with empty line', () => {
 			testDataProcessor(
-				'``` js\n' +
+				'```js\n' +
 				'\n' +
 				'```',
 
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 
-				'<pre><code class="lang-js"></code></pre>',
+				'<pre><code class="language-js"></code></pre>',
 
 				// When converting back, empty code blocks will be removed.
 				// This might be an issue when switching from source to editor
@@ -273,7 +273,43 @@ describe( 'GFMDataProcessor', () => {
 				'<p><code>code `` code ```</code></p>',
 
 				// When converting back ````` will be normalized to ``.
-				'`` code `` code ``` ``'
+				'`code `` code ``` `'
+			);
+		} );
+
+		it( 'should handle triple ticks inside code', () => {
+			testDataProcessor(
+				'````\n' +
+				'```\n' +
+				'Code\n' +
+				'```\n' +
+				'````',
+
+				'<pre><code>' +
+				'```\n' +
+				'Code\n' +
+				'```' +
+				'</code></pre>'
+			);
+		} );
+
+		it( 'should handle triple and quatruple ticks inside code', () => {
+			testDataProcessor(
+				'`````\n' +
+				'````\n' +
+				'```\n' +
+				'Code\n' +
+				'```\n' +
+				'````\n' +
+				'`````',
+
+				'<pre><code>' +
+				'````\n' +
+				'```\n' +
+				'Code\n' +
+				'```\n' +
+				'````' +
+				'</code></pre>'
 			);
 		} );
 	} );

+ 41 - 7
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/escaping.js

@@ -5,6 +5,7 @@
 
 import MarkdownDataProcessor from '../../src/gfmdataprocessor';
 import { stringify } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
+import { testDataProcessor } from '../../tests/_utils/utils';
 import ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document';
 import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
 
@@ -26,15 +27,15 @@ const testCases = {
 };
 
 describe( 'GFMDataProcessor', () => {
-	let dataProcessor;
-
-	beforeEach( () => {
-		const viewDocument = new ViewDocument( new StylesProcessor() );
-		dataProcessor = new MarkdownDataProcessor( viewDocument );
-	} );
-
 	describe( 'escaping', () => {
 		describe( 'toView', () => {
+			let dataProcessor;
+
+			beforeEach( () => {
+				const viewDocument = new ViewDocument( new StylesProcessor() );
+				dataProcessor = new MarkdownDataProcessor( viewDocument );
+			} );
+
 			for ( const key in testCases ) {
 				const test = testCases[ key ].test;
 				const result = testCases[ key ].result;
@@ -70,5 +71,38 @@ describe( 'GFMDataProcessor', () => {
 				expect( stringify( documentFragment ) ).to.equal( '<pre><code>\\`</code></pre>' );
 			} );
 		} );
+
+		describe( 'HTML', () => {
+			// To note that the test util inlines entities in text nodes, hence the expected HTML in these tests
+			// contain the raw characters but we "know" that those are text nodes and therefore should be converted
+			// back to entities when outputting markdown.
+
+			it( 'should escape <', () => {
+				testDataProcessor( '\\<', '<p><</p>' );
+			} );
+
+			it( 'should escape HTML as text', () => {
+				testDataProcessor( '\\<h1>Test\\</h1>', '<p><h1>Test</h1></p>' );
+			} );
+
+			it( 'should not escape \\< inside inline code', () => {
+				testDataProcessor( '`\\<`', '<p><code>\\<</code></p>' );
+			} );
+
+			it( 'should not touch escape-like HTML inside code blocks', () => {
+				testDataProcessor(
+					'```\n' +
+					'\\<h1>Test\\</h1>\n' +
+					'```',
+					'<pre><code>' +
+					'\\<h1>Test\\</h1>' +
+					'</code></pre>' );
+			} );
+
+			// Necessary test as we're overriding Turndown's escape(). Just to be sure.
+			it( 'should still escape markdown characters', () => {
+				testDataProcessor( '\\* \\_', '<p>* _</p>' );
+			} );
+		} );
 	} );
 } );

+ 12 - 2
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/headers.js

@@ -95,7 +95,11 @@ describe( 'GFMDataProcessor', () => {
 				'# header\n' +
 				'# header',
 
-				'<h1>header</h1><h1>header</h1>'
+				'<h1>header</h1><h1>header</h1>',
+
+				'# header\n' +
+				'\n' +
+				'# header'
 			);
 		} );
 
@@ -105,7 +109,13 @@ describe( 'GFMDataProcessor', () => {
 				'## header\n' +
 				'### header',
 
-				'<h1>header</h1><h2>header</h2><h3>header</h3>'
+				'<h1>header</h1><h2>header</h2><h3>header</h3>',
+
+				'# header\n' +
+				'\n' +
+				'## header\n' +
+				'\n' +
+				'### header'
 			);
 		} );
 

+ 26 - 26
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/horizontal-rules.js

@@ -6,24 +6,24 @@
 import { testDataProcessor } from '../_utils/utils';
 
 describe( 'GFMDataProcessor', () => {
-	// Horizontal rules are are always rendered by GitHub as <hr> and normalized when converting
-	// back to * * *.
+	// Horizontal rules are always rendered by GitHub as <hr> and normalized when converting
+	// back to ---.
 	describe( 'horizontal rules', () => {
 		describe( 'dashes', () => {
 			it( '#1', () => {
-				testDataProcessor( '---', '<hr></hr>', '* * *' );
+				testDataProcessor( '---', '<hr></hr>', '---' );
 			} );
 
 			it( '#2', () => {
-				testDataProcessor( ' ---', '<hr></hr>', '* * *' );
+				testDataProcessor( ' ---', '<hr></hr>', '---' );
 			} );
 
 			it( '#3', () => {
-				testDataProcessor( '  ---', '<hr></hr>', '* * *' );
+				testDataProcessor( '  ---', '<hr></hr>', '---' );
 			} );
 
 			it( '#4', () => {
-				testDataProcessor( '   ---', '<hr></hr>', '* * *' );
+				testDataProcessor( '   ---', '<hr></hr>', '---' );
 			} );
 
 			it( '#5 - code', () => {
@@ -43,19 +43,19 @@ describe( 'GFMDataProcessor', () => {
 
 		describe( 'dashes with spaces', () => {
 			it( '#1', () => {
-				testDataProcessor( '- - -', '<hr></hr>', '* * *' );
+				testDataProcessor( '- - -', '<hr></hr>', '---' );
 			} );
 
 			it( '#2', () => {
-				testDataProcessor( ' - - -', '<hr></hr>', '* * *' );
+				testDataProcessor( ' - - -', '<hr></hr>', '---' );
 			} );
 
 			it( '#3', () => {
-				testDataProcessor( '  - - -', '<hr></hr>', '* * *' );
+				testDataProcessor( '  - - -', '<hr></hr>', '---' );
 			} );
 
 			it( '#4', () => {
-				testDataProcessor( '   - - -', '<hr></hr>', '* * *' );
+				testDataProcessor( '   - - -', '<hr></hr>', '---' );
 			} );
 
 			it( '#5 - code', () => {
@@ -75,19 +75,19 @@ describe( 'GFMDataProcessor', () => {
 
 		describe( 'asterisks', () => {
 			it( '#1', () => {
-				testDataProcessor( '***', '<hr></hr>', '* * *' );
+				testDataProcessor( '***', '<hr></hr>', '---' );
 			} );
 
 			it( '#2', () => {
-				testDataProcessor( ' ***', '<hr></hr>', '* * *' );
+				testDataProcessor( ' ***', '<hr></hr>', '---' );
 			} );
 
 			it( '#3', () => {
-				testDataProcessor( '  ***', '<hr></hr>', '* * *' );
+				testDataProcessor( '  ***', '<hr></hr>', '---' );
 			} );
 
 			it( '#4', () => {
-				testDataProcessor( '   ***', '<hr></hr>', '* * *' );
+				testDataProcessor( '   ***', '<hr></hr>', '---' );
 			} );
 
 			it( '#5 - code', () => {
@@ -107,19 +107,19 @@ describe( 'GFMDataProcessor', () => {
 
 		describe( 'asterisks with spaces', () => {
 			it( '#1', () => {
-				testDataProcessor( '* * *', '<hr></hr>', '* * *' );
+				testDataProcessor( '* * *', '<hr></hr>', '---' );
 			} );
 
 			it( '#2', () => {
-				testDataProcessor( ' * * *', '<hr></hr>', '* * *' );
+				testDataProcessor( ' * * *', '<hr></hr>', '---' );
 			} );
 
 			it( '#3', () => {
-				testDataProcessor( '  * * *', '<hr></hr>', '* * *' );
+				testDataProcessor( '  * * *', '<hr></hr>', '---' );
 			} );
 
 			it( '#4', () => {
-				testDataProcessor( '   * * *', '<hr></hr>', '* * *' );
+				testDataProcessor( '   * * *', '<hr></hr>', '---' );
 			} );
 
 			it( '#5 - code', () => {
@@ -139,19 +139,19 @@ describe( 'GFMDataProcessor', () => {
 
 		describe( 'underscores', () => {
 			it( '#1', () => {
-				testDataProcessor( '___', '<hr></hr>', '* * *' );
+				testDataProcessor( '___', '<hr></hr>', '---' );
 			} );
 
 			it( '#2', () => {
-				testDataProcessor( ' ___', '<hr></hr>', '* * *' );
+				testDataProcessor( ' ___', '<hr></hr>', '---' );
 			} );
 
 			it( '#3', () => {
-				testDataProcessor( '  ___', '<hr></hr>', '* * *' );
+				testDataProcessor( '  ___', '<hr></hr>', '---' );
 			} );
 
 			it( '#4', () => {
-				testDataProcessor( '   ___', '<hr></hr>', '* * *' );
+				testDataProcessor( '   ___', '<hr></hr>', '---' );
 			} );
 
 			it( '#5 - code', () => {
@@ -171,19 +171,19 @@ describe( 'GFMDataProcessor', () => {
 
 		describe( 'underscores with spaces', () => {
 			it( '#1', () => {
-				testDataProcessor( '_ _ _', '<hr></hr>', '* * *' );
+				testDataProcessor( '_ _ _', '<hr></hr>', '---' );
 			} );
 
 			it( '#2', () => {
-				testDataProcessor( ' _ _ _', '<hr></hr>', '* * *' );
+				testDataProcessor( ' _ _ _', '<hr></hr>', '---' );
 			} );
 
 			it( '#3', () => {
-				testDataProcessor( '  _ _ _', '<hr></hr>', '* * *' );
+				testDataProcessor( '  _ _ _', '<hr></hr>', '---' );
 			} );
 
 			it( '#4', () => {
-				testDataProcessor( '   _ _ _', '<hr></hr>', '* * *' );
+				testDataProcessor( '   _ _ _', '<hr></hr>', '---' );
 			} );
 
 			it( '#5 - code', () => {

+ 26 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/html.js

@@ -0,0 +1,26 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import { testDataProcessor } from '../_utils/utils';
+
+describe( 'GFMDataProcessor', () => {
+	describe( 'html', () => {
+		it( 'should keep html', () => {
+			testDataProcessor(
+				'test with <keep>html</keep> and <notkeep>not html</notkeep>',
+
+				'<p>test with <keep>html</keep> and <notkeep>not html</notkeep></p>',
+
+				'test with <keep>html</keep> and not html',
+
+				{
+					setup: dataProcessor => {
+						dataProcessor.keepHtml( 'keep' );
+					}
+				}
+			);
+		} );
+	} );
+} );

+ 3 - 3
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/images.js

@@ -35,7 +35,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process referenced images', () => {
 			testDataProcessor(
-				'![alt text][logo]\n' +
+				'![alt text][logo]\n\n' +
 				'[logo]: http://example.com/image.png "title text"',
 
 				'<p><img alt="alt text" src="http://example.com/image.png" title="title text"></img></p>',
@@ -47,7 +47,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process referenced images without title', () => {
 			testDataProcessor(
-				'![alt text][logo]\n' +
+				'![alt text][logo]\n\n' +
 				'[logo]: http://example.com/image.png',
 
 				'<p><img alt="alt text" src="http://example.com/image.png"></img></p>',
@@ -59,7 +59,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process referenced images without alt text', () => {
 			testDataProcessor(
-				'![][logo]\n' +
+				'![][logo]\n\n' +
 				'[logo]: http://example.com/image.png "title text"',
 
 				'<p><img alt="" src="http://example.com/image.png" title="title text"></img></p>',

+ 47 - 125
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/links.js

@@ -7,57 +7,34 @@ import { testDataProcessor } from '../_utils/utils';
 
 describe( 'GFMDataProcessor', () => {
 	describe( 'links', () => {
-		it( 'should autolink', () => {
-			testDataProcessor(
-				'Link: <http://example.com/>.',
-				'<p>Link: <a href="http://example.com/">http://example.com/</a>.</p>',
-
-				// When converting back it will be represented as standard markdown link.
-				'Link: [http://example.com/](http://example.com/).'
-			);
-		} );
-
-		it( 'should autolink #2', () => {
+		it( 'should not autolink', () => {
 			testDataProcessor(
 				'Link: http://example.com/.',
-				'<p>Link: <a href="http://example.com/">http://example.com/</a>.</p>',
-
-				// When converting back it will be represented as standard markdown link.
-				'Link: [http://example.com/](http://example.com/).'
+				'<p>Link: http://example.com/.</p>'
 			);
 		} );
 
-		it( 'should autolink with params', () => {
+		it( 'should not autolink with params', () => {
 			testDataProcessor(
-				'Link: <http://example.com/?foo=1&bar=2>.',
-				'<p>Link: <a href="http://example.com/?foo=1&bar=2">http://example.com/?foo=1&bar=2</a>.</p>',
-
-				// When converting back it will be represented as standard markdown link.
-				'Link: [http://example.com/?foo=1&bar=2](http://example.com/?foo=1&bar=2).'
+				'Link: http://example.com/?foo=1&bar=2.',
+				'<p>Link: http://example.com/?foo=1&bar=2.</p>'
 			);
 		} );
 
-		it( 'should autolink inside list', () => {
+		it( 'should not autolink inside list', () => {
 			testDataProcessor(
-				'* <http://example.com/>',
-
-				'<ul><li><a href="http://example.com/">http://example.com/</a></li></ul>',
-
-				// When converting back it will be represented as standard markdown link.
-				'*   [http://example.com/](http://example.com/)'
+				'*   http://example.com/',
+				'<ul><li>http://example.com/</li></ul>'
 			);
 		} );
 
-		it( 'should autolink inside blockquote', () => {
+		it( 'should not autolink inside blockquote', () => {
 			testDataProcessor(
-				'> Blockquoted: <http://example.com/>',
+				'> Blockquoted: http://example.com/',
 
 				'<blockquote>' +
-				'<p>Blockquoted: <a href="http://example.com/">http://example.com/</a></p>' +
-				'</blockquote>',
-
-				// When converting back it will be represented as standard markdown link.
-				'> Blockquoted: [http://example.com/](http://example.com/)'
+				'<p>Blockquoted: http://example.com/</p>' +
+				'</blockquote>'
 			);
 		} );
 
@@ -138,37 +115,17 @@ describe( 'GFMDataProcessor', () => {
 			);
 		} );
 
-		it( 'should process inline links with spaces in URL', () => {
-			testDataProcessor(
-				'[URL and title]( /url/has space )',
-				'<p><a href="/url/has space">URL and title</a></p>',
-
-				// When converting back unneeded spaces will be removed.
-				'[URL and title](/url/has space)'
-			);
-		} );
-
-		it( 'should process inline links with titles and spaces in URL', () => {
-			testDataProcessor(
-				'[URL and title]( /url/has space/ "url has space and title")',
-				'<p><a href="/url/has space/" title="url has space and title">URL and title</a></p>',
-
-				// When converting back unneeded spaces will be removed.
-				'[URL and title](/url/has space/ "url has space and title")'
-			);
-		} );
-
-		it( 'should process empty link', () => {
-			testDataProcessor(
-				'[Empty]()',
-
-				'<p><a href="">Empty</a></p>'
-			);
-		} );
+		// it( 'should process empty link', () => {
+		// 	testDataProcessor(
+		// 		'[Empty]()',
+		//
+		// 		'<p><a href="">Empty</a></p>'
+		// 	);
+		// } );
 
 		it( 'should process reference links', () => {
 			testDataProcessor(
-				'Foo [bar] [1].\n' +
+				'Foo [bar][1].\n\n' +
 				'[1]: /url/  "Title"',
 
 				'<p>Foo <a href="/url/" title="Title">bar</a>.</p>',
@@ -181,19 +138,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process reference links - without space', () => {
 			testDataProcessor(
-				'Foo [bar][1].\n' +
-				'[1]: /url/  "Title"',
-
-				'<p>Foo <a href="/url/" title="Title">bar</a>.</p>',
-
-				'Foo [bar](/url/ "Title").'
-			);
-		} );
-
-		it( 'should process reference links - with newline', () => {
-			testDataProcessor(
-				'Foo [bar]\n' +
-				'[1].\n' +
+				'Foo [bar][1].\n\n' +
 				'[1]: /url/  "Title"',
 
 				'<p>Foo <a href="/url/" title="Title">bar</a>.</p>',
@@ -204,18 +149,18 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process reference links - with embedded brackets', () => {
 			testDataProcessor(
-				'With [embedded [brackets]] [b].\n' +
+				'With [embedded [brackets]][b].\n\n' +
 				'[b]: /url/',
 
 				'<p>With <a href="/url/">embedded [brackets]</a>.</p>',
 
-				'With [embedded [brackets]](/url/).'
+				'With [embedded \\[brackets\\]](/url/).'
 			);
 		} );
 
 		it( 'should process reference links - with reference indented once', () => {
 			testDataProcessor(
-				'Indented [once][].\n' +
+				'Indented [once][].\n\n' +
 				' [once]: /url',
 
 				'<p>Indented <a href="/url">once</a>.</p>',
@@ -226,7 +171,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process reference links - with reference indented twice', () => {
 			testDataProcessor(
-				'Indented [twice][].\n' +
+				'Indented [twice][].\n\n' +
 				'  [twice]: /url',
 
 				'<p>Indented <a href="/url">twice</a>.</p>',
@@ -237,7 +182,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process reference links - with reference indented three times', () => {
 			testDataProcessor(
-				'Indented [trice][].\n' +
+				'Indented [trice][].\n\n' +
 				'   [trice]: /url',
 
 				'<p>Indented <a href="/url">trice</a>.</p>',
@@ -246,28 +191,9 @@ describe( 'GFMDataProcessor', () => {
 			);
 		} );
 
-		it( 'should NOT process reference links - with reference indented four times', () => {
-			testDataProcessor(
-				'Indented [four][].\n' +
-				'    [four]: /url',
-
-				// GitHub renders it as:
-				// <p>Indented [four][].<br>
-				// [four]: /url</p>
-				// Marked converts it to the code block.
-				'<p>Indented [four][].</p><pre><code>[four]: /url</code></pre>',
-
-				'Indented [four][].\n' +
-				'\n' +
-				'```\n' +
-				'[four]: /url\n' +
-				'```'
-			);
-		} );
-
 		it( 'should process reference links when title and reference are same #1', () => {
 			testDataProcessor(
-				'[this] [this]\n' +
+				'[this][this]\n\n' +
 				'[this]: foo',
 
 				'<p><a href="foo">this</a></p>',
@@ -278,7 +204,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process reference links when title and reference are same #2', () => {
 			testDataProcessor(
-				'[this][this]\n' +
+				'[this][this]\n\n' +
 				'[this]: foo',
 
 				'<p><a href="foo">this</a></p>',
@@ -289,7 +215,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process reference links when only title is provided and is same as reference #1', () => {
 			testDataProcessor(
-				'[this] []\n' +
+				'[this][]\n\n' +
 				'[this]: foo',
 
 				'<p><a href="foo">this</a></p>',
@@ -300,7 +226,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process reference links when only title is provided and is same as reference #2', () => {
 			testDataProcessor(
-				'[this][]\n' +
+				'[this][]\n\n' +
 				'[this]: foo',
 
 				'<p><a href="foo">this</a></p>',
@@ -311,7 +237,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process reference links when only title is provided and is same as reference #3', () => {
 			testDataProcessor(
-				'[this]\n' +
+				'[this]\n\n' +
 				'[this]: foo',
 
 				'<p><a href="foo">this</a></p>',
@@ -322,17 +248,11 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should not process reference links when reference is not found #1', () => {
 			testDataProcessor(
-				'[this] []',
-
-				'<p>[this] []</p>'
-			);
-		} );
-
-		it( 'should not process reference links when reference is not found #2', () => {
-			testDataProcessor(
 				'[this][]',
 
-				'<p>[this][]</p>'
+				'<p>[this][]</p>',
+
+				'\\[this\\]\\[\\]'
 			);
 		} );
 
@@ -340,35 +260,37 @@ describe( 'GFMDataProcessor', () => {
 			testDataProcessor(
 				'[this]',
 
-				'<p>[this]</p>'
+				'<p>[this]</p>',
+
+				'\\[this\\]'
 			);
 		} );
 
 		it( 'should process reference links nested in brackets #1', () => {
 			testDataProcessor(
-				'[a reference inside [this][]]\n' +
+				'[a reference inside [this][]]\n\n' +
 				'[this]: foo',
 
 				'<p>[a reference inside <a href="foo">this</a>]</p>',
 
-				'[a reference inside [this](foo)]'
+				'\\[a reference inside [this](foo)\\]'
 			);
 		} );
 
 		it( 'should process reference links nested in brackets #2', () => {
 			testDataProcessor(
-				'[a reference inside [this]]\n' +
+				'[a reference inside [this]]\n\n' +
 				'[this]: foo',
 
 				'<p>[a reference inside <a href="foo">this</a>]</p>',
 
-				'[a reference inside [this](foo)]'
+				'\\[a reference inside [this](foo)\\]'
 			);
 		} );
 
 		it( 'should not process reference links when title is same as reference but reference is different', () => {
 			testDataProcessor(
-				'[this](/something/else/)\n' +
+				'[this](/something/else/)\n\n' +
 				'[this]: foo',
 
 				'<p><a href="/something/else/">this</a></p>',
@@ -379,19 +301,19 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should not process reference links suppressed by backslashes', () => {
 			testDataProcessor(
-				'Suppress \\[this] and [this\\].\n' +
+				'Suppress \\[this] and [this\\].\n\n' +
 				'[this]: foo',
 
 				'<p>Suppress [this] and [this].</p>',
 
-				'Suppress [this] and [this].'
+				'Suppress \\[this\\] and \\[this\\].'
 			);
 		} );
 
 		it( 'should process reference links when used across multiple lines #1', () => {
 			testDataProcessor(
 				'This is [multiline\n' +
-				'reference]\n' +
+				'reference]\n\n' +
 				'[multiline reference]: foo',
 
 				'<p>This is <a href="foo">multiline<br></br>reference</a></p>',
@@ -404,7 +326,7 @@ describe( 'GFMDataProcessor', () => {
 		it( 'should process reference links when used across multiple lines #2', () => {
 			testDataProcessor(
 				'This is [multiline \n' +
-				'reference]\n' +
+				'reference]\n\n' +
 				'[multiline reference]: foo',
 
 				'<p>This is <a href="foo">multiline<br></br>reference</a></p>',
@@ -416,7 +338,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process reference links case-insensitive', () => {
 			testDataProcessor(
-				'[hi]\n' +
+				'[hi]\n\n' +
 				'[HI]: /url',
 
 				'<p><a href="/url">hi</a></p>',

+ 42 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/lists.js

@@ -4,6 +4,10 @@
  */
 
 import { testDataProcessor } from '../_utils/utils';
+import MarkdownDataProcessor from '../../src/gfmdataprocessor';
+import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
+import ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document';
+import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
 
 describe( 'GFMDataProcessor', () => {
 	describe( 'lists', () => {
@@ -340,4 +344,42 @@ describe( 'GFMDataProcessor', () => {
 			);
 		} );
 	} );
+
+	describe( 'todo lists', () => {
+		it( 'should process todo lists', () => {
+			testDataProcessor(
+				'*   [ ] Item 1\n' +
+				'*   [x] Item 2',
+
+				'<ul>' +
+					'<li><input disabled="" type="checkbox"></input>Item 1</li>' +
+					'<li><input checked="" disabled="" type="checkbox"></input>Item 2</li>' +
+				'</ul>' );
+		} );
+
+		it( 'should process the HTML produced by the todo list feature', () => {
+			const viewDocument = new ViewDocument( new StylesProcessor() );
+
+			const htmlDataProcessor = new HtmlDataProcessor( viewDocument );
+			const mdDataProcessor = new MarkdownDataProcessor( viewDocument );
+
+			const viewFragment = htmlDataProcessor.toView(
+				'<ul class="todo-list">' +
+					'<li><label class="todo-list__label">' +
+						'<input type="checkbox" disabled="disabled">' +
+						'<span class="todo-list__label__description">Item 1</span>' +
+					'</label></li>' +
+					'<li><label class="todo-list__label">' +
+						'<input type="checkbox" disabled="disabled" checked="checked">' +
+						'<span class="todo-list__label__description">Item 2</span>' +
+					'</label></li>' +
+				'</ul>'
+			);
+
+			expect( mdDataProcessor.toData( viewFragment ) ).to.equal(
+				'*   [ ] Item 1\n' +
+				'*   [x] Item 2'
+			);
+		} );
+	} );
 } );

+ 6 - 3
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/manual/gfmdataprocessor.js

@@ -5,17 +5,20 @@
 
 /* globals document */
 
-import MarkdownDataProcessor from '../../../src/gfmdataprocessor';
+import Document from '@ckeditor/ckeditor5-engine/src/view/document';
+import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
 import { stringify, parse } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 
+import MarkdownDataProcessor from '../../../src/gfmdataprocessor';
+
 const markdownTextArea = document.getElementById( 'markdown' );
 const viewTextArea = document.getElementById( 'view' );
-const dataProcessor = new MarkdownDataProcessor();
+const dataProcessor = new MarkdownDataProcessor( new Document( new StylesProcessor() ) );
 
 document.getElementById( 'button_to_view' ).addEventListener( 'click', convertToView );
 document.getElementById( 'button_to_md' ).addEventListener( 'click', convertToMarkdown );
 
-markdownTextArea.value = '### Header\n\nFoo bar baz biz.';
+markdownTextArea.value = '### Header 3\n\nTodo:\n\n* [ ] Test me';
 convertToView();
 
 function convertToView() {

+ 3 - 42
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/paragraphs.js

@@ -40,25 +40,6 @@ describe( 'GFMDataProcessor', () => {
 				// <h1>header</h1>
 				'<p>single line</p><h1>header</h1>',
 
-				// To-markdown always put 2 empty lines after paragraph.
-				'single line\n\n' +
-				'# header'
-			);
-		} );
-
-		it( 'with header after #2', () => {
-			testDataProcessor(
-				'single line\n' +
-				'header\n' +
-				'===',
-
-				// GitHub is rendering as:
-				// <p>single line</p>
-				//
-				// <h1>header</h1>
-				'<p>single line</p><h1>header</h1>',
-
-				// To-markdown always put 2 empty lines after paragraph and normalize header to #.
 				'single line\n' +
 				'\n' +
 				'# header'
@@ -78,17 +59,16 @@ describe( 'GFMDataProcessor', () => {
 				// </blockquote>
 				'<p>single line</p><blockquote><p>quote</p></blockquote>',
 
-				// To-markdown always put 2 empty lines after paragraph.
-				'single line\n' +
+				'single line' +
 				'\n' +
-				'> quote'
+				'\n> quote'
 			);
 		} );
 
 		it( 'with list after', () => {
 			testDataProcessor(
 				'single line\n' +
-				'* item',
+				'*   item',
 
 				// GitHub is rendering as:
 				// <p>single line</p>
@@ -98,29 +78,10 @@ describe( 'GFMDataProcessor', () => {
 				// </ul>
 				'<p>single line</p><ul><li>item</li></ul>',
 
-				// To-markdown always put 2 empty lines after paragraph.
 				'single line\n' +
 				'\n' +
 				'*   item'
 			);
 		} );
-
-		it( 'with div element after', () => {
-			testDataProcessor(
-				'single line\n' +
-				'<div>div element</div>',
-
-				// GitHub is rendering as:
-				// <p>single line</p>
-				//
-				// <div>div element</div>
-				'<p>single line</p><div>div element</div>',
-
-				// To-markdown always put 2 empty lines after paragraph.
-				'single line\n' +
-				'\n' +
-				'<div>div element</div>'
-			);
-		} );
 	} );
 } );

+ 2 - 2
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strikethrough.js

@@ -9,7 +9,7 @@ describe( 'GFMDataProcessor', () => {
 	describe( 'Strikethrough', () => {
 		it( 'should process strikethrough text', () => {
 			testDataProcessor(
-				'~~deleted~~',
+				'~deleted~',
 
 				'<p><del>deleted</del></p>'
 			);
@@ -17,7 +17,7 @@ describe( 'GFMDataProcessor', () => {
 
 		it( 'should process strikethrough inside text', () => {
 			testDataProcessor(
-				'This is ~~deleted content~~.',
+				'This is ~deleted content~.',
 
 				'<p>This is <del>deleted content</del>.</p>'
 			);

+ 12 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strong-emphasis.js

@@ -78,6 +78,18 @@ describe( 'GFMDataProcessor', () => {
 			testDataProcessor(
 				'This should_not_be_emp.',
 
+				'<p>This should_not_be_emp.</p>',
+
+				// Turndow escape markdown markup characters used inside text.
+				'This should\\_not\\_be\\_emp.'
+			);
+		} );
+
+		it( 'should not render escape marks', () => {
+			testDataProcessor(
+				// Following the previous test.
+				'This should\\_not\\_be\\_emp.',
+
 				'<p>This should_not_be_emp.</p>'
 			);
 		} );

+ 2 - 2
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/tables.js

@@ -119,7 +119,7 @@ describe( 'GFMDataProcessor', () => {
 			testDataProcessor(
 				'Header 1|Header 2|Header 3|Header 4\n' +
 				':-------|:------:|-------:|--------\n' +
-				'*Cell 1*  |**Cell 2**  |~~Cell 3~~  |Cell 4',
+				'*Cell 1*  |**Cell 2**  |~Cell 3~  |Cell 4',
 
 				'<table>' +
 					'<thead>' +
@@ -151,7 +151,7 @@ describe( 'GFMDataProcessor', () => {
 				// After converting back it will be normalized.
 				'| Header 1 | Header 2 | Header 3 | Header 4 |\n' +
 				'| :-- | :-: | --: | --- |\n' +
-				'| _Cell 1_ | **Cell 2** | ~~Cell 3~~ | Cell 4 |'
+				'| _Cell 1_ | **Cell 2** | ~Cell 3~ | Cell 4 |'
 			);
 		} );
 	} );

+ 50 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/text.js

@@ -0,0 +1,50 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import { testDataProcessor } from '../_utils/utils';
+
+describe( 'GFMDataProcessor', () => {
+	describe( 'text', () => {
+		describe( 'urls', () => {
+			it( 'should not escape urls', () => {
+				testDataProcessor(
+					'escape\\_this https://test.com/do_[not]-escape escape\\_this',
+					'<p>escape_this https://test.com/do_[not]-escape escape_this</p>'
+				);
+			} );
+
+			it( 'should not escape urls (data escaped between urls)', () => {
+				testDataProcessor(
+					'escape\\_this https://test.com/do_[not]-escape escape\\_this https://test.com/do_[not]-escape',
+					'<p>escape_this https://test.com/do_[not]-escape escape_this https://test.com/do_[not]-escape</p>'
+				);
+			} );
+
+			it( 'should not escape urls (at start)', () => {
+				testDataProcessor(
+					'https://test.com/do_[not]-escape escape\\_this',
+					'<p>https://test.com/do_[not]-escape escape_this</p>'
+				);
+			} );
+
+			it( 'should not escape urls (at end)', () => {
+				testDataProcessor(
+					'escape\\_this https://test.com/do_[not]-escape',
+					'<p>escape_this https://test.com/do_[not]-escape</p>'
+				);
+			} );
+
+			[
+				'https://test.com/do_[not]-escape',
+				'http://test.com/do_[not]-escape',
+				'www.test.com/do_[not]-escape'
+			].forEach( url => {
+				it( `should not escape urls (${ url })`, () => {
+					testDataProcessor( url, `<p>${ url }</p>` );
+				} );
+			} );
+		} );
+	} );
+} );

+ 351 - 139
yarn.lock

@@ -259,9 +259,9 @@
     js-tokens "^4.0.0"
 
 "@babel/parser@^7.10.3", "@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.11.1", "@babel/parser@^7.6.0", "@babel/parser@^7.7.7", "@babel/parser@^7.9.6":
-  version "7.11.1"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.1.tgz#d91a387990b21e5d20047b336bb19b0553f02ff5"
-  integrity sha512-u9QMIRdKVF7hfEkb3nu2LgZDIzCQPv+yHD9Eg6ruoJLjkrQ9fFz4IBSlF/9XwoNri9+2F1IY+dYuOfZrXq8t3w==
+  version "7.11.3"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.3.tgz#9e1eae46738bcd08e23e867bab43e7b95299a8f9"
+  integrity sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA==
 
 "@babel/plugin-proposal-async-generator-functions@^7.10.4":
   version "7.10.5"
@@ -799,9 +799,9 @@
     esutils "^2.0.2"
 
 "@babel/runtime@^7.8.4":
-  version "7.11.1"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.1.tgz#087afc57e7bf1073e792fe54f8fb3cfa752f9230"
-  integrity sha512-nH5y8fLvVl3HAb+ezbgcgwrH8QbClWo8xzkOu7+oyqngo3EVorwpWJQaqXPjGRpfj7mQvsJCl/S8knkfkPWqrw==
+  version "7.11.2"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
+  integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
   dependencies:
     regenerator-runtime "^0.13.4"
 
@@ -1258,9 +1258,9 @@
     "@types/node" ">= 8"
 
 "@octokit/types@^5.0.0", "@octokit/types@^5.0.1", "@octokit/types@^5.2.0":
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.2.1.tgz#c212f03b0492faf215fa2ae506d5ec18038c2a36"
-  integrity sha512-PugtgEw8u++zAyBpDpSkR8K1OsT2l8QWp3ECL6bZHFoq9PfHDoKeGFWSuX2Z+Ghy93k1fkKf8tsmqNBv+8dEfQ==
+  version "5.4.0"
+  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.4.0.tgz#25f2f8e24fec09214553168c41c06383c9d0f529"
+  integrity sha512-D/uotqF69M50OIlwMqgyIg9PuLT2daOiBAYF0P40I2ekFA2ESwwBY5dxZe/UhXdPvIbNKDzuZmQrO7rMpuFbcg==
   dependencies:
     "@types/node" ">= 8"
 
@@ -1339,10 +1339,10 @@
     "@sinonjs/commons" "^1"
     "@sinonjs/samsam" "^5.0.2"
 
-"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.0.3":
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.0.3.tgz#86f21bdb3d52480faf0892a480c9906aa5a52938"
-  integrity sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==
+"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.1.0":
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.1.0.tgz#3afe719232b541bb6cf3411a4c399a188de21ec0"
+  integrity sha512-42nyaQOVunX5Pm6GRJobmzbS7iLI+fhERITnETXzzwDZh+TtDr/Au3yAvXVjFmZ4wEUaE4Y3NFZfKv0bV0cbtg==
   dependencies:
     "@sinonjs/commons" "^1.6.0"
     lodash.get "^4.4.2"
@@ -1634,6 +1634,11 @@ a-sync-waterfall@^1.0.0:
   resolved "https://registry.yarnpkg.com/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz#75b6b6aa72598b497a125e7a2770f14f4c8a1fa7"
   integrity sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==
 
+abab@^2.0.3:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.4.tgz#6dfa57b417ca06d21b2478f0e638302f99c2405c"
+  integrity sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==
+
 abbrev@1, abbrev@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -1654,6 +1659,14 @@ acorn-globals@^3.0.0:
   dependencies:
     acorn "^4.0.4"
 
+acorn-globals@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45"
+  integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==
+  dependencies:
+    acorn "^7.1.1"
+    acorn-walk "^7.1.1"
+
 acorn-jsx@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
@@ -1671,6 +1684,11 @@ acorn-walk@^6.2.0:
   resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
   integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
 
+acorn-walk@^7.1.1:
+  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@^3.0.4, acorn@^3.1.0, acorn@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
@@ -1945,24 +1963,20 @@ arrify@^1.0.1:
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
   integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
 
-arrify@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
-  integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
-
 asap@^2.0.3, asap@~2.0.3:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
   integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
 
-asn1.js@^4.0.0:
-  version "4.10.1"
-  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
-  integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==
+asn1.js@^5.2.0:
+  version "5.4.1"
+  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
+  integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
   dependencies:
     bn.js "^4.0.0"
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
+    safer-buffer "^2.1.0"
 
 asn1@~0.2.3:
   version "0.2.4"
@@ -2070,9 +2084,9 @@ aws-sign2@~0.7.0:
   integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
 
 aws4@^1.8.0:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
-  integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
+  version "1.10.1"
+  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428"
+  integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==
 
 babel-code-frame@^6.26.0:
   version "6.26.0"
@@ -2434,6 +2448,11 @@ brorand@^1.0.1:
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
   integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
 
+browser-process-hrtime@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
+  integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
+
 browser-stdout@1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
@@ -2501,14 +2520,14 @@ browserify-zlib@^0.2.0:
     pako "~1.0.5"
 
 browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.5:
-  version "4.13.0"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.13.0.tgz#42556cba011e1b0a2775b611cba6a8eca18e940d"
-  integrity sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==
+  version "4.14.0"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.0.tgz#2908951abfe4ec98737b72f34c3bcedc8d43b000"
+  integrity sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==
   dependencies:
-    caniuse-lite "^1.0.30001093"
-    electron-to-chromium "^1.3.488"
-    escalade "^3.0.1"
-    node-releases "^1.1.58"
+    caniuse-lite "^1.0.30001111"
+    electron-to-chromium "^1.3.523"
+    escalade "^3.0.2"
+    node-releases "^1.1.60"
 
 buffer-from@^1.0.0:
   version "1.1.1"
@@ -2692,11 +2711,6 @@ camelcase@^5.0.0, camelcase@^5.3.1:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
-camelcase@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
-  integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
-
 caniuse-api@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
@@ -2707,10 +2721,10 @@ caniuse-api@^3.0.0:
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001109:
-  version "1.0.30001111"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001111.tgz#dd0ce822c70eb6c7c068e4a55c22e19ec1501298"
-  integrity sha512-xnDje2wchd/8mlJu8sXvWxOGvMgv+uT3iZ3bkIAynKOzToCssWCmkz/ZIkQBs/2pUB4uwnJKVORWQ31UkbVjOg==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001111:
+  version "1.0.30001113"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001113.tgz#22016ab55b5a8b04fa00ca342d9ee1b98df48065"
+  integrity sha512-qMvjHiKH21zzM/VDZr6oosO6Ri3U0V2tC015jRXjOecwQCJtsU5zklTNTk31jQbIOP8gha0h1ccM/g0ECP+4BA==
 
 caseless@~0.12.0:
   version "0.12.0"
@@ -2895,9 +2909,9 @@ chokidar@^2.1.8:
     fsevents "^1.2.7"
 
 chokidar@^3.0.0, chokidar@^3.3.0, chokidar@^3.4.0, chokidar@^3.4.1:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1"
-  integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==
+  version "3.4.2"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d"
+  integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==
   dependencies:
     anymatch "~3.1.1"
     braces "~3.0.2"
@@ -3164,14 +3178,6 @@ commonmark@^0.29.1:
     minimist "~1.2.0"
     string.prototype.repeat "^0.2.0"
 
-compare-func@^1.3.1:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.4.tgz#6b07c4c5e8341119baf44578085bda0f4a823516"
-  integrity sha512-sq2sWtrqKPkEXAC8tEJA1+BqAH9GbFkGBtUOqrUX57VSfwp8xyktctk+uLoRy5eccTdxzDcVIztlYDpKs3Jv1Q==
-  dependencies:
-    array-ify "^1.0.0"
-    dot-prop "^3.0.0"
-
 compare-func@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3"
@@ -3287,11 +3293,11 @@ content-type@~1.0.4:
   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
 
 conventional-changelog-writer@^4.0.16:
-  version "4.0.16"
-  resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz#ca10f2691a8ea6d3c2eb74bd35bcf40aa052dda5"
-  integrity sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==
+  version "4.0.17"
+  resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz#4753aaa138bf5aa59c0b274cb5937efcd2722e21"
+  integrity sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==
   dependencies:
-    compare-func "^1.3.1"
+    compare-func "^2.0.0"
     conventional-commits-filter "^2.0.6"
     dateformat "^3.0.0"
     handlebars "^4.7.6"
@@ -3663,6 +3669,23 @@ csso@^4.0.2:
   dependencies:
     css-tree "1.0.0-alpha.39"
 
+cssom@^0.4.4:
+  version "0.4.4"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
+  integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==
+
+cssom@~0.3.6:
+  version "0.3.8"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
+  integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
+
+cssstyle@^2.2.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
+  integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
+  dependencies:
+    cssom "~0.3.6"
+
 cuid@^2.1.4:
   version "2.1.8"
   resolved "https://registry.yarnpkg.com/cuid/-/cuid-2.1.8.tgz#cbb88f954171e0d5747606c0139fb65c5101eac0"
@@ -3697,6 +3720,15 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
+data-urls@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
+  integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==
+  dependencies:
+    abab "^2.0.3"
+    whatwg-mimetype "^2.3.0"
+    whatwg-url "^8.0.0"
+
 date-fns@^1.29.0:
   version "1.30.1"
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
@@ -3763,6 +3795,11 @@ decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
+decimal.js@^10.2.0:
+  version "10.2.0"
+  resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
+  integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==
+
 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"
@@ -4021,6 +4058,13 @@ domelementtype@^2.0.1:
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d"
   integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==
 
+domexception@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
+  integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==
+  dependencies:
+    webidl-conversions "^5.0.0"
+
 domhandler@^2.3.0:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
@@ -4060,13 +4104,6 @@ domutils@^2.0.0:
     domelementtype "^2.0.1"
     domhandler "^3.0.0"
 
-dot-prop@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177"
-  integrity sha1-G3CK8JSknJoOfbyteQq6U52sEXc=
-  dependencies:
-    is-obj "^1.0.0"
-
 dot-prop@^5.1.0, dot-prop@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb"
@@ -4082,9 +4119,9 @@ dreamopt@~0.6.0:
     wordwrap ">=0.0.2"
 
 duplexer@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
-  integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
+  integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
 
 duplexify@^3.4.2, duplexify@^3.6.0:
   version "3.7.1"
@@ -4129,10 +4166,10 @@ ee-first@1.1.1:
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
-electron-to-chromium@^1.3.488:
-  version "1.3.520"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.520.tgz#dfda0a14a4aed785cbddfdb505ea122f75978392"
-  integrity sha512-q6H9E1sXDCjRHP+X06vcP+N0ki8ZvYoRPZfKnDuiRX10WWXxEHzKFVf4O9rBFMpuPtR3M+2KAdJnugJoBBp3Rw==
+electron-to-chromium@^1.3.523:
+  version "1.3.533"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.533.tgz#d7e5ca4d57e9bc99af87efbe13e7be5dde729b0f"
+  integrity sha512-YqAL+NXOzjBnpY+dcOKDlZybJDCOzgsq4koW3fvyty/ldTmsb4QazZpOWmVvZ2m0t5jbBf7L0lIGU3BUipwG+A==
 
 elliptic@^6.5.3:
   version "6.5.3"
@@ -4295,7 +4332,7 @@ es6-error@^4.0.1:
   resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
   integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
 
-escalade@^3.0.1:
+escalade@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4"
   integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==
@@ -4315,7 +4352,7 @@ escape-string-regexp@^2.0.0:
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
   integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
 
-escodegen@^1.9.0:
+escodegen@^1.14.1, escodegen@^1.9.0:
   version "1.14.3"
   resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
   integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==
@@ -4460,9 +4497,9 @@ estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
   integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
 
 estraverse@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642"
-  integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
+  integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
 
 esutils@^2.0.2:
   version "2.0.3"
@@ -4840,9 +4877,9 @@ flush-write-stream@^1.0.0:
     readable-stream "^2.3.6"
 
 follow-redirects@^1.0.0:
-  version "1.12.1"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6"
-  integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==
+  version "1.13.0"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
+  integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
 
 for-in@^1.0.1, for-in@^1.0.2:
   version "1.0.2"
@@ -5041,9 +5078,9 @@ get-stream@^4.0.0:
     pump "^3.0.0"
 
 get-stream@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
-  integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
+  integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
   dependencies:
     pump "^3.0.0"
 
@@ -5639,6 +5676,13 @@ html-comment-regex@^1.1.0:
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
   integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
 
+html-encoding-sniffer@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
+  integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==
+  dependencies:
+    whatwg-encoding "^1.0.5"
+
 html-escaper@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
@@ -5920,6 +5964,11 @@ invariant@^2.2.2, invariant@^2.2.4:
   dependencies:
     loose-envify "^1.0.0"
 
+ip-regex@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
+  integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
+
 is-absolute-url@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
@@ -6182,7 +6231,7 @@ is-number@^7.0.0:
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
-is-obj@^1.0.0, is-obj@^1.0.1:
+is-obj@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
   integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
@@ -6234,6 +6283,11 @@ is-posix-bracket@^0.1.0:
   resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
   integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=
 
+is-potential-custom-element-name@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
+  integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
+
 is-primitive@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
@@ -6476,9 +6530,9 @@ javascript-stringify@^2.0.1:
   integrity sha512-yV+gqbd5vaOYjqlbk16EG89xB5udgjqQF3C5FAORDg4f/IS1Yc5ERCv5e/57yBcfJYw05V5JyIXabhwb75Xxow==
 
 jest-worker@^26.2.1:
-  version "26.2.1"
-  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.2.1.tgz#5d630ab93f666b53f911615bc13e662b382bd513"
-  integrity sha512-+XcGMMJDTeEGncRb5M5Zq9P7K4sQ1sirhjdOxsN1462h6lFo9w59bl2LVQmdGEEeU3m+maZCkS2Tcc9SfCHO4A==
+  version "26.3.0"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f"
+  integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==
   dependencies:
     "@types/node" "*"
     merge-stream "^2.0.0"
@@ -6495,14 +6549,14 @@ js-base64@^2.1.8:
   integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
 
 js-beautify@^1.11.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.11.0.tgz#afb873dc47d58986360093dcb69951e8bcd5ded2"
-  integrity sha512-a26B+Cx7USQGSWnz9YxgJNMmML/QG2nqIaL7VVYPCXbqiKz8PN0waSNvroMtvAK6tY7g/wPdNWGEP+JTNIBr6A==
+  version "1.12.0"
+  resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.12.0.tgz#6c7e6a47a6075a7c8e60c861e850440a5479d36e"
+  integrity sha512-hZCm93+sWHqrsB2ac38cPX4A9t6mfReq13ZUr/0dk6rCXNLIq0R4lu0EiuJc0Ip6RiWNtE0vECjXOhcy/jMt9Q==
   dependencies:
     config-chain "^1.1.12"
     editorconfig "^0.15.3"
     glob "^7.1.3"
-    mkdirp "~1.0.3"
+    mkdirp "^1.0.4"
     nopt "^4.0.3"
 
 js-stringify@^1.0.1, js-stringify@^1.0.2:
@@ -6589,6 +6643,38 @@ jsbn@~0.1.0:
     taffydb "2.6.2"
     underscore "~1.8.3"
 
+jsdom@^16.2.0:
+  version "16.4.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb"
+  integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==
+  dependencies:
+    abab "^2.0.3"
+    acorn "^7.1.1"
+    acorn-globals "^6.0.0"
+    cssom "^0.4.4"
+    cssstyle "^2.2.0"
+    data-urls "^2.0.0"
+    decimal.js "^10.2.0"
+    domexception "^2.0.1"
+    escodegen "^1.14.1"
+    html-encoding-sniffer "^2.0.1"
+    is-potential-custom-element-name "^1.0.0"
+    nwsapi "^2.2.0"
+    parse5 "5.1.1"
+    request "^2.88.2"
+    request-promise-native "^1.0.8"
+    saxes "^5.0.0"
+    symbol-tree "^3.2.4"
+    tough-cookie "^3.0.1"
+    w3c-hr-time "^1.0.2"
+    w3c-xmlserializer "^2.0.0"
+    webidl-conversions "^6.1.0"
+    whatwg-encoding "^1.0.5"
+    whatwg-mimetype "^2.3.0"
+    whatwg-url "^8.0.0"
+    ws "^7.2.3"
+    xml-name-validator "^3.0.0"
+
 jsesc@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
@@ -6934,9 +7020,9 @@ lint-staged@^10.2.6:
     stringify-object "^3.3.0"
 
 listr2@^2.1.0:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.4.1.tgz#006fc94ae77b3195403cbf3a4a563e2d6366224f"
-  integrity sha512-8pYsCZCztr5+KAjReLyBeGhLV0vaQ2Du/eMe/ux9QAfQl7efiWejM1IWjALh0zHIRYuIbhQ8N2KztZ4ci56pnQ==
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.5.1.tgz#f265dddf916c8a9b475437b34ae85a7d8f495c7a"
+  integrity sha512-qkNRW70SwfwWLD/eiaTf2tfgWT/ZvjmMsnEFJOCzac0cjcc8rYHDBr1eQhRxopj6lZO7Oa5sS/pZzS6q+BsX+w==
   dependencies:
     chalk "^4.1.0"
     cli-truncate "^2.1.0"
@@ -6944,7 +7030,7 @@ listr2@^2.1.0:
     indent-string "^4.0.0"
     log-update "^4.0.0"
     p-map "^4.0.0"
-    rxjs "^6.6.0"
+    rxjs "^6.6.2"
     through "^2.3.8"
 
 load-json-file@^1.0.0:
@@ -7091,6 +7177,11 @@ lodash.some@^4.4.0:
   resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
   integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=
 
+lodash.sortby@^4.7.0:
+  version "4.7.0"
+  resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
+  integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
+
 lodash.template@^4.0.2:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
@@ -7370,6 +7461,11 @@ markdown@~0.5.0:
   dependencies:
     nopt "~2.1.1"
 
+marked@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e"
+  integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==
+
 marked@^0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.2.tgz#4faad28d26ede351a7a1aaa5fec67915c869e355"
@@ -7464,17 +7560,15 @@ meow@^3.7.0:
     trim-newlines "^1.0.0"
 
 meow@^7.0.0, meow@^7.0.1:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/meow/-/meow-7.0.1.tgz#1ed4a0a50b3844b451369c48362eb0515f04c1dc"
-  integrity sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/meow/-/meow-7.1.0.tgz#50ecbcdafa16f8b58fb7eb9675b933f6473b3a59"
+  integrity sha512-kq5F0KVteskZ3JdfyQFivJEj2RaA8NFsS4+r9DaMKLcUHpk5OcHS3Q0XkCXONB1mZRPsu/Y/qImKri0nwSEZog==
   dependencies:
     "@types/minimist" "^1.2.0"
-    arrify "^2.0.1"
-    camelcase "^6.0.0"
     camelcase-keys "^6.2.2"
     decamelize-keys "^1.1.0"
     hard-rejection "^2.1.0"
-    minimist-options "^4.0.2"
+    minimist-options "4.1.0"
     normalize-package-data "^2.5.0"
     read-pkg-up "^7.0.1"
     redent "^3.0.0"
@@ -7610,7 +7704,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
   dependencies:
     brace-expansion "^1.1.7"
 
-minimist-options@^4.0.2:
+minimist-options@4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
   integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==
@@ -7696,7 +7790,7 @@ mkdirp@0.5.5, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkd
   dependencies:
     minimist "^1.2.5"
 
-mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.3:
+mkdirp@^1.0.3, mkdirp@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@@ -7836,7 +7930,7 @@ nice-try@^1.0.4:
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
   integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 
-nise@^4.0.1:
+nise@^4.0.4:
   version "4.0.4"
   resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd"
   integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==
@@ -7922,7 +8016,7 @@ node-preload@^0.2.1:
   dependencies:
     process-on-spawn "^1.0.0"
 
-node-releases@^1.1.58:
+node-releases@^1.1.60:
   version "1.1.60"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084"
   integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==
@@ -8078,6 +8172,11 @@ nunjucks@^3.1.3:
   optionalDependencies:
     chokidar "^3.3.0"
 
+nwsapi@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
+  integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
+
 nyc@^15.0.1:
   version "15.1.0"
   resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02"
@@ -8210,9 +8309,9 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
     wrappy "1"
 
 onetime@^5.1.0:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.1.tgz#5c8016847b0d67fcedb7eef254751cfcdc7e9418"
-  integrity sha512-ZpZpjcJeugQfWsfyQlshVoowIIQ1qBGSVll4rfDq6JJVO//fesjoX808hXWfBjY+ROZgpKDI5TRSRBSoJiZ8eg==
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
+  integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
   dependencies:
     mimic-fn "^2.1.0"
 
@@ -8373,13 +8472,12 @@ parent-module@^1.0.0:
     callsites "^3.0.0"
 
 parse-asn1@^5.0.0, parse-asn1@^5.1.5:
-  version "5.1.5"
-  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"
-  integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==
+  version "5.1.6"
+  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
+  integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
   dependencies:
-    asn1.js "^4.0.0"
+    asn1.js "^5.2.0"
     browserify-aes "^1.0.0"
-    create-hash "^1.1.0"
     evp_bytestokey "^1.0.0"
     pbkdf2 "^3.0.3"
     safe-buffer "^5.1.1"
@@ -8446,6 +8544,11 @@ parse5@1.0.0:
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.0.0.tgz#d53ec053a2054e7538424ea9a6eeb9b265261279"
   integrity sha1-1T7AU6IFTnU4Qk6ppu65smUmEnk=
 
+parse5@5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
+  integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
+
 parseqs@0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
@@ -8647,9 +8750,9 @@ posix-character-classes@^0.1.0:
   integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
 
 postcss-calc@^7.0.1:
-  version "7.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1"
-  integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.3.tgz#d65cca92a3c52bf27ad37a5f732e0587b74f1623"
+  integrity sha512-IB/EAEmZhIMEIhG7Ov4x+l47UaXOS1n2f4FBUk/aKllQhtSCxWhTzn0nJgkqN7fo/jcWySvWTSB6Syk9L+31bA==
   dependencies:
     postcss "^7.0.27"
     postcss-selector-parser "^6.0.2"
@@ -9111,9 +9214,9 @@ pretty-hrtime@^1.0.3:
   integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
 
 prismjs@^1.17.1:
-  version "1.20.0"
-  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.20.0.tgz#9b685fc480a3514ee7198eac6a3bf5024319ff03"
-  integrity sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==
+  version "1.21.0"
+  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.21.0.tgz#36c086ec36b45319ec4218ee164c110f9fc015a3"
+  integrity sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw==
   optionalDependencies:
     clipboard "^2.0.0"
 
@@ -9875,6 +9978,22 @@ replace-ext@1.0.0:
   resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
   integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=
 
+request-promise-core@1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
+  integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==
+  dependencies:
+    lodash "^4.17.19"
+
+request-promise-native@^1.0.8:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28"
+  integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==
+  dependencies:
+    request-promise-core "1.1.4"
+    stealthy-require "^1.1.1"
+    tough-cookie "^2.3.3"
+
 request@^2.51.0, request@^2.87.0, request@^2.88.0, request@^2.88.2:
   version "2.88.2"
   resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
@@ -10063,7 +10182,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
   dependencies:
     aproba "^1.1.1"
 
-rxjs@^6.6.0:
+rxjs@^6.6.0, rxjs@^6.6.2:
   version "6.6.2"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2"
   integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==
@@ -10112,6 +10231,13 @@ sax@^1.2.4, sax@~1.2.4:
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
 
+saxes@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
+  integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==
+  dependencies:
+    xmlchars "^2.2.0"
+
 scheduler@^0.19.1:
   version "0.19.1"
   resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
@@ -10198,13 +10324,6 @@ semver@~5.3.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
   integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8=
 
-serialize-javascript@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea"
-  integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==
-  dependencies:
-    randombytes "^2.1.0"
-
 serialize-javascript@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
@@ -10313,16 +10432,16 @@ sinon-chai@^3.5.0:
   integrity sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==
 
 sinon@^9.0.2:
-  version "9.0.2"
-  resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d"
-  integrity sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==
+  version "9.0.3"
+  resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.3.tgz#bffc3ec28c936332cd2a41833547b9eed201ecff"
+  integrity sha512-IKo9MIM111+smz9JGwLmw5U1075n1YXeAq8YeSFlndCLhAL5KGn6bLgu7b/4AYHTV/LcEMcRm2wU2YiL55/6Pg==
   dependencies:
     "@sinonjs/commons" "^1.7.2"
     "@sinonjs/fake-timers" "^6.0.1"
     "@sinonjs/formatio" "^5.0.1"
-    "@sinonjs/samsam" "^5.0.3"
+    "@sinonjs/samsam" "^5.1.0"
     diff "^4.0.2"
-    nise "^4.0.1"
+    nise "^4.0.4"
     supports-color "^7.1.0"
 
 sitemap@^6.1.6:
@@ -10663,6 +10782,11 @@ stdout-stream@^1.4.0:
   dependencies:
     readable-stream "^2.0.1"
 
+stealthy-require@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
+  integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
+
 stream-browserify@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
@@ -11057,6 +11181,11 @@ swig-templates@^2.0.3:
     optimist "~0.6"
     uglify-js "2.6.0"
 
+symbol-tree@^3.2.4:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
+  integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+
 table@^5.2.3, table@^5.4.6:
   version "5.4.6"
   resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
@@ -11099,15 +11228,15 @@ tar@^6.0.2:
     yallist "^4.0.0"
 
 terser-webpack-plugin@^1.4.3:
-  version "1.4.4"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f"
-  integrity sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==
+  version "1.4.5"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b"
+  integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==
   dependencies:
     cacache "^12.0.2"
     find-cache-dir "^2.1.0"
     is-wsl "^1.1.0"
     schema-utils "^1.0.0"
-    serialize-javascript "^3.1.0"
+    serialize-javascript "^4.0.0"
     source-map "^0.6.1"
     terser "^4.1.2"
     webpack-sources "^1.4.0"
@@ -11285,7 +11414,7 @@ token-stream@1.0.0:
   resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-1.0.0.tgz#cc200eab2613f4166d27ff9afc7ca56d49df6eb4"
   integrity sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=
 
-tough-cookie@~2.5.0:
+tough-cookie@^2.3.3, tough-cookie@~2.5.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
   integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@@ -11293,6 +11422,22 @@ tough-cookie@~2.5.0:
     psl "^1.1.28"
     punycode "^2.1.1"
 
+tough-cookie@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
+  integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==
+  dependencies:
+    ip-regex "^2.1.0"
+    psl "^1.1.28"
+    punycode "^2.1.1"
+
+tr46@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479"
+  integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==
+  dependencies:
+    punycode "^2.1.1"
+
 tree-model@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/tree-model/-/tree-model-1.0.7.tgz#45b2307aa2dfc2da8ceb6a631c1e2b15550dce88"
@@ -11360,6 +11505,18 @@ tunnel-agent@^0.6.0:
   dependencies:
     safe-buffer "^5.0.1"
 
+turndown-plugin-gfm@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz#6f8678a361f35220b2bdf5619e6049add75bf1c7"
+  integrity sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==
+
+turndown@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/turndown/-/turndown-6.0.0.tgz#c083d6109a9366be1b84b86b20af09140ea4b413"
+  integrity sha512-UVJBhSyRHCpNKtQ00mNWlYUM/i+tcipkb++F0PrOpt0L7EhNd0AX9mWEpL2dRFBu7LWXMp4HgAMA4OeKKnN7og==
+  dependencies:
+    jsdom "^16.2.0"
+
 tweetnacl@^0.14.3, tweetnacl@~0.14.0:
   version "0.14.5"
   resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
@@ -11852,6 +12009,20 @@ vue-template-compiler@^2.6.11:
     de-indent "^1.0.2"
     he "^1.1.0"
 
+w3c-hr-time@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
+  integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==
+  dependencies:
+    browser-process-hrtime "^1.0.0"
+
+w3c-xmlserializer@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a"
+  integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==
+  dependencies:
+    xml-name-validator "^3.0.0"
+
 walkdir@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39"
@@ -11887,6 +12058,16 @@ watchpack@^1.7.4:
     chokidar "^3.4.1"
     watchpack-chokidar2 "^2.0.0"
 
+webidl-conversions@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
+  integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
+
+webidl-conversions@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
+  integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
+
 webpack-cli@^3.3.11:
   version "3.3.12"
   resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a"
@@ -11960,6 +12141,27 @@ webpack@^4.43.0:
     watchpack "^1.7.4"
     webpack-sources "^1.4.1"
 
+whatwg-encoding@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
+  integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==
+  dependencies:
+    iconv-lite "0.4.24"
+
+whatwg-mimetype@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
+  integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
+
+whatwg-url@^8.0.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.1.0.tgz#c628acdcf45b82274ce7281ee31dd3c839791771"
+  integrity sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw==
+  dependencies:
+    lodash.sortby "^4.7.0"
+    tr46 "^2.0.2"
+    webidl-conversions "^5.0.0"
+
 which-module@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
@@ -11997,9 +12199,9 @@ window-size@0.1.0:
   integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=
 
 windows-release@^3.1.0:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.1.tgz#cb4e80385f8550f709727287bf71035e209c4ace"
-  integrity sha512-Pngk/RDCaI/DkuHPlGTdIkDiTAnAkyMjoQMZqRsxydNl1qGXNIoZrB7RK8g53F2tEgQBMqQJHQdYZuQEEAu54A==
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999"
+  integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==
   dependencies:
     execa "^1.0.0"
 
@@ -12088,7 +12290,7 @@ write@1.0.3:
   dependencies:
     mkdirp "^0.5.1"
 
-ws@^7.1.2:
+ws@^7.1.2, ws@^7.2.3:
   version "7.3.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
   integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==
@@ -12100,6 +12302,16 @@ ws@~6.1.0:
   dependencies:
     async-limiter "~1.0.0"
 
+xml-name-validator@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
+  integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
+
+xmlchars@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
+  integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
+
 xmlcreate@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.3.tgz#df9ecd518fd3890ab3548e1b811d040614993497"