Piotr Jasiun пре 9 година
родитељ
комит
19ccfd460d

+ 157 - 0
packages/ckeditor5-engine/src/treecontroller/model-to-view-converters.js

@@ -0,0 +1,157 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+export function insert( nodeCreator ) {
+	return ( evt, model, context ) => {
+		model.consumable.consume( model.item, 'insert' );
+
+		const viewPosition = context.mapper.toViewPosition( model.range.start );
+
+		const viewNode = ( nodeCreator instanceof viewNode ) ? nodeCreator : nodeCreator( model, context );
+
+		context.mapper.bind( model.item, viewNode );
+
+		context.writer.insert( viewPosition, viewNode );
+
+		evt.stop();
+	}
+}
+
+export function insertText() {
+	return ( evt, model, context ) => {
+		model.consumable.consume( model.item, 'insert' );
+
+		const viewPosition = context.mapper.toViewPosition( model.position );
+		const viewNode = new ViewText( model.item.data );
+
+		context.writer.insert( viewPosition, viewNode );
+
+		evt.stop();
+	}
+}
+
+export function setAttribute( attributesCreator ) {
+	return ( evt, model, context ) => {
+		let attributes;
+		if ( !attributesCreator ) {
+			attributes = model.item.getAttribute( model.attributeKey )
+		} else {
+			attributes = attributesCreator( model )
+		}
+
+		if ( attributes ) {
+			model.consumable.consume( model.item, eventNameToConsumableType( evt.name ) );
+
+			const viewElement = context.mapper.toViewElement( model.item );
+
+			if ( attributes typeof 'string' || attributes typeof 'number' ) {
+				viewElement.setAttribute( model.attributeKey, attributes );
+			} else {
+				for ( var attributeKey in attributes ) {
+					viewElement.setAttribute( attributeKey, attributes[ attributeKey ] );
+				}
+			}
+
+			evt.stop();
+		}
+	}
+}
+
+export function removeAttribute( attributesCreator ) {
+	return ( evt, model, context ) => {
+		let attributeKeys;
+		if ( !attributesCreator ) {
+			attributeKeys = model.attributeKey;
+		} else {
+			attributeKeys = attributesCreator( model )
+		}
+
+		if ( attributeKeys ) {
+			model.consumable.consume( model.item, eventNameToConsumableType( evt.name ) );
+
+			const viewElement = context.mapper.toViewElement( model.item );
+
+			if ( attributeKeys typeof 'string' ) {
+				viewElement.removeAttribute( attributeKeys );
+			} else {
+				for ( var attributeKey of attributeKeys ) {
+					viewElement.removeAttribute( attributeKey );
+				}
+			}
+
+			evt.stop();
+		}
+	}
+}
+
+export function wrap( nodeCreator ) {
+	return ( evt, model, context ) => {
+		model.consumable.consume( model.item, eventNameToConsumableType( evt.name ) );
+
+		const viewRange = context.mapper.toViewRange( model.range );
+
+		const viewNode = ( nodeCreator instanceof viewNode ) ? nodeCreator : nodeCreator( model );
+
+		context.writer.wrap( viewRange, viewNode );
+
+		evt.stop();
+	}
+}
+
+export function unwrap() {
+	return ( evt, model, context ) => {
+		model.consumable.consume( model.item, eventNameToConsumableType( evt.name ) );
+
+		const viewRange = context.mapper.toViewRange( model.range );
+
+		const viewNode = ( nodeCreator instanceof viewNode ) ? nodeCreator : nodeCreator( model );
+
+		context.writer.unwrap( viewRange, viewNode );
+
+		evt.stop();
+	}
+}
+
+export function move() {
+	return ( evt, model, context ) => {
+		const length = 0;
+		const walker = new TreeWalker( { boundaries: range, shallow: true } );
+
+		for ( let value of walker ) {
+			length += value.length;
+		}
+
+		const sourceModelRange = Range.createFromPositionAndShift( sourcePosition, length );
+
+		sourceViewRange = context.mapper.toViewRange( sourceModelRange );
+		targetViewPosition = context.mapper.toViewRange( range.start );
+
+		context.writer.move( sourceViewRange, targetViewPosition );
+	}
+}
+
+export function remove() {
+	return ( evt, model, context ) => {
+		const length = 0;
+		const walker = new TreeWalker( { boundaries: range, shallow: true } );
+
+		for ( let value of walker ) {
+			length += value.length;
+		}
+
+		const sourceModelRange = Range.createFromPositionAndShift( sourcePosition, length );
+
+		sourceViewRange = context.mapper.toViewRange( sourceModelRange );
+
+		context.writer.remove( sourceViewRange );
+	}
+}
+
+function eventNameToConsumableType( evtName ) {
+	const parts = evtName.split( ':' );
+	return parts[ 0 ] + ':' + parts[ 1 ];
+}

