Przeglądaj źródła

Refactoring image captioning code.

Szymon Kupś 9 lat temu
rodzic
commit
63fa14eed0

+ 54 - 0
packages/ckeditor5-image/src/imagecaptioning/converters.js

@@ -0,0 +1,54 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module image/imagecaptioning/converters
+ */
+
+import { isImage } from '../utils';
+import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
+import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element';
+import viewWriter from '@ckeditor/ckeditor5-engine/src/view/writer';
+
+/**
+ * Creates a converter that converts image caption model element to view element.
+ *
+ * @param {Function|module:engine/view/element~Element} elementCreator
+ * @return {Function}
+ */
+export function captionModelToView( elementCreator ) {
+	const insertConverter = insertElementAtEnd( elementCreator );
+
+	return ( evt, data, consumable, conversionApi ) => {
+		const captionElement = data.item;
+
+		if ( isImage( captionElement.parent ) && ( captionElement.childCount > 0 ) ) {
+			insertConverter( evt, data, consumable, conversionApi );
+		}
+	};
+}
+
+// Returns converter that converts inserting operation to view element that is inserted at the end of its parent.
+//
+// @private
+// @param {Function|module:engine/view/element~Element} elementCreator
+// @return {Function}
+function insertElementAtEnd( elementCreator ) {
+	return ( evt, data, consumable, conversionApi ) => {
+		if ( !consumable.consume( data.item, 'insert' ) ) {
+			return;
+		}
+
+		const imageFigure = conversionApi.mapper.toViewElement( data.range.start.parent );
+		const viewPosition = ViewPosition.createAt( imageFigure, 'end' );
+
+		const viewElement = ( elementCreator instanceof ViewElement ) ?
+			elementCreator.clone( true ) :
+			elementCreator( data, consumable, conversionApi );
+
+		conversionApi.mapper.bindElements( data.item, viewElement );
+		viewWriter.insert( viewPosition, viewElement );
+	};
+}

+ 0 - 7
packages/ckeditor5-image/src/imagecaptioning/imagecaptioning.js

@@ -18,11 +18,4 @@ export default class ImageCaptioning extends Plugin {
 	static get requires() {
 		return [ ImageCaptioningEngine ];
 	}
-
-	/**
-	 * @inheritDoc
-	 */
-	init() {
-
-	}
 }

+ 53 - 99
packages/ckeditor5-image/src/imagecaptioning/imagecaptioningengine.js

@@ -10,9 +10,7 @@
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import ModelTreeWalker from '@ckeditor/ckeditor5-engine/src/model/treewalker';
 import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
-import ViewEditableElement from '@ckeditor/ckeditor5-engine/src/view/editableelement';
 import ViewContainerElement from '@ckeditor/ckeditor5-engine/src/view/containerelement';
-import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element';
 import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
 import ViewRange from '@ckeditor/ckeditor5-engine/src/view/range';
 import viewWriter from '@ckeditor/ckeditor5-engine/src/view/writer';
@@ -20,6 +18,8 @@ import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
 import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter';
 import ViewMatcher from '@ckeditor/ckeditor5-engine/src/view/matcher';
 import { isImage, isImageWidget } from '../utils';
