Переглянути джерело

Introduce mapModelPositionOnInlineElement() default model-to-view position mapper.

Maciej Gołaszewski 7 роки тому
батько
коміт
d3dc25b4c4

+ 3 - 2
packages/ckeditor5-engine/src/controller/editingcontroller.js

@@ -16,7 +16,7 @@ import { clearAttributes, convertCollapsedSelection, convertRangeSelection, inse
 import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import { convertSelectionChange } from '../conversion/upcasthelpers';
-import { mapViewPositionInsideInlineElement } from '../conversion/mapper-helpers';
+import { mapModelPositionOnInlineElement, mapViewPositionInsideInlineElement } from '../conversion/mapper-helpers';
 
 /**
  * Controller for the editing pipeline. The editing pipeline controls {@link ~EditingController#model model} rendering,
@@ -124,8 +124,9 @@ export default class EditingController {
 			return viewRoot;
 		} );
 
-		// Attach view-to-model position mapper for inline elements (inline widget).
+		// Attach view-to-model & model-to-view position mapper for inline elements (inline widget).
 		this.mapper.on( 'viewToModelPosition', mapViewPositionInsideInlineElement( this.model ) );
+		this.mapper.on( 'modelToViewPosition', mapModelPositionOnInlineElement( this.model, this.view ) );
 	}
 
 	/**

+ 51 - 2
packages/ckeditor5-engine/src/conversion/mapper-helpers.js

@@ -8,11 +8,10 @@
  */
 
 /**
- * Maps view position to model element if view positions is inside inLine element - ie. inline widget.
+ * Maps view position to model position if view positions is wrongly set inside inline element - ie. inline widget.
  *
  *		editor.editing.mapper.on( 'viewToModel', mapViewPositionInsideInlineElement( model ) );
  *
- *
  * It will try to map view offset of selection to an expected model position in order to create consistent selection.
  *
  * If DOM selection starts before inline element then view position offset will be 0 and be considered at it start and thus model position
@@ -62,3 +61,53 @@ export function mapViewPositionInsideInlineElement( model ) {
 		}
 	};
 }
+
+/**
+ * Maps model position to view position if the position set after/before inline element (ie. inline widget).
+ *
+ *		editor.editing.mapper.on( 'modelToView', mapModelPositionOnInlineElement( model, view ) );
+ *
+ * This methods ensures that selection set on inline element is set outside surrounding text nodes as by default the position would be set
+ * at the end or begging of a text node that is previous/next to the inline element:
+ *
+ * Without this mapper helper the selection would set inside text nodes:
+ *
+ *		//             +-------------------------------+--- model positions
+ *		//             \                               |
+ *		<paragraph>foo [<inline-widget></inline-widget>]</paragraph>
+ *
+ *		// will map to:
+ *		//     +---------------------+- view positions (inside text nodes)
+ *		//     |                     |
+ *		<p>foo {<span class="widget">}widget</span></p>
+ *
+ * With this mapper helper the selection will set outside text nodes:
+ *
+ *		//     +---------------------+--- view positions (outside text nodes)
+ *		//     |                     |
+ *		<p>foo [<span class="widget">]widget</span></p>
+ *
+ * @param {module:engine/model/model~Model} model
+ * @param {module:engine/model/model~Model} view
+ * @return {Function}
+ */
+export function mapModelPositionOnInlineElement( model, view ) {
+	return ( evt, data ) => {
+		if ( data.isPhantom ) {
+			return;
+		}
+
+		const isBeforeNode = data.modelPosition.stickiness === 'toNext';
+		const node = isBeforeNode ? data.modelPosition.nodeAfter : data.modelPosition.nodeBefore;
+
+		if ( node && model.schema.isInline( node ) && !node.is( 'text' ) ) {
+			const viewElement = data.mapper.toViewElement( node );
+
+			if ( !viewElement ) {
+				return;
+			}
+
+			data.viewPosition = isBeforeNode ? view.createPositionBefore( viewElement ) : view.createPositionAfter( viewElement );
+		}
+	};
+}