+ 60 - 0
packages/ckeditor5-engine/src/treecontroller/modelconsumable.js

@@ -0,0 +1,60 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+export default class ModelConsumable {
+	constructor() {
+		this.consumable = new Map();
+	}
+
+	add( item, type ) {
+		if ( !this.consumable.has( item ) ) {
+			this.consumable.set( item, new Map() );
+		}
+
+		this.consumable.get( item ).set( type, true );
+	}
+
+	consume( item, type ) {
+		if ( this.test( item, type ) ) {
+			this.consumable.get( item ).set( type, false );
+
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	test( item, type ) {
+		const itemConsumables = this.consumable.get( item );
+
+		if ( value === undefined ) {
+			return null;
+		}
+
+		const value = itemConsumables.get( type, true );
+
+		if ( value === undefined ) {
+			return null;
+		}
+
+		return value;
+	}
+
+	revert( item, type ) {
+		const test = this.test( item, type );
+
+		if ( test === false ) {
+			this.consumable.get( item ).set( type, true );
+
+			return true;
+		} else if ( test === true ) {
+			return false;
+		}
+
+		return null;
+	}
+}

+ 130 - 0
packages/ckeditor5-engine/src/treecontroller/modelconversiondispatcher.js

@@ -0,0 +1,130 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Consumeable from './modelconsumeable.js';
+
+export default class ModelConversionDispatcher {
+	constructor( context ) {
+		this.context = context;
+	}
+
+	convertChange( type, changeInfo ) {
+		if ( type == 'insert' || type == 'reinsert' ) {
+			this.convertInsert( changeInfo.range );
+		} else if ( type == 'move' ) {
+			this.convertMove( changeInfo.range, changeInfo.sourcePosition );
+		} else if ( type == 'remove' ) {
+			this.convertRemove( changeInfo.range, changeInfo.sourcePosition );
+		} else if ( type == 'addAttribute' || type == 'removeAttribute' || type == 'changeAttribute' ) {
+			this.convertAttribute( type, changeInfo.range, changeInfo.key, changeInfo.oldValue, changeInfo.newValue );
+		}
+	}
+
+	convertInsert( range ) {
+		const consumable = new Consumeable();
+		const values = [];
+
+		for ( let value of range ) {
+			values.push( value );
+
+			const item = value.item;
+
+			consumable.add( item, 'insert' );
+
+			for ( let key of item.getAttributes() ) {
+				consumable.add( item, 'addAttribute:' + key );
+			}
+		}
+
+		for ( let value of values ) {
+			const item = value.item;
+			const range = Range.createFromPositionAndShift( value.previousPosition, value.length );
+			const model = {
+				item,
+				range,
+				consumable
+			};
+
+			this._testAndFire( 'insert', model, context );
+
+			for ( let key of item.getAttributes() ) {
+				model.attributeKey = key;
+
+				this._testAndFire( 'addAttribute:' + key, model, this.context );
+			}
+		}
+	}
+
+	convertMove( range, sourcePosition ) {
+		const model = {
+			range: range,
+			sourcePosition: sourcePosition,
+		};
+
+		this.fire( 'move', model, this.context );
+	}
+
+	convertRemove( range, sourcePosition ) {
+		const model = {
+			range: range,
+			sourcePosition: sourcePosition,
+		};
+
+		this.fire( 'remove', model, this.context );
+	}
+
+	convertAttribute( type, range, key, oldValue, newValue ) {
+		const consumable = new Consumeable();
+		const values = [];
+
+		for ( let value of range ) {
+			values.push( value );
+
+			const item = value.item;
+
+			for ( let key of item.getAttributes() ) {
+				consumable.add( item, type + ':' + key );
+			}
+		}
+
+		const writer = this.writer;
+		const mapper = this.mapper;
+
+		for ( let value of values ) {
+			const item = value.item;
+			const range = Range.createFromPositionAndShift( value.previousPosition, value.length );
+			const model = {
+				item: item,
+				range: range,
+				attributeKey: key,
+				consumable: consumable
+			};
+
+			this._testAndFire( type + ':' + key, model, this.context );
+		}
+	}
+
+	_testAndFire( type, model, context ) {
+		if ( !model.consumable.test( model.item, type ) ) {
+			// Do not fire event if the item was consumed.
+			return;
+		}
+
+		if ( type === 'insert' ) {
+			if ( model.item instanceof TextFragment ) {
+				// e.g. insert:text
+				this.fire( type + ':text', model, context );
+			} else {
+				// e.g. insert:element:p
+				this.fire( type + ':element:' + model.item.name, model, context );
+			}
+		} else {
+			// e.g. addAttribute:alt:img
+			this.fire( type + ':' + model.item.name, model, context );
+		}
+	}
+}