Browse Source

Extract restricted editing mode converters and utils.

Maciej Gołaszewski 6 years ago
parent
commit
2bf2633b9e

+ 152 - 0
packages/ckeditor5-restricted-editing/src/restrictededitingmode/converters.js

@@ -0,0 +1,152 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module restricted-editing/restrictededitingmode/converters
+ */
+
+import Matcher from '@ckeditor/ckeditor5-engine/src/view/matcher';
+import { getMarker } from './utils';
+
+const HIGHLIGHT_CLASS = 'ck-restricted-editing-exception_selected';
+
+/**
+ *
+ * @param editor
+ */
+export function setupMarkersConversion( editor ) {
+	// The restricted editing does not attach additional data to the zones so there's no need for smarter markers management.
+	// Also, the markers will only be created when  when loading the data.
+	let markerNumber = 0;
+
+	editor.conversion.for( 'upcast' ).add( upcastHighlightToMarker( {
+		view: {
+			name: 'span',
+			classes: 'ck-restricted-editing-exception'
+		},
+		model: () => {
+			markerNumber++; // Starting from restricted-editing-exception:1 marker.
+
+			return `restricted-editing-exception:${ markerNumber }`;
+		}
+	} ) );
+
+	// Currently the marker helpers are tied to other use-cases and do not render collapsed marker as highlight.
+	// That's why there are 2 downcast converters for them:
+	// 1. The default marker-to-highlight will wrap selected text with `<span>`.
+	editor.conversion.for( 'downcast' ).markerToHighlight( {
+		model: 'restricted-editing-exception',
+		// Use callback to return new object every time new marker instance is created - otherwise it will be seen as the same marker.
+		view: () => ( {
+			name: 'span',
+			classes: 'ck-restricted-editing-exception',
+			priority: -10
+		} )
+	} );
+
+	// 2. But for collapsed marker we need to render it as an element.
+	// Additionally the editing pipeline should always display a collapsed markers.
+	editor.conversion.for( 'editingDowncast' ).markerToElement( {
+		model: 'restricted-editing-exception',
+		view: ( markerData, viewWriter ) => viewWriter.createUIElement( 'span', {
+			class: 'ck-restricted-editing-exception ck-restricted-editing-exception_collapsed'
+		} )
+	} );
+
+	editor.conversion.for( 'dataDowncast' ).markerToElement( {
+		model: 'restricted-editing-exception',
+		view: ( markerData, viewWriter ) => viewWriter.createEmptyElement( 'span', {
+			class: 'ck-restricted-editing-exception'
+		} )
+	} );
+}
+
+/**
+ * Adds a visual highlight style to a restricted editing exception the selection is anchored to.
+ *
+ * Highlight is turned on by adding the `.ck-restricted-editing-exception_selected` class to the
+ * exception in the view:
+ *
+ * * The class is removed before the conversion has started, as callbacks added with the `'highest'` priority
+ * to {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} events.
+ * * The class is added in the view post fixer, after other changes in the model tree were converted to the view.
+ *
+ * This way, adding and removing the highlight does not interfere with conversion.
+ */
+export function setupExceptionHighlighting( editor ) {
+	const view = editor.editing.view;
+	const model = editor.model;
+	const highlightedMarkers = new Set();
+
+	// Adding the class.
+	view.document.registerPostFixer( writer => {
+		const modelSelection = model.document.selection;
+
+		const marker = getMarker( editor, modelSelection.anchor );
+
+		if ( !marker ) {
+			return;
+		}
+
+		for ( const viewElement of editor.editing.mapper.markerNameToElements( marker.name ) ) {
+			writer.addClass( HIGHLIGHT_CLASS, viewElement );
+			highlightedMarkers.add( viewElement );
+		}
+	} );
+
+	// Removing the class.
+	editor.conversion.for( 'editingDowncast' ).add( dispatcher => {
+		// Make sure the highlight is removed on every possible event, before conversion is started.
+		dispatcher.on( 'insert', removeHighlight, { priority: 'highest' } );
+		dispatcher.on( 'remove', removeHighlight, { priority: 'highest' } );
+		dispatcher.on( 'attribute', removeHighlight, { priority: 'highest' } );
+		dispatcher.on( 'selection', removeHighlight, { priority: 'highest' } );
+
+		function removeHighlight() {
+			view.change( writer => {
+				for ( const item of highlightedMarkers.values() ) {
+					writer.removeClass( HIGHLIGHT_CLASS, item );
+					highlightedMarkers.delete( item );
+				}
+			} );
+		}
+	} );
+}
+
+function upcastHighlightToMarker( config ) {
+	return dispatcher => dispatcher.on( 'element:span', ( evt, data, conversionApi ) => {
+		const { writer } = conversionApi;
+
+		const matcher = new Matcher( config.view );
+		const matcherResult = matcher.match( data.viewItem );
+
+		// If there is no match, this callback should not do anything.
+		if ( !matcherResult ) {
+			return;
+		}
+
+		const match = matcherResult.match;
+
+		// Force consuming element's name (taken from upcast helpers elementToElement converter).
+		match.name = true;
+
+		const { modelRange: convertedChildrenRange } = conversionApi.convertChildren( data.viewItem, data.modelCursor );
+		conversionApi.consumable.consume( data.viewItem, match );
+
+		const markerName = config.model( data.viewItem );
+		const fakeMarkerStart = writer.createElement( '$marker', { 'data-name': markerName } );
+		const fakeMarkerEnd = writer.createElement( '$marker', { 'data-name': markerName } );
+
+		// Insert in reverse order to use converter content positions directly (without recalculating).
+		writer.insert( fakeMarkerEnd, convertedChildrenRange.end );
+		writer.insert( fakeMarkerStart, convertedChildrenRange.start );
+
+		data.modelRange = writer.createRange(
+			writer.createPositionBefore( fakeMarkerStart ),
+			writer.createPositionAfter( fakeMarkerEnd )
+		);
+		data.modelCursor = data.modelRange.end;
+	} );
+}

