8
0
Просмотр исходного кода

Merge branch 'master' into i/7850

Maciej Gołaszewski 5 лет назад
Родитель
Сommit
4c1fdd35e1

+ 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 - 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 Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown';
+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';
+
+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!**' );
+```

BIN
packages/ckeditor5-markdown-gfm/tests/manual/sample.jpg


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

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

+ 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.

+ 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 ) {