Browse Source

Enforce input command to the ranges inside exception marker.

Maciej Gołaszewski 6 years ago
parent
commit
a7f4aef509

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

@@ -165,6 +165,14 @@ export default class RestrictedEditingModeEditing extends Plugin {
 		this.listenTo( model, 'deleteContent', restrictDeleteContent( editor ), { priority: 'high' } );
 		this.listenTo( model, 'applyOperation', restrictAttributeOperation( editor ), { priority: 'high' } );
 
+		const inputCommand = this.editor.commands.get( 'input' );
+
+		// The restricted editing might be configured without input support - ie allow only bolding or removing text.
+		// This check is bit synthetic since only tests are used this way.
+		if ( inputCommand ) {
+			this.listenTo( inputCommand, 'execute', restrictInputRangeOption( editor ), { priority: 'high' } );
+		}
+
 		setupExceptionHighlighting( editor );
 	}
 
@@ -341,3 +349,30 @@ function restrictAttributeOperation( editor ) {
 		operation.range = operation.range.getIntersection( marker.getRange() );
 	};
 }
+
+// Ensures that input command is executed with a range that is inside exception marker.
+//
+// This restriction is due to fact that using native spell check changes text outside exception marker.
+function restrictInputRangeOption( editor ) {
+	return ( evt, args ) => {
+		const [ options ] = args;
+		const { range } = options;
+
+		// Only check "input" command executed with a range value.
+		// Selection might be set in exception marker but passed range might point elsewhere.
+		if ( !range ) {
+			return;
+		}
+
+		if ( !isRangeInsideSingleMarker( editor, range ) ) {
+			evt.stop();
+		}
+	};
+}
+
+function isRangeInsideSingleMarker( editor, range ) {
+	const markerAtStart = getMarkerAtPosition( editor, range.start );
+	const markerAtEnd = getMarkerAtPosition( editor, range.end );
+
+	return markerAtStart && markerAtEnd && markerAtEnd === markerAtStart;
+}

+ 1 - 0
packages/ckeditor5-restricted-editing/tests/manual/restrictedediting.html

@@ -7,6 +7,7 @@
 <div id="editor">
 	<h2>Heading 1</h2>
 	<p>Paragraph <span class="ck-restricted-editing-exception">it is editable</span></p>
+	<p>Exception on part of a word: <a href="ckeditor.com">coompi</a><span class="ck-restricted-editing-exception"><a href="ckeditor.com">ter</a> (fix spelling in Firefox).</span></p>
 	<p><strong>Bold</strong> <i>Italic</i> <a href="foo">Link</a></p>
 	<ul>
 		<li>UL List item 1</li>

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

@@ -479,6 +479,100 @@ describe( 'RestrictedEditingModeEditing', () => {
 		} );
 	} );
 
+	describe( 'enforcing restrictions on input command', () => {
+		let firstParagraph;
+
+		beforeEach( async () => {
+			editor = await VirtualTestEditor.create( { plugins: [ Paragraph, Typing, RestrictedEditingModeEditing ] } );
+			model = editor.model;
+
+			setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
+
+			firstParagraph = model.document.getRoot().getChild( 0 );
+		} );
+
+		afterEach( async () => {
+			await editor.destroy();
+		} );
+
+		it( 'should prevent changing text before exception marker', () => {
+			addExceptionMarker( 4, 7, firstParagraph );
+
+			model.change( writer => {
+				writer.setSelection( firstParagraph, 5 );
+			} );
+
+			// Simulate native spell-check action.
+			editor.execute( 'input', {
+				text: 'xxxxxxx',
+				range: model.createRange(
+					model.createPositionAt( firstParagraph, 0 ),
+					model.createPositionAt( firstParagraph, 7 )
+				)
+			} );
+
+			assertEqualMarkup( getModelData( model ), '<paragraph>foo b[]ar baz</paragraph>' );
+		} );
+
+		it( 'should prevent changing text before exception marker', () => {
+			addExceptionMarker( 4, 7, firstParagraph );
+
+			model.change( writer => {
+				writer.setSelection( firstParagraph, 5 );
+			} );
+
+			// Simulate native spell-check action.
+			editor.execute( 'input', {
+				text: 'xxxxxxx',
+				range: model.createRange(
+					model.createPositionAt( firstParagraph, 4 ),
+					model.createPositionAt( firstParagraph, 9 )
+				)
+			} );
+
+			assertEqualMarkup( getModelData( model ), '<paragraph>foo b[]ar baz</paragraph>' );
+		} );
+
+		it( 'should prevent changing text before (change crossing different markers)', () => {
+			addExceptionMarker( 0, 4, firstParagraph );
+			addExceptionMarker( 7, 9, firstParagraph, 2 );
+
+			model.change( writer => {
+				writer.setSelection( firstParagraph, 2 );
+			} );
+
+			// Simulate native spell-check action.
+			editor.execute( 'input', {
+				text: 'xxxxxxx',
+				range: model.createRange(
+					model.createPositionAt( firstParagraph, 2 ),
+					model.createPositionAt( firstParagraph, 8 )
+				)
+			} );
+
+			assertEqualMarkup( getModelData( model ), '<paragraph>fo[]o bar baz</paragraph>' );
+		} );
+
+		it( 'should allow changing text inside single marker', () => {
+			addExceptionMarker( 0, 9, firstParagraph );
+
+			model.change( writer => {
+				writer.setSelection( firstParagraph, 2 );
+			} );
+
+			// Simulate native spell-check action.
+			editor.execute( 'input', {
+				text: 'xxxxxxx',
+				range: model.createRange(
+					model.createPositionAt( firstParagraph, 2 ),
+					model.createPositionAt( firstParagraph, 8 )
+				)
+			} );
+
+			assertEqualMarkup( getModelData( model ), '<paragraph>foxxxxxxx[]baz</paragraph>' );
+		} );
+	} );
+
 	describe( 'clipboard', () => {
 		let model, viewDoc;