+import { captionModelToView } from './converters';
+import { captionEditableCreator, isCaptionEditable, getCaptionFromImage } from './utils';
 
 export default class ImageCaptioningEngine extends Plugin {
 	/**
@@ -65,7 +65,7 @@ export default class ImageCaptioningEngine extends Plugin {
 		);
 
 		// Model to view converter for editing pipeline.
-		const editableCreator = editingViewEditableCreator( viewDocument );
+		const editableCreator = captionEditableCreator( viewDocument );
 		editing.modelToView.on(
 			'insert:caption',
 			captionModelToView( editableCreator )
@@ -74,57 +74,41 @@ export default class ImageCaptioningEngine extends Plugin {
 		// Adding / removing caption element when there is no text in the model.
 		const selection = viewDocument.selection;
 
-		this.lastCaptionAdded = undefined;
+		this.lastCaptionUsed = undefined;
 
 		// Update view before each rendering.
 		this.listenTo( viewDocument, 'render', () => {
-			const imageFigure = selection.getSelectedElement();
-			const mapper = editing.mapper;
-
+			// Check if there is an empty caption view element to remove.
 			this._removeEmptyCaption();
 
-			// Clicking on image figure without figcaption.
-			if ( imageFigure && isImageWidget( imageFigure ) ) {
-				const modelImage = mapper.toModelElement( imageFigure );
-				const modelCaption = getCaption( modelImage );
-				let viewCaption =  mapper.toViewElement( modelCaption );
-
-				if ( !viewCaption ) {
-					// TODO: this is same code as in insertElementAtEnd - refactor.
-					const viewPosition = ViewPosition.createAt( imageFigure, 'end' );
-					viewCaption = editableCreator();
-
-					mapper.bindElements( modelCaption, viewCaption );
-					viewWriter.insert( viewPosition, viewCaption );
-				}
+			// Check if image widget is selected and caption view element needs to be added.
+			this._addCaption();
 
-				this.lastCaptionAdded = viewCaption;
-
-				return;
-			}
-
-			// Inside figcaption.
+			// If selection is currently inside caption - store it to hide when empty.
 			const editableElement = selection.editableElement;
 
-			if ( editableElement && isCaptionViewElement( selection.editableElement ) ) {
-				this.lastCaptionAdded = selection.editableElement;
-
-				return;
+			if ( editableElement && isCaptionEditable( selection.editableElement ) ) {
+				this.lastCaptionUsed = selection.editableElement;
 			}
 		}, { priority: 'high' } );
 	}
 
+	/**
+	 * Checks if there is an empty caption element to remove from view.
+	 *
+	 * @private
+	 */
 	_removeEmptyCaption() {
 		const viewSelection = this.editor.editing.view.selection;
-		const viewCaption = this.lastCaptionAdded;
+		const viewCaptionElement = this.lastCaptionUsed;
 
 		// No caption to hide.
-		if ( !viewCaption ) {
+		if ( !viewCaptionElement ) {
 			return;
 		}
 
 		// If selection is placed inside caption - do not remove it.
-		if ( viewSelection.editableElement === viewCaption ) {
+		if ( viewSelection.editableElement === viewCaptionElement ) {
 			return;
 		}
 
@@ -132,7 +116,7 @@ export default class ImageCaptioningEngine extends Plugin {
 		const selectedElement = viewSelection.getSelectedElement();
 
 		if ( selectedElement && isImageWidget( selectedElement ) ) {
-			const viewImage = viewCaption.findAncestor( element => element == selectedElement );
+			const viewImage = viewCaptionElement.findAncestor( element => element == selectedElement );
 
 			if ( viewImage ) {
 				return;
@@ -140,50 +124,48 @@ export default class ImageCaptioningEngine extends Plugin {
 		}
 
 		// Remove image caption if its empty.
-		if ( viewCaption.childCount === 0 ) {
+		if ( viewCaptionElement.childCount === 0 ) {
 			const mapper = this.editor.editing.mapper;
-			viewWriter.remove( ViewRange.createOn( viewCaption ) );
-			mapper.unbindViewElement( viewCaption );
+			viewWriter.remove( ViewRange.createOn( viewCaptionElement ) );
+			mapper.unbindViewElement( viewCaptionElement );
 		}
 	}
-}
 
-const captionSymbol = Symbol( 'imageCaption' );
-
-function isCaptionViewElement( viewElement ) {
-	return !!viewElement.getCustomProperty( captionSymbol );
-}
-
-function editingViewEditableCreator( viewDocument ) {
-	return () => {
-		const editable = new ViewEditableElement( 'figcaption', { contenteditable: true } ) ;
-		editable.document = viewDocument;
-		editable.setCustomProperty( captionSymbol, true );
-
-		editable.on( 'change:isFocused', ( evt, property, is ) => {
-			if ( is ) {
-				editable.addClass( 'focused' );
-			} else {
-				editable.removeClass( 'focused' );
+	/**
+	 * Checks if selected image needs a new caption element inside.
+	 *
+	 * @private
+	 */
+	_addCaption() {
+		const editing = this.editor.editing;
+		const selection = editing.view.selection;
+		const imageFigure = selection.getSelectedElement();
+		const mapper = editing.mapper;
+		const editableCreator = captionEditableCreator( editing.view );
+
+		if ( imageFigure && isImageWidget( imageFigure ) ) {
+			const modelImage = mapper.toModelElement( imageFigure );
+			const modelCaption = getCaptionFromImage( modelImage );
+			let viewCaption =  mapper.toViewElement( modelCaption );
+
+			if ( !viewCaption ) {
+				// TODO: this is same code as in insertElementAtEnd - refactor.
+				const viewPosition = ViewPosition.createAt( imageFigure, 'end' );
+				viewCaption = editableCreator();
+
+				mapper.bindElements( modelCaption, viewCaption );
+				viewWriter.insert( viewPosition, viewCaption );
 			}
-		} );
 
-		return editable;
-	};
-}
-
-function captionModelToView( element ) {
-	const insertConverter = insertElementAtEnd( element );
-
-	return ( evt, data, consumable, conversionApi ) => {
-		const captionElement = data.item;
-
-		if ( isImage( captionElement.parent ) && ( captionElement.childCount > 0 ) ) {
-			insertConverter( evt, data, consumable, conversionApi );
+			this.lastCaptionUsed = viewCaption;
 		}
-	};
+	}
 }
 
+// Checks whether data inserted to the model document have image element that has no caption element inside it.
+// If there is none - adds it to the image element.
+//
+// @private
 function insertCaptionElement( evt, changeType, data, batch ) {
 	if ( changeType !== 'insert' ) {
 		return;
@@ -197,7 +179,7 @@ function insertCaptionElement( evt, changeType, data, batch ) {
 	for ( let value of walker ) {
 		const item = value.item;
 
-		if ( value.type == 'elementStart' && isImage( item ) && !getCaption( item ) ) {
+		if ( value.type == 'elementStart' && isImage( item ) && !getCaptionFromImage( item ) ) {
 			// Using batch of insertion.
 			batch.document.enqueueChanges( () => {
 				batch.insert( ModelPosition.createAt( item, 'end' ), new ModelElement( 'caption' ) );
@@ -205,31 +187,3 @@ function insertCaptionElement( evt, changeType, data, batch ) {
 		}
 	}
 }
-
-function getCaption( image ) {
-	for ( let node of image.getChildren() ) {
-		if ( node instanceof ModelElement && node.name == 'caption' ) {
-			return node;
-		}
-	}
-
-	return null;
-}
-
-function insertElementAtEnd( elementCreator ) {
-	return ( evt, data, consumable, conversionApi ) => {
-		if ( !consumable.consume( data.item, 'insert' ) ) {
-			return;
-		}
-
-		const imageFigure = conversionApi.mapper.toViewElement( data.range.start.parent );
-		const viewPosition = ViewPosition.createAt( imageFigure, 'end' );
-
-		const viewElement = ( elementCreator instanceof ViewElement ) ?
-			elementCreator.clone( true ) :
-			elementCreator( data, consumable, conversionApi );
-
-		conversionApi.mapper.bindElements( data.item, viewElement );
-		viewWriter.insert( viewPosition, viewElement );
-	};
-}

+ 63 - 0
packages/ckeditor5-image/src/imagecaptioning/utils.js

@@ -0,0 +1,63 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module image/imagecaptioning/utils
+ */
+
+import ViewEditableElement from '@ckeditor/ckeditor5-engine/src/view/editableelement';
+import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
+
+const captionSymbol = Symbol( 'imageCaption' );
+
+/**
+ * Returns function that creates caption editable element for given {@link module:engine/view/document~Document}.
+ *
+ * @param {module:engine/view/document~Document} viewDocument
+ * @return {Function}
+ */
+export function captionEditableCreator( viewDocument ) {
+	return () => {
+		const editable = new ViewEditableElement( 'figcaption', { contenteditable: true } ) ;
+		editable.document = viewDocument;
+		editable.setCustomProperty( captionSymbol, true );
+
+		editable.on( 'change:isFocused', ( evt, property, is ) => {
+			if ( is ) {
+				editable.addClass( 'focused' );
+			} else {
+				editable.removeClass( 'focused' );
+			}
+		} );
+
+		return editable;
+	};
+}
+
+/**
+ * Returns `true` if given view element is image's caption editable.
+ *
+ * @param {module:engine/view/element~Element} viewElement
+ * @return {Boolean}
+ */
+export function isCaptionEditable( viewElement ) {
+	return !!viewElement.getCustomProperty( captionSymbol );
+}
+
+/**
+ * Returns caption's model element from given image element. Returns `null` if no caption is found.
+ *
+ * @param {module:engine/model/element~Element} imageModelElement
+ * @return {module:engine/model/element~Element|null}
+ */
+export function getCaptionFromImage( imageModelElement ) {
+	for ( let node of imageModelElement.getChildren() ) {
+		if ( node instanceof ModelElement && node.name == 'caption' ) {
+			return node;
+		}
+	}
+
+	return null;
+}