+ 42 - 0
packages/ckeditor5-restricted-editing/src/restrictededitingmode/utils.js

@@ -0,0 +1,42 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module restricted-editing/restrictededitingmode/utils
+ */
+
+export function getMarker( editor, position ) {
+	for ( const marker of editor.model.markers ) {
+		const markerRange = marker.getRange();
+
+		if ( isPositionInRangeOrOnRangeBoundary( markerRange, position ) ) {
+			if ( marker.name.startsWith( 'restricted-editing-exception:' ) ) {
+				return marker;
+			}
+		}
+	}
+}
+
+export function isPositionInRangeOrOnRangeBoundary( range, position ) {
+	return (
+		range.containsPosition( position ) ||
+		range.end.isEqual( position ) ||
+		range.start.isEqual( position )
+	);
+}
+
+export function isSelectionInExceptionMarker( marker, selection ) {
+	if ( !marker ) {
+		return false;
+	}
+
+	const markerRange = marker.getRange();
+
+	if ( selection.isCollapsed ) {
+		return isPositionInRangeOrOnRangeBoundary( markerRange, selection.focus );
+	}
+
+	return markerRange.containsRange( selection.getFirstRange(), true );
+}

+ 7 - 187
packages/ckeditor5-restricted-editing/src/restrictededitingmodeediting.js

@@ -8,10 +8,9 @@
  */
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import Matcher from '@ckeditor/ckeditor5-engine/src/view/matcher';
 import RestrictedEditingNavigationCommand from './restrictededitingmodenavigationcommand';
