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

Add enforcing restrictions on remove attribute operation.

Maciej Gołaszewski 6 лет назад
Родитель
Сommit
ca49d25713

+ 25 - 0
packages/ckeditor5-restricted-editing/src/restrictededitingmodeediting.js

@@ -163,6 +163,7 @@ export default class RestrictedEditingModeEditing extends Plugin {
 		doc.registerPostFixer( resurrectCollapsedMarkerPostFixer( editor ) );
 
 		this.listenTo( model, 'deleteContent', restrictDeleteContent( editor ), { priority: 'high' } );
+		this.listenTo( model, 'applyOperation', restrictAttributeOperation( editor ), { priority: 'high' } );
 
 		setupExceptionHighlighting( editor );
 	}
@@ -316,3 +317,27 @@ function restrictDeleteContent( editor ) {
 		selection.setTo( allowedToDelete );
 	};
 }
+
+// Ensures that remove attribute operation is executed on proper range.
+//
+// The restriction is enforced by trimming range of an AttributeOperation:
+function restrictAttributeOperation( editor ) {
+	return ( evt, args ) => {
+		const [ operation ] = args;
+
+		if ( operation.type != 'removeAttribute' ) {
+			return;
+		}
+
+		const marker = getMarkerAtPosition( editor, operation.range.start ) || getMarkerAtPosition( editor, operation.range.end );
+
+		// Stop method execution if marker was not found at selection focus.
+		if ( !marker ) {
+			evt.stop();
+
+			return;
+		}
+
+		operation.range = operation.range.getIntersection( marker.getRange() );
+	};
+}

+ 61 - 0
packages/ckeditor5-restricted-editing/tests/restrictededitingmodeediting.js

@@ -418,6 +418,67 @@ describe( 'RestrictedEditingModeEditing', () => {
 		} );
 	} );
 
+	describe( 'enforcing restrictions on remove attribute operation', () => {
+		beforeEach( async () => {
+			editor = await VirtualTestEditor.create( { plugins: [ Paragraph, Typing, RestrictedEditingModeEditing ] } );
+			model = editor.model;
+
+			editor.model.schema.extend( '$text', { allowAttributes: [ 'link' ] } );
+		} );
+
+		afterEach( async () => {
+			await editor.destroy();
+		} );
+
+		it( 'should not allow to delete content outside restricted area', () => {
+			setModelData( model, '<paragraph><$text link="true">[]foo bar</$text> baz</paragraph>' );
+			const firstParagraph = model.document.getRoot().getChild( 0 );
+			addExceptionMarker( 4, 7, firstParagraph );
+
+			model.change( writer => {
+				writer.removeAttribute( 'link', writer.createRange(
+					writer.createPositionAt( firstParagraph, 0 ),
+					writer.createPositionAt( firstParagraph, 1 )
+				) );
+			} );
+
+			assertEqualMarkup( getModelData( model ), '<paragraph><$text link="true">[]foo bar</$text> baz</paragraph>' );
+		} );
+
+		it( 'should trim deleted content to a exception marker (end in marker)', () => {
+			setModelData( model, '<paragraph><$text link="true">[]foo bar</$text> baz</paragraph>' );
+			const firstParagraph = model.document.getRoot().getChild( 0 );
+			addExceptionMarker( 4, 7, firstParagraph );
+
+			model.change( writer => {
+				writer.removeAttribute( 'link', writer.createRange(
+					writer.createPositionAt( firstParagraph, 0 ),
+					writer.createPositionAt( firstParagraph, 7 )
+				) );
+			} );
+
+			assertEqualMarkup( getModelData( model ), '<paragraph><$text link="true">[]foo </$text>bar baz</paragraph>' );
+		} );
+
+		it( 'should trim deleted content to a exception marker (start in marker)', () => {
+			setModelData( model, '<paragraph><$text link="true">[]foo bar baz</$text></paragraph>' );
+			const firstParagraph = model.document.getRoot().getChild( 0 );
+			addExceptionMarker( 4, 7, firstParagraph );
+
+			model.change( writer => {
+				writer.removeAttribute( 'link', writer.createRange(
+					writer.createPositionAt( firstParagraph, 5 ),
+					writer.createPositionAt( firstParagraph, 8 )
+				) );
+			} );
+
+			assertEqualMarkup(
+				getModelData( model ),
+				'<paragraph><$text link="true">[]foo b</$text>ar<$text link="true"> baz</$text></paragraph>'
+			);
+		} );
+	} );
+
 	describe( 'clipboard', () => {
 		let model, viewDoc;