浏览代码

Merge branch 'master' into i/7802

Piotrek Koszuliński 5 年之前
父节点
当前提交
52b77db2c1
共有 29 个文件被更改,包括 653 次插入160 次删除
  1. 1 0
      packages/ckeditor5-code-block/package.json
  2. 62 0
      packages/ckeditor5-code-block/tests/codeblock-integration.js
  3. 7 2
      packages/ckeditor5-engine/src/model/utils/insertcontent.js
  4. 66 25
      packages/ckeditor5-engine/src/view/downcastwriter.js
  5. 11 4
      packages/ckeditor5-link/src/linkui.js
  6. 17 0
      packages/ckeditor5-link/tests/linkui.js
  7. 1 5
      packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.js
  8. 2 7
      packages/ckeditor5-markdown-gfm/docs/features/markdown.md
  9. 7 1
      packages/ckeditor5-markdown-gfm/package.json
  10. 36 0
      packages/ckeditor5-markdown-gfm/src/markdown.js
  11. 9 4
      packages/ckeditor5-markdown-gfm/src/markdown2html/markdown2html.js
  12. 22 4
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/blockquotes.js
  13. 2 2
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/code.js
  14. 1 1
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/escaping.js
  15. 31 0
      packages/ckeditor5-markdown-gfm/tests/manual/markdown.html
  16. 71 0
      packages/ckeditor5-markdown-gfm/tests/manual/markdown.js
  17. 9 0
      packages/ckeditor5-markdown-gfm/tests/manual/markdown.md
  18. 二进制
      packages/ckeditor5-markdown-gfm/tests/manual/sample.jpg
  19. 26 0
      packages/ckeditor5-markdown-gfm/tests/markdown.js
  20. 1 2
      packages/ckeditor5-mention/src/mentionui.js
  21. 23 5
      packages/ckeditor5-mention/tests/mentionui.js
  22. 19 4
      packages/ckeditor5-table/src/tablewalker.js
  23. 13 28
      packages/ckeditor5-table/src/utils/ui/contextualballoon.js
  24. 4 4
      packages/ckeditor5-table/tests/tablewalker.js
  25. 5 4
      packages/ckeditor5-utils/src/collection.js
  26. 36 1
      packages/ckeditor5-utils/src/dom/rect.js
  27. 6 1
      packages/ckeditor5-utils/src/focustracker.js
  28. 86 0
      packages/ckeditor5-utils/tests/dom/rect.js
  29. 79 56
      yarn.lock

+ 1 - 0
packages/ckeditor5-code-block/package.json

@@ -24,6 +24,7 @@
     "@ckeditor/ckeditor5-editor-classic": "^21.0.0",
     "@ckeditor/ckeditor5-engine": "^21.0.0",
     "@ckeditor/ckeditor5-indent": "^21.0.0",
+    "@ckeditor/ckeditor5-markdown-gfm": "^21.0.0",
     "@ckeditor/ckeditor5-paragraph": "^21.0.0",
     "@ckeditor/ckeditor5-undo": "^21.0.0"
   },

+ 62 - 0
packages/ckeditor5-code-block/tests/codeblock-integration.js

@@ -0,0 +1,62 @@
+/**
+ * @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 ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import Enter from '@ckeditor/ckeditor5-enter/src/enter';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor';
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+import CodeBlockEditing from '../src/codeblockediting';
+
+// A simple plugin that enables the GFM data processor.
+class CodeBlockIntegration extends Plugin {
+	constructor( editor ) {
+		super( editor );
+		editor.data.processor = new GFMDataProcessor( editor.data.viewDocument );
+	}
+}
+
+function getEditor( initialData = '' ) {
+	return ClassicTestEditor
+		.create( initialData, {
+			plugins: [ CodeBlockIntegration, CodeBlockEditing, Enter, Paragraph ]
+		} );
+}
+
+describe( 'CodeBlock - integration', () => {
+	describe( 'with Markdown GFM', () => {
+		it( 'should be loaded and returned from the editor (for plain text)', async () => {
+			const editor = await getEditor(
+				'```\n' +
+				'test()\n' +
+				'```'
+			);
+
+			expect( editor.getData() ).to.equal(
+				'```plaintext\n' +
+				'test()\n' +
+				'```'
+			);
+
+			await editor.destroy();
+		} );
+		it( 'should be loaded and returned from the editor (for defined language)', async () => {
+			const editor = await getEditor(
+				'```javascript\n' +
+				'test()\n' +
+				'```'
+			);
+
+			expect( editor.getData() ).to.equal(
+				'```javascript\n' +
+				'test()\n' +
+				'```'
+			);
+
+			await editor.destroy();
+		} );
+	} );
+} );

+ 7 - 2
packages/ckeditor5-engine/src/model/utils/insertcontent.js

@@ -443,12 +443,17 @@ class Insertion {
 				// At this point the insertion position should be after the node we'll merge. If it isn't,
 				// it should need to be secured as in the left merge case.
 				/**
-				 * An internal error occured during merging insertion content with siblings.
+				 * An internal error occurred during merging inserted content with its siblings.
 				 * The insertion position should equal to the merge position.
 				 *
+				 * If you encountered this error, report it back to the CKEditor 5 team
+				 * with as many details regarding the content being inserted and the insertion position
+				 * as possible.
+				 *
 				 * @error insertcontent-invalid-insertion-position
 				 */
-				throw new CKEditorError( 'insertcontent-invalid-insertion-position', this );
+				throw new CKEditorError( 'insertcontent-invalid-insertion-position: ' +
+					'An internal error occurred during insertContent().', this );
 			}
 
 			// Move the position to the previous node, so it isn't moved to the graveyard on merge.

+ 66 - 25
packages/ckeditor5-engine/src/view/downcastwriter.js

@@ -427,10 +427,10 @@ export default class DowncastWriter {
 	}
 
 	/**
-	 * Breaks attribute nodes at provided position or at boundaries of provided range. It breaks attribute elements inside
-	 * up to a container element.
+	 * Breaks attribute elements at provided position or at boundaries of a provided range. It breaks attribute elements
+	 * up to their first ancestor that is a container element.
 	 *
-	 * In following examples `<p>` is a container, `<b>` and `<u>` are attribute nodes:
+	 * In following examples `<p>` is a container, `<b>` and `<u>` are attribute elements:
 	 *
 	 *		<p>foo<b><u>bar{}</u></b></p> -> <p>foo<b><u>bar</u></b>[]</p>
 	 *		<p>foo<b><u>{}bar</u></b></p> -> <p>foo{}<b><u>bar</u></b></p>
@@ -685,7 +685,10 @@ export default class DowncastWriter {
 			 *
 			 * @error view-writer-invalid-position-container
 			 */
