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

Initial implementation of VirtualSelectionStack class.

Szymon Kupś 8 лет назад
Родитель
Сommit
3402c85cd7

+ 22 - 1
packages/ckeditor5-widget/src/utils.js

@@ -7,6 +7,8 @@
  * @module widget/utils
  */
 
+import VirtualSelectionStack from './virtualselectionstack';
+
 const widgetSymbol = Symbol( 'isWidget' );
 const labelSymbol = Symbol( 'label' );
 
@@ -39,7 +41,9 @@ export function isWidget( element ) {
  * * sets `contenteditable` attribute to `true`,
  * * adds custom `getFillerOffset` method returning `null`,
  * * adds `ck-widget` CSS class,
- * * adds custom property allowing to recognize widget elements by using {@link ~isWidget}.
+ * * adds custom property allowing to recognize widget elements by using {@link ~isWidget},
+ * * implements `setVirtualSelection` and `removeVirtualSelection` custom properties to handle virtual selection
+ * on widgets.
  *
  * @param {module:engine/view/element~Element} element
  * @param {Object} [options={}]
@@ -57,6 +61,23 @@ export function toWidget( element, options = {} ) {
 		setLabel( element, options.label );
 	}
 
+	if ( options.setVirtualSelection && options.removeVirtualSelection ) {
+		const stack = new VirtualSelectionStack();
+
+		stack.on( 'change:top', ( evt, data ) => {
+			if ( data.oldDescriptor ) {
+				options.removeVirtualSelection( data.oldDescriptor );
+			}
+
+			if ( data.newDescriptor ) {
+				options.setVirtualSelection( data.newDescriptor );
+			}
+		} );
+
+		element.setCustomProperty( 'setVirtualSelection', descriptor => stack.add( descriptor ) );
+		element.setCustomProperty( 'removeVirtualSelection', descriptor => stack.remove( descriptor ) );
+	}
+
 	return element;
 }
 

+ 111 - 0
packages/ckeditor5-widget/src/virtualselectionstack.js

@@ -0,0 +1,111 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module widget/virtualselectionstack
+ */
+
+import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
+
+export default class VirtualSelectionStack {
+	constructor() {
+		this._stack = [];
+	}
+
+	add( descriptor ) {
+		const stack = this._stack;
+		let i = 0;
+
+		// Find correct place to insert descriptor in the stack.
+		while ( stack[ i ] && shouldABeBeforeB( stack[ i ], descriptor ) ) {
+			i++;
+		}
+
+		stack.splice( i, 0, descriptor );
+
+		// New element at the stack top.
+		if ( i === 0 ) {
+			const data = {
+				newDescriptor: descriptor
+			};
+
+			// If old descriptor is present it was pushed down the stack.
+			if ( stack[ 1 ] ) {
+				const oldDescriptor = stack[ 1 ];
+
+				// New descriptor on the top is same as previous one - do not fire any event.
+				if ( compareDescriptors( descriptor, oldDescriptor ) ) {
+					return;
+				}
+
+				data.oldDescriptor = oldDescriptor;
+			}
+
+			this.fire( 'change:top', data );
+		}
+	}
+
+	remove( descriptor ) {
+		const stack = this._stack;
+		const length = stack.length;
+
+		if ( length === 0 ) {
+			return;
+		}
+
+		let i = 0;
+
+		while ( stack[ i ] && !compareDescriptors( descriptor, stack[ i ] ) ) {
+			i++;
+
+			// Descriptor not found.
+			if ( i > stack.length ) {
+				return;
+			}
+		}
+
+		stack.splice( i, 1 );
+
+		// Element from stack top was removed - fire `change:top` event with new first element. It might be `undefined`
+		// which informs that no selection is currently at the top.
+		if ( i === 0 ) {
+			const data = {
+				oldDescriptor: descriptor
+			};
+
+			if ( stack[ 0 ] ) {
+				const newDescriptor = stack[ 0 ];
+
+				// New descriptor on the top is same as removed one - do not fire any event.
+				if ( compareDescriptors( descriptor, newDescriptor ) ) {
+					return;
+				}
+
+				data.newDescriptor = newDescriptor;
+			}
+
+			this.fire( 'change:top', data );
+		}
+	}
+}
+
+mix( VirtualSelectionStack, EmitterMixin );
+
+function compareDescriptors( descriptorA, descriptorB ) {
+	return descriptorA.priority == descriptorB.priority && descriptorA.class == descriptorB.class;
+}
+
+function shouldABeBeforeB( a, b ) {
+	if ( a.priority > b.priority ) {
+		return true;
+	} else if ( a.priority < b.priority ) {
+		return false;
+	}
+
+	// When priorities are equal and names are different - use classes to compare.
+	// TODO: class should be required.
+	return a.class < b.class;
+}