浏览代码

Docs: Documented the WidgetTypeAround plugin and its utils.

Aleksander Nowodzinski 5 年之前
父节点
当前提交
17a9dfadb0

+ 0 - 34
packages/ckeditor5-widget/src/widgettypearound/mouseoveroutobserver.js

@@ -1,34 +0,0 @@
-/**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/**
- * @module widget/widgettypearound/mouseoveroutobserver
- */
-
-import DomEventObserver from '@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver';
-
-/**
- * TODO
- *
- * @extends module:engine/view/observer/domeventobserver~DomEventObserver
- */
-export default class MouseOverOutObserver extends DomEventObserver {
-	/**
-	 * @inheritDoc
-	 */
-	constructor( view ) {
-		super( view );
-
-		this.domEventType = [ 'mouseover', 'mouseout' ];
-		this.useCapture = true;
-	}
-
-	/**
-	 * @inheritDoc
-	 */
-	onDomEvent( domEvent ) {
-		this.fire( domEvent.type, domEvent );
-	}
-}

+ 43 - 10
packages/ckeditor5-widget/src/widgettypearound/utils.js

@@ -9,18 +9,47 @@
 
 import { isWidget } from '../utils';
 
+/**
+ * Checks if an element is a widget that qualifies to get the type around UI.
+ *
+ * @param {module:engine/view/element~Element} viewElement
+ * @param {module:engine/model/element~Element} modelElement
+ * @param {module:engine/model/schema~Schema} schema
+ * @returns {Boolean}
+ */
 export function isTypeAroundWidget( viewElement, modelElement, schema ) {
 	return viewElement && isWidget( viewElement ) && !schema.isInline( modelElement );
 }
 
+/**
+ * For the passed HTML element, this helper finds the closest type around button ancestor.
+ *
+ * @param {HTMLElement} domElement
+ * @returns {HTMLElement|null}
+ */
 export function getClosestTypeAroundDomButton( domElement ) {
 	return domElement.closest( '.ck-widget__type-around__button' );
 }
 