-
-const HIGHLIGHT_CLASS = 'ck-restricted-editing-exception_selected';
+import { setupExceptionHighlighting, setupMarkersConversion } from './restrictededitingmode/converters';
+import { getMarker, isSelectionInExceptionMarker } from './restrictededitingmode/utils';
 
 /**
  * The Restricted Editing Mode editing feature.
@@ -50,7 +49,7 @@ export default class RestrictedEditingModeEditing extends Plugin {
 		editor.commands.add( 'goToPreviousRestrictedEditingRegion', new RestrictedEditingNavigationCommand( editor, 'backward' ) );
 		editor.commands.add( 'goToNextRestrictedEditingRegion', new RestrictedEditingNavigationCommand( editor, 'forward' ) );
 
-		this._setupMarkersConversion( editor );
+		setupMarkersConversion( editor );
 
 		const getCommandExecuter = commandName => {
 			return ( data, cancel ) => {
@@ -67,7 +66,7 @@ export default class RestrictedEditingModeEditing extends Plugin {
 		editor.keystrokes.set( 'Tab', getCommandExecuter( 'goToNextRestrictedEditingRegion' ) );
 		editor.keystrokes.set( 'Shift+Tab', getCommandExecuter( 'goToPreviousRestrictedEditingRegion' ) );
 
-		this._setupExceptionHighlighting();
+		setupExceptionHighlighting( editor );
 		this._setupRestrictedEditingMode( editor );
 
 		// Block clipboard completely in restricted mode.
@@ -76,113 +75,6 @@ export default class RestrictedEditingModeEditing extends Plugin {
 		}, { priority: 'highest' } );
 	}
 
-	/**
-	 *
-	 * @param editor
-	 * @private
-	 */
-	_setupMarkersConversion( editor ) {
-		// The restricted editing does not attach additional data to the zones so there's no need for smarter markers management.
-		// Also, the markers will only be created when  when loading the data.
-		let markerNumber = 0;
-
-		editor.conversion.for( 'upcast' ).add( upcastHighlightToMarker( {
-			view: {
-				name: 'span',
-				classes: 'ck-restricted-editing-exception'
-			},
-			model: () => {
-				markerNumber++; // Starting from restricted-editing-exception:1 marker.
-
-				return `restricted-editing-exception:${ markerNumber }`;
-			}
-		} ) );
-
-		// Currently the marker helpers are tied to other use-cases and do not render collapsed marker as highlight.
-		// That's why there are 2 downcast converters for them:
-		// 1. The default marker-to-highlight will wrap selected text with `<span>`.
-		editor.conversion.for( 'downcast' ).markerToHighlight( {
-			model: 'restricted-editing-exception',
-			// Use callback to return new object every time new marker instance is created - otherwise it will be seen as the same marker.
-			view: () => ( {
-				name: 'span',
-				classes: 'ck-restricted-editing-exception',
-				priority: -10
-			} )
-		} );
-
-		// 2. But for collapsed marker we need to render it as an element.
-		// Additionally the editing pipeline should always display a collapsed markers.
-		editor.conversion.for( 'editingDowncast' ).markerToElement( {
-			model: 'restricted-editing-exception',
-			view: ( markerData, viewWriter ) => viewWriter.createUIElement( 'span', {
-				class: 'ck-restricted-editing-exception ck-restricted-editing-exception_collapsed'
-			} )
-		} );
-
-		editor.conversion.for( 'dataDowncast' ).markerToElement( {
-			model: 'restricted-editing-exception',
-			view: ( markerData, viewWriter ) => viewWriter.createEmptyElement( 'span', {
-				class: 'ck-restricted-editing-exception'
-			} )
-		} );
-	}
-
-	/**
-	 * Adds a visual highlight style to a restricted editing exception the selection is anchored to.
-	 *
-	 * Highlight is turned on by adding the `.ck-restricted-editing-exception_selected` class to the
-	 * exception in the view:
-	 *
-	 * * The class is removed before the conversion has started, as callbacks added with the `'highest'` priority
-	 * to {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} events.
-	 * * The class is added in the view post fixer, after other changes in the model tree were converted to the view.
-	 *
-	 * This way, adding and removing the highlight does not interfere with conversion.
-	 *
-	 * @private
-	 */
-	_setupExceptionHighlighting() {
-		const editor = this.editor;
-		const view = editor.editing.view;
-		const model = editor.model;
-		const highlightedMarkers = new Set();
-
-		// Adding the class.
-		view.document.registerPostFixer( writer => {
-			const modelSelection = model.document.selection;
-
-			const marker = this._getMarker( modelSelection.anchor );
-
-			if ( !marker ) {
-				return;
-			}
-
-			for ( const viewElement of editor.editing.mapper.markerNameToElements( marker.name ) ) {
-				writer.addClass( HIGHLIGHT_CLASS, viewElement );
-				highlightedMarkers.add( viewElement );
-			}
-		} );
-
-		// Removing the class.
-		editor.conversion.for( 'editingDowncast' ).add( dispatcher => {
-			// Make sure the highlight is removed on every possible event, before conversion is started.
-			dispatcher.on( 'insert', removeHighlight, { priority: 'highest' } );
-			dispatcher.on( 'remove', removeHighlight, { priority: 'highest' } );
-			dispatcher.on( 'attribute', removeHighlight, { priority: 'highest' } );
-			dispatcher.on( 'selection', removeHighlight, { priority: 'highest' } );
-
-			function removeHighlight() {
-				view.change( writer => {
-					for ( const item of highlightedMarkers.values() ) {
-						writer.removeClass( HIGHLIGHT_CLASS, item );
-						highlightedMarkers.delete( item );
-					}
-				} );
-			}
-		} );
-	}
-
 	_setupRestrictedEditingMode( editor ) {
 		this._disableCommands( editor );
 
@@ -223,7 +115,7 @@ export default class RestrictedEditingModeEditing extends Plugin {
 	}
 
 	_tryExtendMarkerStart( change, writer, changeApplied ) {
-		const markerAtStart = this._getMarker( change.position.getShiftedBy( 1 ) );
+		const markerAtStart = getMarker( this.editor, change.position.getShiftedBy( 1 ) );
 
 		if ( markerAtStart && markerAtStart.getStart().isEqual( change.position.getShiftedBy( 1 ) ) ) {
 			writer.updateMarker( markerAtStart, {
@@ -236,7 +128,7 @@ export default class RestrictedEditingModeEditing extends Plugin {
 	}
 
 	_tryExtendMarkedEnd( change, writer, changeApplied ) {
-		const markerAtEnd = this._getMarker( change.position );
+		const markerAtEnd = getMarker( this.editor, change.position );
 
 		if ( markerAtEnd && markerAtEnd.getEnd().isEqual( change.position ) ) {
 			writer.updateMarker( markerAtEnd, {
@@ -258,7 +150,7 @@ export default class RestrictedEditingModeEditing extends Plugin {
 			return;
 		}
 
-		const marker = this._getMarker( selection.focus );
+		const marker = getMarker( editor, selection.focus );
 
 		if ( isSelectionInExceptionMarker( marker, selection ) ) {
 			this._enableCommands( marker );
@@ -267,20 +159,6 @@ export default class RestrictedEditingModeEditing extends Plugin {
 		}
 	}
 
-	_getMarker( position ) {
-		const editor = this.editor;
-
-		for ( const marker of editor.model.markers ) {
-			const markerRange = marker.getRange();
-
-			if ( isPositionInRangeOrOnRangeBoundary( markerRange, position ) ) {
-				if ( marker.name.startsWith( 'restricted-editing-exception:' ) ) {
-					return marker;
-				}
-			}
-		}
-	}
-
 	_enableCommands( marker ) {
 		const editor = this.editor;
 
@@ -332,61 +210,3 @@ export default class RestrictedEditingModeEditing extends Plugin {
 			.filter( name => !this._alwaysEnabled.has( name ) );
 	}
 }
-
-function upcastHighlightToMarker( config ) {
-	return dispatcher => dispatcher.on( 'element:span', ( evt, data, conversionApi ) => {
-		const { writer } = conversionApi;
-
-		const matcher = new Matcher( config.view );
-		const matcherResult = matcher.match( data.viewItem );
-
-		// If there is no match, this callback should not do anything.
-		if ( !matcherResult ) {
-			return;
-		}
-
-		const match = matcherResult.match;
-
-		// Force consuming element's name (taken from upcast helpers elementToElement converter).
-		match.name = true;
-
-		const { modelRange: convertedChildrenRange } = conversionApi.convertChildren( data.viewItem, data.modelCursor );
-		conversionApi.consumable.consume( data.viewItem, match );
-
-		const markerName = config.model( data.viewItem );
-		const fakeMarkerStart = writer.createElement( '$marker', { 'data-name': markerName } );
-		const fakeMarkerEnd = writer.createElement( '$marker', { 'data-name': markerName } );
-
-		// Insert in reverse order to use converter content positions directly (without recalculating).
-		writer.insert( fakeMarkerEnd, convertedChildrenRange.end );
-		writer.insert( fakeMarkerStart, convertedChildrenRange.start );
-
-		data.modelRange = writer.createRange(
-			writer.createPositionBefore( fakeMarkerStart ),
-			writer.createPositionAfter( fakeMarkerEnd )
-		);
-		data.modelCursor = data.modelRange.end;
-	} );
-}
-
-function isSelectionInExceptionMarker( marker, selection ) {
-	if ( !marker ) {
-		return false;
-	}
-
-	const markerRange = marker.getRange();
-
-	if ( selection.isCollapsed ) {
-		return isPositionInRangeOrOnRangeBoundary( markerRange, selection.focus );
-	}
-
-	return markerRange.containsRange( selection.getFirstRange(), true );
-}
-
-function isPositionInRangeOrOnRangeBoundary( range, position ) {
-	return (
-		range.containsPosition( position ) ||
-		range.end.isEqual( position ) ||
-		range.start.isEqual( position )
-	);
-}