-			throw new CKEditorError( 'view-writer-invalid-position-container', this.document );
+			throw new CKEditorError(
+				'view-writer-invalid-position-container: Position\'s parent container cannot be found.',
+				this.document
+			);
 		}
 
 		const insertionPosition = this._breakAttributes( position, true );
@@ -872,7 +875,10 @@ export default class DowncastWriter {
 	 */
 	wrap( range, attribute ) {
 		if ( !( attribute instanceof AttributeElement ) ) {
-			throw new CKEditorError( 'view-writer-wrap-invalid-attribute', this.document );
+			throw new CKEditorError(
+				'view-writer-wrap-invalid-attribute: DowncastWriter#wrap() must be called with an attribute element.',
+				this.document
+			);
 		}
 
 		validateRangeContainer( range, this.document );
@@ -913,11 +919,15 @@ export default class DowncastWriter {
 	unwrap( range, attribute ) {
 		if ( !( attribute instanceof AttributeElement ) ) {
 			/**
-			 * Attribute element need to be instance of attribute element.
+			 * The `attribute` passed to {@link module:engine/view/downcastwriter~DowncastWriter#unwrap `DowncastWriter#unwrap()`}
+			 * must be an instance of {@link module:engine/view/attributeelement~AttributeElement `AttributeElement`}.
 			 *
 			 * @error view-writer-unwrap-invalid-attribute
 			 */
-			throw new CKEditorError( 'view-writer-unwrap-invalid-attribute', this.document );
+			throw new CKEditorError(
+				'view-writer-unwrap-invalid-attribute: DowncastWriter#unwrap() must be called with an attribute element.',
+				this.document
+			);
 		}
 
 		validateRangeContainer( range, this.document );
@@ -1593,31 +1603,43 @@ export default class DowncastWriter {
 		// If position is placed inside EmptyElement - throw an exception as we cannot break inside.
 		if ( position.parent.is( 'emptyElement' ) ) {
 			/**
-			 * Cannot break inside EmptyElement instance.
+			 * Cannot break an `EmptyElement` instance.
+			 *
+			 * This error is thrown if
+			 * {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes `DowncastWriter#breakAttributes()`}
+			 * was executed in an incorrect position.
 			 *
 			 * @error view-writer-cannot-break-empty-element
 			 */
-			throw new CKEditorError( 'view-writer-cannot-break-empty-element', this.document );
+			throw new CKEditorError( 'view-writer-cannot-break-empty-element: Cannot break an EmptyElement instance.', this.document );
 		}
 
 		// If position is placed inside UIElement - throw an exception as we cannot break inside.
 		if ( position.parent.is( 'uiElement' ) ) {
 			/**
-			 * Cannot break inside UIElement instance.
+			 * Cannot break a `UIElement` instance.
+			 *
+			 * This error is thrown if
+			 * {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes `DowncastWriter#breakAttributes()`}
+			 * was executed in an incorrect position.
 			 *
 			 * @error view-writer-cannot-break-ui-element
 			 */
-			throw new CKEditorError( 'view-writer-cannot-break-ui-element', this.document );
+			throw new CKEditorError( 'view-writer-cannot-break-ui-element: Cannot break a UIElement instance.', this.document );
 		}
 
 		// If position is placed inside RawElement - throw an exception as we cannot break inside.
 		if ( position.parent.is( 'rawElement' ) ) {
 			/**
-			 * Cannot break inside RawElement instance.
+			 * Cannot break a `RawElement` instance.
+			 *
+			 * This error is thrown if
+			 * {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes `DowncastWriter#breakAttributes()`}
+			 * was executed in an incorrect position.
 			 *
 			 * @error view-writer-cannot-break-raw-element
 			 */
-			throw new CKEditorError( 'view-writer-cannot-break-raw-element: Cannot break inside a RawElement instance.', this.document );
+			throw new CKEditorError( 'view-writer-cannot-break-raw-element: Cannot break a RawElement instance.', this.document );
 		}
 
 		// There are no attributes to break and text nodes breaking is not forced.
@@ -1770,7 +1792,8 @@ function _hasNonUiChildren( parent ) {
 }
 
 /**
- * Attribute element need to be instance of attribute element.
+ * The `attribute` passed to {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#wrap()`}
+ * must be an instance of {@link module:engine/view/attributeelement~AttributeElement `AttributeElement`}.
  *
  * @error view-writer-wrap-invalid-attribute
  */
@@ -1904,14 +1927,24 @@ function validateNodesToInsert( nodes, errorContext ) {
 	for ( const node of nodes ) {
 		if ( !validNodesToInsert.some( ( validNode => node instanceof validNode ) ) ) { // eslint-disable-line no-use-before-define
 			/**
-			 * Inserted nodes should be valid to insert. of {@link module:engine/view/attributeelement~AttributeElement AttributeElement},
-			 * {@link module:engine/view/containerelement~ContainerElement ContainerElement},
-			 * {@link module:engine/view/emptyelement~EmptyElement EmptyElement},
-			 * {@link module:engine/view/uielement~UIElement UIElement}, {@link module:engine/view/text~Text Text}.
+			 * One of the nodes to be inserted is of invalid type.
+			 *
+			 * Nodes to be inserted with {@link module:engine/view/downcastwriter~DowncastWriter#insert `DowncastWriter#insert()`} should be
+			 * of the following types:
 			 *
-			 * @error view-writer-insert-invalid-node
+			 * * {@link module:engine/view/attributeelement~AttributeElement AttributeElement},
+			 * * {@link module:engine/view/containerelement~ContainerElement ContainerElement},
+			 * * {@link module:engine/view/emptyelement~EmptyElement EmptyElement},
+			 * * {@link module:engine/view/uielement~UIElement UIElement},
+			 * * {@link module:engine/view/rawelement~RawElement RawElement},
+			 * * {@link module:engine/view/text~Text Text}.
+			 *
+			 * @error view-writer-insert-invalid-node-type
 			 */
-			throw new CKEditorError( 'view-writer-insert-invalid-node', errorContext );
+			throw new CKEditorError(
+				'view-writer-insert-invalid-node-type: One of the nodes to be inserted is of invalid type.',
+				errorContext
+			);
 		}
 
 		if ( !node.is( '$text' ) ) {
@@ -1942,14 +1975,22 @@ function validateRangeContainer( range, errorContext ) {
 
 	if ( !startContainer || !endContainer || startContainer !== endContainer ) {
 		/**
-		 * Range container is invalid. This can happen if {@link module:engine/view/range~Range#start range start} and
-		 * {@link module:engine/view/range~Range#end range end} positions are not placed inside same container or
-		 * parent container for these positions cannot be found.
+		 * The container of the given range is invalid.
+		 *
+		 * This may happen if {@link module:engine/view/range~Range#start range start} and
+		 * {@link module:engine/view/range~Range#end range end} positions are not placed inside the same container element or
+		 * a parent container for these positions cannot be found.
+		 *
+		 * Methods like {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#remove()`},
+		 * {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#clean()`},
+		 * {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#wrap()`},
+		 * {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#unwrap()`} need to be called
+		 * on a range that have its start and end positions located in the same container element. Both positions can be
+		 * nested within other elements (e.g. an attribute element) but the closest container ancestor must be the same.
 		 *
 		 * @error view-writer-invalid-range-container
 		 */
-
-		throw new CKEditorError( 'view-writer-invalid-range-container', errorContext );
+		throw new CKEditorError( 'view-writer-invalid-range-container: The container of the given range is invalid.', errorContext );
 	}
 }
 

+ 11 - 4
packages/ckeditor5-link/src/linkui.js

@@ -394,6 +394,10 @@ export default class LinkUI extends Plugin {
 	_showUI( forceVisible = false ) {
 		// When there's no link under the selection, go straight to the editing UI.
 		if ( !this._getSelectedLinkElement() ) {
+			// Show visual selection on a text without a link when the contextual balloon is displayed.
+			// See https://github.com/ckeditor/ckeditor5/issues/4721.
+			this._showFakeVisualSelection();
+
 			this._addActionsView();
 
 			// Be sure panel with link is visible.
@@ -402,9 +406,6 @@ export default class LinkUI extends Plugin {
 			}
 
 			this._addFormView();
-			// Show visual selection on a text without a link when the contextual balloon is displayed.
-			// See https://github.com/ckeditor/ckeditor5/issues/4721.
-			this._showFakeVisualSelection();
 		}
 		// If there's a link under the selection...
 		else {
@@ -586,14 +587,20 @@ export default class LinkUI extends Plugin {
 	 */
 	_getBalloonPositionData() {
 		const view = this.editor.editing.view;
+		const model = this.editor.model;
 		const viewDocument = view.document;
 		const targetLink = this._getSelectedLinkElement();
+		const range = model.markers.has( VISUAL_SELECTION_MARKER_NAME ) ?
+			// There are cases when we highlight selection using a marker (#7705, #4721).
+			this.editor.editing.mapper.toViewRange( model.markers.get( VISUAL_SELECTION_MARKER_NAME ).getRange() ) :
+			// If no markers are available refer to a regular selection.
+			viewDocument.selection.getFirstRange();
 
 		const target = targetLink ?
 			// When selection is inside link element, then attach panel to this element.
 			view.domConverter.mapViewToDom( targetLink ) :
 			// Otherwise attach panel to the selection.
-			view.domConverter.viewRangeToDom( viewDocument.selection.getFirstRange() );
+			view.domConverter.viewRangeToDom( range );
 
 		return { target };
 	}

+ 17 - 0
packages/ckeditor5-link/tests/linkui.js

@@ -162,6 +162,23 @@ describe( 'LinkUI', () => {
 			} );
 		} );
 
+		it( 'should pass a proper position target to the balloon toolbar', () => {
+			setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );
+
+			linkUIFeature._showUI();
+
+			const markerModelRange = editor.model.markers.get( 'link-ui' ).getRange();
+			const markerViewRange = editor.editing.mapper.toViewRange( markerModelRange );
+			const domRange = editor.editing.view.domConverter.viewRangeToDom( markerViewRange );
+
+			expect( balloonAddSpy.calledWithExactly( {
+				view: formView,
+				position: {
+					target: domRange
+				}
+			} ), 'spy arguments' ).to.be.true;
+		} );
+
 		it( 'should add #actionsView to the balloon and attach the balloon to the link element when collapsed selection is inside ' +
 			'that link',
 		() => {

+ 1 - 5
packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.js

@@ -11,11 +11,7 @@ import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articleplugi
 import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
 import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
 
-import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor';
-
-function Markdown( editor ) {
-	editor.data.processor = new GFMDataProcessor( editor.editing.view.document );
-}
+import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown';
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-markdown' ), {

+ 2 - 7
packages/ckeditor5-markdown-gfm/docs/features/markdown.md

@@ -31,7 +31,7 @@ To enable this data processor in your editor install the [`@ckeditor/ckeditor5-m
 npm install --save @ckeditor/ckeditor5-markdown-gfm
 ```
 
-Then, you can enable this data processor by creating a simple plugin which will load it:
+Then, you can enable this data processor by using {@link module:markdown-gfm/markdown~Markdown} plugin which will change default {@link module:engine/dataprocessor/dataprocessor~DataProcessor data processor} with {@link module:markdown-gfm/gfmdataprocessor~GFMDataProcessor}:
 
 ```js
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
@@ -41,12 +41,7 @@ import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
 // ...
 
-import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor';
-
-// Simple plugin which loads the data processor.
-function Markdown( editor ) {
-	editor.data.processor = new GFMDataProcessor( editor.editing.view.document );
-}
+import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown';
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-markdown' ), {

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

@@ -10,8 +10,14 @@
     "ckeditor5-plugin"
   ],
   "dependencies": {
+    "@ckeditor/ckeditor5-basic-styles": "^21.0.0",
+    "@ckeditor/ckeditor5-code-block": "^21.0.0",
+    "@ckeditor/ckeditor5-core": "^21.0.0",
+    "@ckeditor/ckeditor5-editor-classic": "^21.0.0",
     "@ckeditor/ckeditor5-engine": "^21.0.0",
-    "marked": "^0.7.0",
+    "@ckeditor/ckeditor5-list": "^21.0.0",
+    "@ckeditor/ckeditor5-table": "^21.0.0",
+    "marked": "^1.1.1",
     "turndown": "^6.0.0",
     "turndown-plugin-gfm": "^1.0.2"
   },

+ 36 - 0
packages/ckeditor5-markdown-gfm/src/markdown.js

@@ -0,0 +1,36 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module markdown-gfm/markdown
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import GFMDataProcessor from './gfmdataprocessor';
+
+/**
+ * The GitHub Flavored Markdown (GFM) plugin.
+ *
+ * For a detailed overview, check the {@glink features/markdown Markdown feature documentation}.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class Markdown extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		editor.data.processor = new GFMDataProcessor( editor.data.viewDocument );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'Markdown';
+	}
+}

+ 9 - 4
packages/ckeditor5-markdown-gfm/src/markdown2html/markdown2html.js

@@ -9,6 +9,15 @@
 
 import marked from 'marked';
 
+// Overrides.
+marked.use( {
+	tokenizer: {
+		// Disable the autolink rule in the lexer.
+		autolink: () => null,
+		url: () => null
+	}
+} );
+
 /**
  * Parses markdown string to an HTML.
  *
@@ -26,7 +35,3 @@ export default function markdown2html( markdown ) {
 }
 
 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$/;

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

@@ -115,8 +115,23 @@ describe( 'GFMDataProcessor', () => {
 						'<code>' +
 							'code 2' +
 						'</code>' +
-					'</pre>' +
-				'</blockquote>'
+					// Space after `</pre>` might be due to bug in engine. See: https://github.com/ckeditor/ckeditor5/issues/7863.
+					'</pre> ' +
+				'</blockquote>',
+
+				'> Example 1:\n' +
+				'>\n' +
+				'> ```\n' +
+				'> code 1\n' +
+				'> ```\n' +
+				'>\n' +
+				'> Example 2:\n' +
+				'>\n' +
+				'> ```\n' +
+				'> code 2\n' +
+				'> ```' +
+				// The below is an artefact of space after `</pre>`. See comment above & https://github.com/ckeditor/ckeditor5/issues/7863.
+				'\n>\n>'
 			);
 		} );
 
@@ -154,7 +169,8 @@ describe( 'GFMDataProcessor', () => {
 						'<code>' +
 							'code 2' +
 						'</code>' +
-					'</pre>' +
+					// Space after `</pre>` might be due to bug in engine. See: https://github.com/ckeditor/ckeditor5/issues/7863.
+					'</pre> ' +
 				'</blockquote>',
 
 				// When converting back to data, DataProcessor will normalize tabs to ```.
@@ -168,7 +184,9 @@ describe( 'GFMDataProcessor', () => {
 				'>\n' +
 				'> ```\n' +
 				'> code 2\n' +
-				'> ```'
+				'> ```' +
+				// The below is an artefact of space after `</pre>`. See comment above & https://github.com/ckeditor/ckeditor5/issues/7863.
+				'\n>\n>'
 			);
 		} );
 	} );

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

@@ -27,11 +27,11 @@ describe( 'GFMDataProcessor', () => {
 			testDataProcessor(
 				'regular text and` inline code`',
 
-				'<p>regular text and<code>inline code</code></p>',
+				'<p>regular text and<code> inline code</code></p>',
 
 				// When converting back it will be normalized and spaces
 				// at the beginning of inline code will be removed.
-				'regular text and`inline code`'
+				'regular text and `inline code`'
 			);
 		} );
 

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

@@ -20,7 +20,7 @@ const testCases = {
 	'right paren': { test: '\\)', result: ')' },
 	'greater than': { test: '\\>', result: '>' },
 	'hash': { test: '\\#', result: '#' },
-	'peroid': { test: '\\.', result: '.' },
+	'period': { test: '\\.', result: '.' },
 	'exclamation mark': { test: '\\!', result: '!' },
 	'plus': { test: '\\+', result: '+' },
 	'minus': { test: '\\-', result: '-' }

+ 31 - 0
packages/ckeditor5-markdown-gfm/tests/manual/markdown.html

@@ -0,0 +1,31 @@
+<style>
+	pre.markdown-output {
+		background: hsl(70, 7%, 16%);
+		color: hsl(0, 0%, 100%);
+		display: block;
+		font-size: 1em;
+		font-family: Monaco, Menlo, Consolas, "Roboto Mono", "Courier New", "Ubuntu Mono", monospace;
+		padding: 1.333em;
+	}
+</style>
+
+<div id="editor">This is [CKEditor 5](https://ckeditor.com).
+![Sample image](./sample.jpg)
+
+```javascript
+function fancyAlert(arg) {
+  if(arg) {
+    $.facebox({div:'#foo'})
+  }
+}
+```
+
+First Header | Second Header
+------------ | -------------
+Content from cell 1 | Content from cell 2
+Content in the first column | Content in the second column
+</div>
+
+<p>Output:</p>
+
+<pre class="markdown-output"><code id="markdown-output"></code></pre>

+ 71 - 0
packages/ckeditor5-markdown-gfm/tests/manual/markdown.js

@@ -0,0 +1,71 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document, setTimeout */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough';
+import TodoList from '@ckeditor/ckeditor5-list/src/todolist';
+import TableProperties from '@ckeditor/ckeditor5-table/src/tableproperties';
+import TableCellProperties from '@ckeditor/ckeditor5-table/src/tablecellproperties';
+import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock';
+import Code from '@ckeditor/ckeditor5-basic-styles/src/code';
+import Markdown from '../../src/markdown';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Markdown, ArticlePluginSet, Code, CodeBlock, Strikethrough, TodoList, TableProperties, TableCellProperties ],
+		toolbar: [
+			'heading',
+			'|',
+			'bold',
+			'italic',
+			'strikethrough',
+			'underline',
+			'link',
+			'|',
+			'code',
+			'codeBlock',
+			'|',
+			'todoList',
+			'bulletedList',
+			'numberedList',
+			'|',
+			'outdent',
+			'indent',
+			'|',
+			'blockQuote',
+			'insertTable',
+			'|',
+			'undo',
+			'redo'
+		],
+		image: {
+			toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
+		},
+		table: {
+			contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties' ],
+			tableToolbar: [ 'bold', 'italic' ]
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+
+		const outputElement = document.querySelector( '#markdown-output' );
+
+		editor.model.document.on( 'change', () => {
+			outputElement.innerText = editor.getData();
+		} );
+
+		// Set the initial data with delay so hightlight.js doesn't catch them.
+		setTimeout( () => {
+			outputElement.innerText = editor.getData();
+		}, 500 );
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 9 - 0
packages/ckeditor5-markdown-gfm/tests/manual/markdown.md

@@ -0,0 +1,9 @@
+#### GitHub Flavored Markdown Editor
+
+- Play around with [GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/).
+- See the markdown generated in the "output" box.
+- You can use `editor.setData( markdownString )` to set markdown content:
+
+```js
+editor.setData( 'This\n\n*is*\n\n**MARKDOWN!**' );
+```

二进制
packages/ckeditor5-markdown-gfm/tests/manual/sample.jpg


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

@@ -0,0 +1,26 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import Markdown from '../src/markdown';
+import GFMDataProcessor from '../src/gfmdataprocessor';
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+
+describe( 'Markdown', () => {
+	it( 'has proper name', () => {
+		expect( Markdown.pluginName ).to.equal( 'Markdown' );
+	} );
+
+	it( 'should set editor.data.processor', () => {
+		return ClassicTestEditor
+			.create( '', {
+				plugins: [ Markdown ]
+			} )
+			.then( editor => {
+				expect( editor.data.processor ).to.be.an.instanceof( GFMDataProcessor );
+
+				editor.destroy(); // Tests cleanup.
+			} );
+	} );
+} );

+ 1 - 2
packages/ckeditor5-mention/src/mentionui.js

@@ -34,7 +34,6 @@ const handledKeyCodes = [
 	keyCodes.arrowdown,
 	keyCodes.enter,
 	keyCodes.tab,
-	keyCodes.space,
 	keyCodes.esc
 ];
 
@@ -121,7 +120,7 @@ export default class MentionUI extends Plugin {
 					this._mentionsView.selectPrevious();
 				}
 
-				if ( data.keyCode == keyCodes.enter || data.keyCode == keyCodes.tab || data.keyCode == keyCodes.space ) {
+				if ( data.keyCode == keyCodes.enter || data.keyCode == keyCodes.tab ) {
 					this._mentionsView.executeSelected();
 				}
 

+ 23 - 5
packages/ckeditor5-mention/tests/mentionui.js

@@ -1572,8 +1572,30 @@ describe( 'MentionUI', () => {
 				testExecuteKey( 'enter', keyCodes.enter, feedItems );
 
 				testExecuteKey( 'tab', keyCodes.tab, feedItems );
+			} );
+
+			describe( 'on other keys', () => {
+				it( 'should do nothing on space', async () => {
+					setData( model, '<paragraph>foo []</paragraph>' );
+
+					model.change( writer => {
+						writer.insertText( '@', doc.selection.getFirstPosition() );
+					} );
+
+					const command = editor.commands.get( 'mention' );
+					const spy = testUtils.sinon.spy( command, 'execute' );
+
+					await waitForDebounce();
+					expectChildViewsIsOnState( [ true, false, false, false, false ] );
 
-				testExecuteKey( 'space', keyCodes.space, feedItems );
+					fireKeyDownEvent( {
+						keyCodes: keyCodes.space,
+						preventDefault: sinon.spy(),
+						stopPropagation: sinon.spy()
+					} );
+
+					sinon.assert.notCalled( spy );
+				} );
 			} );
 		} );
 
@@ -1724,8 +1746,6 @@ describe( 'MentionUI', () => {
 					testExecuteKey( 'enter', keyCodes.enter, issues );
 
 					testExecuteKey( 'tab', keyCodes.tab, issues );
-
-					testExecuteKey( 'space', keyCodes.space, issues );
 				} );
 			} );
 		} );
@@ -1849,8 +1869,6 @@ describe( 'MentionUI', () => {
 					testExecuteKey( 'enter', keyCodes.enter, issues );
 
 					testExecuteKey( 'tab', keyCodes.tab, issues );
-
-					testExecuteKey( 'space', keyCodes.space, issues );
 				} );
 
 				describe( 'mouse', () => {

+ 19 - 4
packages/ckeditor5-table/src/tablewalker.js

@@ -516,8 +516,23 @@ class TableSlot {
 		return model.createPositionAt( this._table.getChild( this.row ), this._cellIndex );
 	}
 
-	// @if CK_DEBUG // get isSpanned() { throw new CKEditorError( 'tablewalker-improper-api-usage', this ); }
-	// @if CK_DEBUG // get colspan() { throw new CKEditorError( 'tablewalker-improper-api-usage', this ); }
-	// @if CK_DEBUG // get rowspan() { throw new CKEditorError( 'tablewalker-improper-api-usage', this ); }
-	// @if CK_DEBUG // get cellIndex() { throw new CKEditorError( 'tablewalker-improper-api-usage', this ); }
+	// @if CK_DEBUG // get isSpanned() { throwMissingGetterError( 'isSpanned' ); }
+	// @if CK_DEBUG // get colspan() { throwMissingGetterError( 'colspan' ); }
+	// @if CK_DEBUG // get rowspan() { throwMissingGetterError( 'rowspan' ); }
+	// @if CK_DEBUG // get cellIndex() { throwMissingGetterError( 'cellIndex' ); }
 }
+
+/**
+ * This `TableSlot`'s getter (property) was removed in CKEditor 5 v20.0.0.
+ *
+ * Check out the new `TableWalker`'s API in the documentation.
+ *
+ * @error tableslot-getter-removed
+ * @param {String} getterName
+ */
+
+// @if CK_DEBUG // function throwMissingGetterError( getterName ) {
+// @if CK_DEBUG //		throw new CKEditorError( 'tableslot-getter-removed: This TableSlot getter does not exist any more.', this, {
+// @if CK_DEBUG //			getterName
+// @if CK_DEBUG //		} );
+// @if CK_DEBUG // }

+ 13 - 28
packages/ckeditor5-table/src/utils/ui/contextualballoon.js

@@ -87,11 +87,7 @@ export function getBalloonCellPositionData( editor ) {
 
 	if ( selection.rangeCount > 1 ) {
 		return {
-			target: () => createBoundingRect( selection.getRanges(), modelRange => {
-				const modelTableCell = getTableCellAtPosition( modelRange.start );
-				const viewTableCell = mapper.toViewElement( modelTableCell );
-				return new Rect( domConverter.viewToDom( viewTableCell ) );
-			} ),
+			target: () => createBoundingRect( selection.getRanges(), editor ),
 			positions: BALLOON_POSITIONS
 		};
 	}
@@ -115,30 +111,19 @@ function getTableCellAtPosition( position ) {
 	return isTableCellSelected ? position.nodeAfter : position.findAncestor( 'tableCell' );
 }
 
-// Returns bounding rect for list of rects.
+// Returns bounding rectangle for given model ranges.
 //
-// @param {Array.<module:utils/dom/rect~Rect>|Array.<*>} list List of `Rect`s or any list to map by `mapFn`.
-// @param {Function} mapFn Mapping function for list elements.
+// @param {Iterable.<module:engine/model/range~Range>} ranges Model ranges that the bounding rect should be returned for.
+// @param {module:core/editor/editor~Editor} editor The editor instance.
 // @returns {module:utils/dom/rect~Rect}
-function createBoundingRect( list, mapFn ) {
-	const rectData = {
-		left: Number.POSITIVE_INFINITY,
-		top: Number.POSITIVE_INFINITY,
-		right: Number.NEGATIVE_INFINITY,
-		bottom: Number.NEGATIVE_INFINITY
-	};
-
-	for ( const item of list ) {
-		const rect = mapFn( item );
-
-		rectData.left = Math.min( rectData.left, rect.left );
-		rectData.top = Math.min( rectData.top, rect.top );
-		rectData.right = Math.max( rectData.right, rect.right );
-		rectData.bottom = Math.max( rectData.bottom, rect.bottom );
-	}
-
-	rectData.width = rectData.right - rectData.left;
-	rectData.height = rectData.bottom - rectData.top;
+function createBoundingRect( ranges, editor ) {
+	const mapper = editor.editing.mapper;
+	const domConverter = editor.editing.view.domConverter;
+	const rects = Array.from( ranges ).map( range => {
+		const modelTableCell = getTableCellAtPosition( range.start );
+		const viewTableCell = mapper.toViewElement( modelTableCell );
+		return new Rect( domConverter.viewToDom( viewTableCell ) );
+	} );
 
-	return new Rect( rectData );
+	return Rect.getBoundingRect( rects );
 }

+ 4 - 4
packages/ckeditor5-table/tests/tablewalker.js

@@ -629,9 +629,9 @@ describe( 'TableWalker', () => {
 
 		const { value } = walker.next();
 
-		expect( () => value.isSpanned ).to.throw( CKEditorError, 'tablewalker-improper-api-usage' );
-		expect( () => value.colspan ).to.throw( CKEditorError, 'tablewalker-improper-api-usage' );
-		expect( () => value.rowspan ).to.throw( CKEditorError, 'tablewalker-improper-api-usage' );
-		expect( () => value.cellIndex ).to.throw( CKEditorError, 'tablewalker-improper-api-usage' );
+		expect( () => value.isSpanned ).to.throw( CKEditorError, 'tableslot-getter-removed' );
+		expect( () => value.colspan ).to.throw( CKEditorError, 'tableslot-getter-removed' );
+		expect( () => value.rowspan ).to.throw( CKEditorError, 'tableslot-getter-removed' );
+		expect( () => value.cellIndex ).to.throw( CKEditorError, 'tableslot-getter-removed' );
 	} );
 } );

+ 5 - 4
packages/ckeditor5-utils/src/collection.js

@@ -201,11 +201,12 @@ export default class Collection {
 			index = this._items.length;
 		} else if ( index > this._items.length || index < 0 ) {
 			/**
-			 * The index number has invalid value.
+			 * The `index` passed to {@link module:utils/collection~Collection#addMany `Collection#addMany()`}
+			 * is invalid. It must be a number between 0 and the the collection's length.
 			 *
 			 * @error collection-add-item-bad-index
 			 */
-			throw new CKEditorError( 'collection-add-item-invalid-index', this );
+			throw new CKEditorError( 'collection-add-item-invalid-index: The index passed to Collection#addMany() is invalid.', this );
 		}
 
 		for ( let offset = 0; offset < items.length; offset++ ) {
@@ -645,7 +646,7 @@ export default class Collection {
 				 *
 				 * @error collection-add-invalid-id
 				 */
-				throw new CKEditorError( 'collection-add-invalid-id', this );
+				throw new CKEditorError( 'collection-add-invalid-id: This item\'s id should be a string.', this );
 			}
 
 			if ( this.get( itemId ) ) {
@@ -654,7 +655,7 @@ export default class Collection {
 				 *
 				 * @error collection-add-item-already-exists
 				 */
-				throw new CKEditorError( 'collection-add-item-already-exists', this );
+				throw new CKEditorError( 'collection-add-item-already-exists: This item already exists in the collection.', this );
 			}
 		} else {
 			item[ idProperty ] = itemId = uid();

+ 36 - 1
packages/ckeditor5-utils/src/dom/rect.js

@@ -78,7 +78,8 @@ export default class Rect {
 			// @if CK_DEBUG // }
 
 			if ( isSourceRange ) {
-				copyRectProperties( this, Rect.getDomRangeRects( source )[ 0 ] );
+				const rangeRects = Rect.getDomRangeRects( source );
+				copyRectProperties( this, Rect.getBoundingRect( rangeRects ) );
 			} else {
 				copyRectProperties( this, source.getBoundingClientRect() );
 			}
@@ -381,6 +382,40 @@ export default class Rect {
 
 		return rects;
 	}
+
+	/**
+	 * Returns a bounding rectangle that contains all the given `rects`.
+	 *
+	 * @param {Iterable.<module:utils/dom/rect~Rect>} rects A list of rectangles that should be contained in the result rectangle.
+	 * @returns {module:utils/dom/rect~Rect|null} Bounding rectangle or `null` if no `rects` were given.
+	 */
+	static getBoundingRect( rects ) {
+		const boundingRectData = {
+			left: Number.POSITIVE_INFINITY,
+			top: Number.POSITIVE_INFINITY,
+			right: Number.NEGATIVE_INFINITY,
+			bottom: Number.NEGATIVE_INFINITY
+		};
+		let rectangleCount = 0;
+
+		for ( const rect of rects ) {
+			rectangleCount++;
+
+			boundingRectData.left = Math.min( boundingRectData.left, rect.left );
+			boundingRectData.top = Math.min( boundingRectData.top, rect.top );
+			boundingRectData.right = Math.max( boundingRectData.right, rect.right );
+			boundingRectData.bottom = Math.max( boundingRectData.bottom, rect.bottom );
+		}
+
+		if ( rectangleCount == 0 ) {
+			return null;
+		}
+
+		boundingRectData.width = boundingRectData.right - boundingRectData.left;
+		boundingRectData.height = boundingRectData.bottom - boundingRectData.top;
+
+		return new Rect( boundingRectData );
+	}
 }
 
 // Acquires all the rect properties from the passed source.

+ 6 - 1
packages/ckeditor5-utils/src/focustracker.js

@@ -77,7 +77,12 @@ export default class FocusTracker {
 	 */
 	add( element ) {
 		if ( this._elements.has( element ) ) {
-			throw new CKEditorError( 'focusTracker-add-element-already-exist', this );
+			/**
+			 * This element is already tracked by {@link module:utils/focustracker~FocusTracker}.
+			 *
+			 * @error focusTracker-add-element-already-exist
+			 */
+			throw new CKEditorError( 'focusTracker-add-element-already-exist: This element is already tracked by FocusTracker.', this );
 		}
 
 		this.listenTo( element, 'focus', () => this._focus( element ), { useCapture: true } );

+ 86 - 0
packages/ckeditor5-utils/tests/dom/rect.js

@@ -52,6 +52,45 @@ describe( 'Rect', () => {
 			assertRect( new Rect( range ), geometry );
 		} );
 
+		it( 'should accept Range (non–collapsed, sequenced horizontally)', () => {
+			const firstGeometry = geometry;
+			const secondGeometry = Object.assign( {}, geometry, {
+				right: 50,
+				left: 40,
+				width: 10
+			} );
+
+			const range = document.createRange();
+			range.selectNode( document.body );
+			sinon.stub( range, 'getClientRects' ).returns( [ firstGeometry, secondGeometry ] );
+
+			const expectedGeometry = Object.assign( {}, geometry, {
+				width: 30,
+				right: 50
+			} );
+
+			assertRect( new Rect( range ), expectedGeometry );
+		} );
+
+		it( 'should accept Range (non–collapsed, sequenced vertically)', () => {
+			const firstGeometry = geometry;
+			const secondGeometry = Object.assign( {}, geometry, {
+				top: 30,
+				bottom: 40
+			} );
+
+			const range = document.createRange();
+			range.selectNode( document.body );
+			sinon.stub( range, 'getClientRects' ).returns( [ firstGeometry, secondGeometry ] );
+
+			const expectedGeometry = Object.assign( {}, geometry, {
+				height: 30,
+				bottom: 40
+			} );
+
+			assertRect( new Rect( range ), expectedGeometry );
+		} );
+
 		// https://github.com/ckeditor/ckeditor5-utils/issues/153
 		it( 'should accept Range (collapsed)', () => {
 			const range = document.createRange();
@@ -1053,6 +1092,53 @@ describe( 'Rect', () => {
 			assertRect( rects[ 0 ], expectedGeometry );
 		} );
 	} );
+
+	describe( 'getBoundingRect()', () => {
+		it( 'should not return a rect instance when no rectangles were given', () => {
+			expect( Rect.getBoundingRect( [] ) ).to.be.null;
+		} );
+
+		it( 'should calculate proper rectangle when multiple rectangles were given', () => {
+			const rects = [
+				new Rect( geometry ),
+				new Rect( {
+					top: 10,
+					right: 100,
+					bottom: 20,
+					left: 80,
+					width: 20,
+					height: 10
+				} ),
+				new Rect( {
+					top: 50,
+					right: 50,
+					bottom: 60,
+					left: 30,
+					width: 20,
+					height: 10
+				} )
+			];
+
+			assertRect( Rect.getBoundingRect( rects ), {
+				top: 10,
+				right: 100,
+				bottom: 60,
+				left: 20,
+				width: 80,
+				height: 50
+			} );
+		} );
+
+		it( 'should calculate proper rectangle when a single rectangles was given', () => {
+			const rectangles = new Set( [ new Rect( geometry ) ] );
+			assertRect( Rect.getBoundingRect( rectangles ), geometry );
+		} );
+
+		it( 'should return proper type', () => {
+			const rectangles = new Set( [ new Rect( geometry ) ] );
+			expect( Rect.getBoundingRect( rectangles ) ).to.be.instanceOf( Rect );
+		} );
+	} );
 } );
 
 function assertRect( rect, expected ) {

+ 79 - 56
yarn.lock

@@ -959,10 +959,10 @@
     style-loader "^1.2.1"
     webpack "^4.43.0"
 
-"@ckeditor/ckeditor5-dev-utils@^20.0.0":
-  version "20.0.0"
-  resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-utils/-/ckeditor5-dev-utils-20.0.0.tgz#f4d467845b7d2a859edb41acf5297f693feed72d"
-  integrity sha512-dJTrmIixAgWrozC9JlMg8GOCXd5v36Wnat8auFTvrUnwf5DM8fGLZ1S+XXhYfOfQTCV8wxwSv+aC3FUHLcHy1A==
+"@ckeditor/ckeditor5-dev-utils@>=13.0.0", "@ckeditor/ckeditor5-dev-utils@^23.1.1":
+  version "23.1.1"
+  resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-utils/-/ckeditor5-dev-utils-23.1.1.tgz#bf758fb798ca1205e73cbbdbeaeae2eee02fff2c"
+  integrity sha512-2Oq6ycNhCX53pTaVH4rXCZ5SedYOI4VIrs2p1kEPpnP5dSMQG3v1aoYee4+NWJKD0mUUFyYjPtvPeA2PPI4Urg==
   dependencies:
     acorn "^6.2.1"
     acorn-walk "^6.2.0"
@@ -980,10 +980,10 @@
     shelljs "^0.8.1"
     through2 "^3.0.1"
 
-"@ckeditor/ckeditor5-dev-utils@^23.1.1":
-  version "23.1.1"
-  resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-utils/-/ckeditor5-dev-utils-23.1.1.tgz#bf758fb798ca1205e73cbbdbeaeae2eee02fff2c"
-  integrity sha512-2Oq6ycNhCX53pTaVH4rXCZ5SedYOI4VIrs2p1kEPpnP5dSMQG3v1aoYee4+NWJKD0mUUFyYjPtvPeA2PPI4Urg==
+"@ckeditor/ckeditor5-dev-utils@^20.0.0":
+  version "20.0.0"
+  resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-utils/-/ckeditor5-dev-utils-20.0.0.tgz#f4d467845b7d2a859edb41acf5297f693feed72d"
+  integrity sha512-dJTrmIixAgWrozC9JlMg8GOCXd5v36Wnat8auFTvrUnwf5DM8fGLZ1S+XXhYfOfQTCV8wxwSv+aC3FUHLcHy1A==
   dependencies:
     acorn "^6.2.1"
     acorn-walk "^6.2.0"
@@ -1189,9 +1189,9 @@
     universal-user-agent "^6.0.0"
 
 "@octokit/graphql@^4.3.1":
-  version "4.5.3"
-  resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.3.tgz#d5ff0d4a8a33e98614a2a7359dac98bc285e062f"
-  integrity sha512-JyYvi3j2tOb5ofASEpcg1Advs07H+Ag+I+ez7buuZfNVAmh1IYcDTuxd4gnYH8S2PSGu+f5IdDGxMmkK+5zsdA==
+  version "4.5.4"
+  resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.4.tgz#c9ef75b0406ebf195bf5f4ed2304a77ed7df27c7"
+  integrity sha512-ITpZ+dQc0cXAW1FmDkHJJM+8Lb6anUnin0VB5hLBilnYVdLC0ICFU/KIvT7OXfW9S81DE3U4Vx2EypDG1OYaPA==
   dependencies:
     "@octokit/request" "^5.3.0"
     "@octokit/types" "^5.0.0"
@@ -1414,9 +1414,9 @@
   integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
 
 "@types/node@*", "@types/node@>= 8", "@types/node@^14.0.18":
-  version "14.0.27"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1"
-  integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==
+  version "14.6.0"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.0.tgz#7d4411bf5157339337d7cff864d9ff45f177b499"
+  integrity sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==
 
 "@types/node@^13.7.0":
   version "13.13.15"
@@ -1595,6 +1595,20 @@
     "@webassemblyjs/wast-parser" "1.9.0"
     "@xtuc/long" "4.2.2"
 
+"@webspellchecker/wproofreader-ckeditor5@^1.0.5":
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/@webspellchecker/wproofreader-ckeditor5/-/wproofreader-ckeditor5-1.0.5.tgz#f6652f7705fd4132ed7adae419acd9d6998895df"
+  integrity sha512-uw5FfEZcWRagCO26yRaXzbV9xyjFR3VRGzWexlp465ZFwm0At9KtXUrvbkMF+RLhuKmXvo4df1v957pG103dWg==
+  dependencies:
+    "@ckeditor/ckeditor5-basic-styles" ">=16.0.0"
+    "@ckeditor/ckeditor5-dev-utils" ">=13.0.0"
+    "@ckeditor/ckeditor5-editor-classic" ">=16.0.0"
+    "@ckeditor/ckeditor5-essentials" ">=16.0.0"
+    "@ckeditor/ckeditor5-heading" ">=16.0.0"
+    "@ckeditor/ckeditor5-list" ">=16.0.0"
+    "@ckeditor/ckeditor5-paragraph" ">=16.0.0"
+    "@ckeditor/ckeditor5-theme-lark" ">=16.0.0"
+
 "@wiris/mathtype-ckeditor5@7.20.0":
   version "7.20.0"
   resolved "https://registry.yarnpkg.com/@wiris/mathtype-ckeditor5/-/mathtype-ckeditor5-7.20.0.tgz#fcbefc2c6934d2ca94721021f05fb912880f4142"
@@ -1743,9 +1757,9 @@ ajv@^5.0.0:
     json-schema-traverse "^0.3.0"
 
 ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3:
-  version "6.12.3"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706"
-  integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==
+  version "6.12.4"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234"
+  integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==
   dependencies:
     fast-deep-equal "^3.1.1"
     fast-json-stable-stringify "^2.0.0"
@@ -2378,9 +2392,9 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
   integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
 
 bn.js@^5.1.1:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
-  integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
+  version "5.1.3"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
+  integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
 
 body-parser@^1.19.0:
   version "1.19.0"
@@ -2722,9 +2736,9 @@ caniuse-api@^3.0.0:
     lodash.uniq "^4.5.0"
 
 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==
+  version "1.0.30001115"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001115.tgz#c04cd828883ba47f6f776312e0817bcc9040cfa4"
+  integrity sha512-NZrG0439ePYna44lJX8evHX2L7Z3/z3qjVLnHgbBb/duNEnGo348u+BQS5o4HTWcrb++100dHFrU36IesIrC1Q==
 
 caseless@~0.12.0:
   version "0.12.0"
@@ -4027,7 +4041,7 @@ dom-serialize@^2.2.1:
     extend "^3.0.0"
     void-elements "^2.0.0"
 
-dom-serializer@0, dom-serializer@^0.2.1:
+dom-serializer@0:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
   integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
@@ -4035,6 +4049,15 @@ dom-serializer@0, dom-serializer@^0.2.1:
     domelementtype "^2.0.1"
     entities "^2.0.0"
 
+dom-serializer@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.0.1.tgz#79695eb49af3cd8abc8d93a73da382deb1ca0795"
+  integrity sha512-1Aj1Qy3YLbdslkI75QEOfdp9TkQ3o8LRISAzxOibjBs/xWwr1WxZFOQphFkZuepHFGo+kB8e5FVJSS0faAJ4Rw==
+  dependencies:
+    domelementtype "^2.0.1"
+    domhandler "^3.0.0"
+    entities "^2.0.0"
+
 dom-serializer@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
@@ -4096,11 +4119,11 @@ domutils@^1.5.1, domutils@^1.7.0, domutils@~1.7.0:
     domelementtype "1"
 
 domutils@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.1.0.tgz#7ade3201af43703fde154952e3a868eb4b635f16"
-  integrity sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.2.0.tgz#f3ce1610af5c30280bde1b71f84b018b958f32cf"
+  integrity sha512-0haAxVr1PR0SqYwCH7mxMpHZUwjih9oPPedqpR/KufsnxPyZ9dyVw1R5093qnJF3WXSbjBkdzRWLw/knJV/fAg==
   dependencies:
-    dom-serializer "^0.2.1"
+    dom-serializer "^1.0.1"
     domelementtype "^2.0.1"
     domhandler "^3.0.0"
 
@@ -4167,9 +4190,9 @@ ee-first@1.1.1:
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
 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==
+  version "1.3.534"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.534.tgz#fc7af8518dd00a5b22a24aed3f116b5d097e2330"
+  integrity sha512-7x2S3yUrspNHQOoPk+Eo+iHViSiJiEGPI6BpmLy1eT2KRNGCkBt/NUYqjfXLd1DpDCQp7n3+LfA1RkbG+LqTZQ==
 
 elliptic@^6.5.3:
   version "6.5.3"
@@ -4414,9 +4437,9 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
   integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
 
 eslint@^7.1.0:
-  version "7.6.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.6.0.tgz#522d67cfaea09724d96949c70e7a0550614d64d6"
-  integrity sha512-QlAManNtqr7sozWm5TF4wIH9gmUm2hE3vNRUvyoYAa4y1l5/jxD/PQStEjBMQtCqZmSep8UxrcecI60hOpe61w==
+  version "7.7.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.7.0.tgz#18beba51411927c4b64da0a8ceadefe4030d6073"
+  integrity sha512-1KUxLzos0ZVsyL81PnRN335nDtQ8/vZUD6uMtWbF+5zDtjKcsklIi78XoE0MVL93QvWTu+E5y44VyyCsOMBrIg==
   dependencies:
     "@babel/code-frame" "^7.0.0"
     ajv "^6.10.0"
@@ -7020,9 +7043,9 @@ lint-staged@^10.2.6:
     stringify-object "^3.3.0"
 
 listr2@^2.1.0:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.5.1.tgz#f265dddf916c8a9b475437b34ae85a7d8f495c7a"
-  integrity sha512-qkNRW70SwfwWLD/eiaTf2tfgWT/ZvjmMsnEFJOCzac0cjcc8rYHDBr1eQhRxopj6lZO7Oa5sS/pZzS6q+BsX+w==
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.6.0.tgz#788a3d202978a1b8582062952cbc49272c8e206a"
+  integrity sha512-nwmqTJYQQ+AsKb4fCXH/6/UmLCEDL1jkRAdSn9M6cEUzoRGrs33YD/3N86gAZQnGZ6hxV18XSdlBcJ1GTmetJA==
   dependencies:
     chalk "^4.1.0"
     cli-truncate "^2.1.0"
@@ -7203,9 +7226,9 @@ lodash.uniq@^4.5.0:
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
 lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@~4.17.10, lodash@~4.17.14:
-  version "4.17.19"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
-  integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
+  version "4.17.20"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
+  integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
 
 log-driver@^1.2.7:
   version "1.2.7"
@@ -7461,16 +7484,16 @@ 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"
   integrity sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==
 
+marked@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/marked/-/marked-1.1.1.tgz#e5d61b69842210d5df57b05856e0c91572703e6a"
+  integrity sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw==
+
 marked@~0.3.6:
   version "0.3.19"
   resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
@@ -7751,10 +7774,10 @@ minipass@^3.0.0, minipass@^3.1.1:
   dependencies:
     yallist "^4.0.0"
 
-minizlib@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3"
-  integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==
+minizlib@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
+  integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
   dependencies:
     minipass "^3.0.0"
     yallist "^4.0.0"
@@ -9624,9 +9647,9 @@ querystring@0.2.0:
   integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
 
 querystringify@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
-  integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
+  integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
 
 quick-lru@^4.0.1:
   version "4.0.1"
@@ -11216,14 +11239,14 @@ tar@^2.0.0:
     inherits "2"
 
 tar@^6.0.2:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39"
-  integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==
+  version "6.0.5"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f"
+  integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==
   dependencies:
     chownr "^2.0.0"
     fs-minipass "^2.0.0"
     minipass "^3.0.0"
-    minizlib "^2.1.0"
+    minizlib "^2.1.1"
     mkdirp "^1.0.3"
     yallist "^4.0.0"