-export function getTypeAroundButtonDirection( domElement ) {
+/**
+ * For the passed type around button element, this helper determines at which position
+ * the paragraph would be inserted into the content if, for instance, the button was
+ * clicked by the user.
+ *
+ * @param {HTMLElement} domElement
+ * @returns {String} Either `'before'` or `'after'`.
+ */
+export function getTypeAroundButtonPosition( domElement ) {
 	return domElement.classList.contains( 'ck-widget__type-around__button_before' ) ? 'before' : 'after';
 }
 
+/**
+ * For the passed HTML element, this helper returns the closest view widget ancestor.
+ *
+ * @param {HTMLElement} domElement
+ * @param {module:engine/view/domconverter~DomConverter} domConverter
+ * @returns {module:engine/view/element~Element|null}
+ */
 export function getClosestWidgetViewElement( domElement, domConverter ) {
 	const widgetDomElement = domElement.closest( '.ck-widget' );
 
@@ -31,22 +60,26 @@ export function getClosestWidgetViewElement( domElement, domConverter ) {
 	return domConverter.mapDomToView( widgetDomElement );
 }
 
-export function directionToWidgetCssClass( direction ) {
-	return `ck-widget_can-type-around_${ direction }`;
-}
-
-export function getWidgetTypeAroundDirections( widgetViewElement ) {
-	const directions = [];
+/**
+ * For the passed widget view element, this helper returns an array of positions which
+ * correspond to the "tight spots" around the widget which cannot be accessed due to
+ * limitations of selection rendering in web browsers.
+ *
+ * @param {module:engine/view/element~Element} widgetViewElement
+ * @returns {Array.<String>}
+ */
+export function getWidgetTypeAroundPositions( widgetViewElement ) {
+	const positions = [];
 
 	if ( isFirstChild( widgetViewElement ) || hasPreviousWidgetSibling( widgetViewElement ) ) {
-		directions.push( 'before' );
+		positions.push( 'before' );
 	}
 
 	if ( isLastChild( widgetViewElement ) || hasNextWidgetSibling( widgetViewElement ) ) {
-		directions.push( 'after' );
+		positions.push( 'after' );
 	}
 
-	return directions;
+	return positions;
 }
 
 function isFirstChild( widget ) {

+ 69 - 27
packages/ckeditor5-widget/src/widgettypearound/widgettypearound.js

@@ -15,23 +15,30 @@ import Template from '@ckeditor/ckeditor5-ui/src/template';
 
 import {
 	isTypeAroundWidget,
-	getWidgetTypeAroundDirections,
+	getWidgetTypeAroundPositions,
 	getClosestTypeAroundDomButton,
-	getTypeAroundButtonDirection,
-	getClosestWidgetViewElement,
-	directionToWidgetCssClass
+	getTypeAroundButtonPosition,
+	getClosestWidgetViewElement
 } from './utils';
 
 import returnIcon from '../../theme/icons/return-arrow.svg';
 import '../../theme/widgettypearound.css';
 
-const POSSIBLE_INSERTION_DIRECTIONS = [ 'before', 'after' ];
+const POSSIBLE_INSERTION_POSITIONS = [ 'before', 'after' ];
 let CACHED_RETURN_ARROW_ICON;
 
 /**
- * TODO
+ * A plugin which allows users to type around widgets where normally it is impossible to place the caret due
+ * to limitations of web browsers. These "tight spots" occur, for instance, before (or after) a widget being
+ * the first (or last) child of its parent or between two block widgets.
+ *
+ * This plugin extends the {@link module:widget/widget~Widget `Widget`} plugin and inject a user interface
+ * with two buttons into each widget instance in the editor. Each of the buttons can be clicked by the
+ * user if the widget is next to the "tight spot". Once clicked, a paragraph is created with the selection anchored
+ * in it so that users can type (or insert content, paste, etc.) straight away.
  *
  * @extends module:core/plugin~Plugin
+ * @private
  */
 export default class WidgetTypeAround extends Plugin {
 	/**
@@ -49,7 +56,7 @@ export default class WidgetTypeAround extends Plugin {
 	}
 
 	/**
-	 * TODO
+	 * @inheritDoc
 	 */
 	init() {
 		this._enableTypeAroundUIInjection();
@@ -57,12 +64,21 @@ export default class WidgetTypeAround extends Plugin {
 		this._enableInsertingParagraphsOnButtonClick();
 	}
 
-	typeAround( widgetViewElement, direction ) {
+	/**
+	 * Inserts a new paragraph next to a widget element with the selection anchored in it.
+	 *
+	 * **Note**: This method is heavily user-oriented and will both focus the editing view and scroll
+	 * the viewport to the selection in the inserted paragraph.
+	 *
+	 * @param {module:engine/view/element~Element} widgetViewElement The view widget element next to which a paragraph is inserted.
+	 * @param {String} position The position where the paragraph is inserted. Either `'before'` or `'after'` the widget.
+	 */
+	typeAround( widgetViewElement, position ) {
 		const editor = this.editor;
 		const editingView = editor.editing.view;
 		let viewPosition;
 
-		if ( direction === 'before' ) {
+		if ( position === 'before' ) {
 			viewPosition = editingView.createPositionBefore( widgetViewElement );
 		} else {
 			viewPosition = editingView.createPositionAfter( widgetViewElement );
@@ -82,13 +98,19 @@ export default class WidgetTypeAround extends Plugin {
 	}
 
 	/**
-	 * TODO
+	 * Creates a listener in the editing conversion pipeline that injects the type around
+	 * UI into every single widget instance created in the editor.
+	 *
+	 * The UI is delivered as a {@link module:engine/view/uielement~UIElement}
+	 * wrapper which renders DOM buttons that users can use to insert paragraphs.
+	 *
+	 * @private
 	 */
 	_enableTypeAroundUIInjection() {
 		const editor = this.editor;
 		const schema = editor.model.schema;
 		const t = editor.locale.t;
-		const labels = {
+		const buttonTitles = {
 			before: t( 'Insert paragraph before widget' ),
 			after: t( 'Insert paragraph after widget' )
 		};
@@ -98,19 +120,27 @@ export default class WidgetTypeAround extends Plugin {
 
 			// Filter out non-widgets and inline widgets.
 			if ( isTypeAroundWidget( viewElement, data.item, schema ) ) {
-				injectUIIntoWidget( editor.editing.view, labels, viewElement );
+				injectUIIntoWidget( editor.editing.view, buttonTitles, viewElement );
 			}
 		}, { priority: 'low' } );
 	}
 
 	/**
-	 * TODO
+	 * Registers an editing view post-fixer which checks all block widgets in the content
+	 * and adds CSS classes to these which should have the typing around (UI) enabled
+	 * and visible for the users.
+	 *
+	 * @private
 	 */
 	_enableDetectionOfTypeAroundWidgets() {
 		const editor = this.editor;
 		const schema = editor.model.schema;
 		const editingView = editor.editing.view;
 
+		function positionToWidgetCssClass( position ) {
+			return `ck-widget_can-type-around_${ position }`;
+		}
+
 		editingView.document.registerPostFixer( writer => {
 			// Find all view elements in the editing root.
 			[ ...editingView.createRangeIn( editingView.document.getRoot() ) ]
@@ -120,22 +150,26 @@ export default class WidgetTypeAround extends Plugin {
 
 					return isTypeAroundWidget( widgetViewElement, modelElement, schema );
 				} )
-				// ...and update widgets' classes depending on possible directions for paragraph insertion.
+				// ...and update widgets' classes depending on possible positions for paragraph insertion.
 				.forEach( ( { item: widgetViewElement } ) => {
-					const directions = getWidgetTypeAroundDirections( widgetViewElement );
+					const positions = getWidgetTypeAroundPositions( widgetViewElement );
 
 					// Remove all classes. In theory we could remove only these that will not be added a few lines later,
 					// but since there are only two... KISS.
-					writer.removeClass( POSSIBLE_INSERTION_DIRECTIONS.map( directionToWidgetCssClass ), widgetViewElement );
+					writer.removeClass( POSSIBLE_INSERTION_POSITIONS.map( positionToWidgetCssClass ), widgetViewElement );
 
-					// Set CSS classes related to possible directions. They are used so the UI knows which buttons to display.
-					writer.addClass( directions.map( directionToWidgetCssClass ), widgetViewElement );
+					// Set CSS classes related to possible positions. They are used so the UI knows which buttons to display.
+					writer.addClass( positions.map( positionToWidgetCssClass ), widgetViewElement );
 				} );
 		} );
 	}
 
 	/**
-	 * TODO
+	 * Registers a `mousedown` listener for the view document which intercepts events
+	 * coming from the type around UI, which happens when a user clicks one of the buttons
+	 * that insert a paragraph next to a widget.
+	 *
+	 * @private
 	 */
 	_enableInsertingParagraphsOnButtonClick() {
 		const editor = this.editor;
@@ -148,10 +182,10 @@ export default class WidgetTypeAround extends Plugin {
 				return;
 			}
 
-			const buttonDirection = getTypeAroundButtonDirection( button );
+			const buttonPosition = getTypeAroundButtonPosition( button );
 			const widgetViewElement = getClosestWidgetViewElement( button, editingView.domConverter );
 
-			this.typeAround( widgetViewElement, buttonDirection );
+			this.typeAround( widgetViewElement, buttonPosition );
 
 			domEventData.preventDefault();
 			evt.stop();
@@ -159,14 +193,19 @@ export default class WidgetTypeAround extends Plugin {
 	}
 }
 
-function injectUIIntoWidget( editingView, labels, widgetViewElement ) {
+// Injects the type around UI into a view widget instance.
+//
+// @param {module:engine/view/element~Element}
+// @param {module:engine/view/view} editingView
+// @param {Object.<String,String>} buttonTitles
+function injectUIIntoWidget( editingView, buttonTitles, widgetViewElement ) {
 	editingView.change( writer => {
 		const typeAroundWrapper = writer.createUIElement( 'div', {
 			class: 'ck ck-reset_all ck-widget__type-around'
 		}, function( domDocument ) {
 			const wrapperDomElement = this.toDomElement( domDocument );
 
-			injectButtons( wrapperDomElement, labels );
+			injectButtons( wrapperDomElement, buttonTitles );
 
 			return wrapperDomElement;
 		} );
@@ -179,23 +218,26 @@ function injectUIIntoWidget( editingView, labels, widgetViewElement ) {
 // FYI: Not using the IconView class because each instance would need to be destroyed to avoid memory leaks
 // and it's pretty hard to figure out when a view (widget) is gone for good so it's cheaper to use raw
 // <svg> here.
-function injectButtons( wrapperDomElement, labels ) {
+//
+// @param {HTMLElement} wrapperDomElement
+// @param {Object.<String,String>} buttonTitles
+function injectButtons( wrapperDomElement, buttonTitles ) {
 	// Do the SVG parsing once and then clone the result <svg> DOM element for each new
 	// button. There could be dozens of them during editor's lifetime.
 	if ( !CACHED_RETURN_ARROW_ICON ) {
 		CACHED_RETURN_ARROW_ICON = new DOMParser().parseFromString( returnIcon, 'image/svg+xml' ).firstChild;
 	}
 
-	for ( const direction of POSSIBLE_INSERTION_DIRECTIONS ) {
+	for ( const position of POSSIBLE_INSERTION_POSITIONS ) {
 		const buttonTemplate = new Template( {
 			tag: 'div',
 			attributes: {
 				class: [
 					'ck',
 					'ck-widget__type-around__button',
-					`ck-widget__type-around__button_${ direction }`
+					`ck-widget__type-around__button_${ position }`
 				],
-				title: labels[ direction ]
+				title: buttonTitles[ position ]
 			},
 			children: [
 				wrapperDomElement.ownerDocument.importNode( CACHED_RETURN_ARROW_ICON, true )

+ 2 - 2
packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js

@@ -54,7 +54,7 @@ describe( 'WidgetTypeAround', () => {
 	} );
 
 	describe( 'typeAround()', () => {
-		it( 'should insert a paragraph with a selection before a widget when direction is "before"', () => {
+		it( 'should insert a paragraph with a selection before a widget when position is "before"', () => {
 			setModelData( editor.model, '<blockWidget></blockWidget>' );
 
 			plugin.typeAround( viewRoot.getChild( 0 ), 'before' );
@@ -62,7 +62,7 @@ describe( 'WidgetTypeAround', () => {
 			expect( getModelData( editor.model ) ).to.equal( '<paragraph>[]</paragraph><blockWidget></blockWidget>' );
 		} );
 
-		it( 'should insert a paragraph with a selection after a widget when direction is "after"', () => {
+		it( 'should insert a paragraph with a selection after a widget when position is "after"', () => {
 			setModelData( editor.model, '<blockWidget></blockWidget>' );
 
 			plugin.typeAround( viewRoot.getChild( 0 ), 'after' );