Explorar o código

Merge branch 'master' into t/6041

Maciej Gołaszewski %!s(int64=6) %!d(string=hai) anos
pai
achega
c63efe61d6

+ 29 - 1
packages/ckeditor5-restricted-editing/src/restrictededitingmodeediting.js

@@ -86,6 +86,7 @@ export default class RestrictedEditingModeEditing extends Plugin {
 		editor.commands.add( 'goToNextRestrictedEditingException', new RestrictedEditingNavigationCommand( editor, 'forward' ) );
 		editor.keystrokes.set( 'Tab', getCommandExecuter( editor, 'goToNextRestrictedEditingException' ) );
 		editor.keystrokes.set( 'Shift+Tab', getCommandExecuter( editor, 'goToPreviousRestrictedEditingException' ) );
+		editor.keystrokes.set( 'Ctrl+A', getSelectAllHandler( editor ) );
 
 		editingView.change( writer => {
 			for ( const root of editingView.document.roots ) {
@@ -324,6 +325,33 @@ function getCommandExecuter( editor, commandName ) {
 	};
 }
 
+// Helper for handling Ctrl+A keydown behaviour.
+function getSelectAllHandler( editor ) {
+	return ( data, cancel ) => {
+		const model = editor.model;
+		const selection = editor.model.document.selection;
+		const marker = getMarkerAtPosition( editor, selection.focus );
+
+		if ( !marker ) {
+			return;
+		}
+
+		// If selection range is inside a restricted editing exception, select text only within the exception.
+		//
+		// Note: Second Ctrl+A press is also blocked and it won't select the entire text in the editor.
+		const selectionRange = selection.getFirstRange();
+		const markerRange = marker.getRange();
+
+		if ( markerRange.containsRange( selectionRange, true ) || selection.isCollapsed ) {
+			cancel();
+
+			model.change( writer => {
+				writer.setSelection( marker.getRange() );
+			} );
+		}
+	};
+}
+
 // Additional filtering rule for enabling "delete" and "forwardDelete" commands if selection is on range boundaries:
 //
 // Does not allow to enable command when selection focus is:
@@ -335,7 +363,7 @@ function filterDeleteCommandsOnMarkerBoundaries( selection, markerRange ) {
 			return false;
 		}
 
-		// Only for collapsed selection - non-collapsed seleciton that extends over a marker is handled elsewhere.
+		// Only for collapsed selection - non-collapsed selection that extends over a marker is handled elsewhere.
 		if ( name == 'forwardDelete' && selection.isCollapsed && markerRange.end.isEqual( selection.focus ) ) {
 			return false;
 		}

+ 1 - 1
packages/ckeditor5-restricted-editing/tests/manual/restrictedediting-focus.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 

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

@@ -1409,6 +1409,188 @@ describe( 'RestrictedEditingModeEditing', () => {
 		} );
 	} );
 
+	describe( 'custom keydown behaviour', () => {
+		let view, evtData;
+
+		beforeEach( async () => {
+			editor = await VirtualTestEditor.create( {
+				plugins: [ Paragraph, RestrictedEditingModeEditing, BoldEditing ]
+			} );
+
+			model = editor.model;
+			view = editor.editing.view;
+		} );
+
+		afterEach( () => {
+			return editor.destroy();
+		} );
+
+		describe( 'Ctrl+A handler', () => {
+			beforeEach( async () => {
+				evtData = {
+					keyCode: getCode( 'A' ),
+					ctrlKey: true,
+					preventDefault: sinon.spy(),
+					stopPropagation: sinon.spy()
+				};
+			} );
+
+			describe( 'collapsed selection', () => {
+				it( 'should select text only within an exception when selection is inside an exception', () => {
+					setModelData( model, '<paragraph>foo ba[]r baz</paragraph>' );
+
+					const paragraph = model.document.getRoot().getChild( 0 );
+
+					// <paragraph>foo <marker>ba[]r</marker> baz</paragraph>
+					addExceptionMarker( 4, 7, paragraph );
+
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.calledOnce( evtData.preventDefault );
+					sinon.assert.calledOnce( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>foo [bar] baz</paragraph>' );
+				} );
+
+				it( 'should select text only within an exception when selection is at the begining of an exception', () => {
+					setModelData( model, '<paragraph>foo []bar baz</paragraph>' );
+
+					const paragraph = model.document.getRoot().getChild( 0 );
+
+					// <paragraph>foo <marker>[]bar</marker> baz</paragraph>
+					addExceptionMarker( 4, 7, paragraph );
+
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.calledOnce( evtData.preventDefault );
+					sinon.assert.calledOnce( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>foo [bar] baz</paragraph>' );
+				} );
+
+				it( 'should select text only within an exception when selection is at the end of an exception', () => {
+					setModelData( model, '<paragraph>foo bar[] baz</paragraph>' );
+
+					const paragraph = model.document.getRoot().getChild( 0 );
+
+					// <paragraph>foo <marker>bar[]</marker> baz</paragraph>
+					addExceptionMarker( 4, 7, paragraph );
+
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.calledOnce( evtData.preventDefault );
+					sinon.assert.calledOnce( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>foo [bar] baz</paragraph>' );
+				} );
+
+				it( 'should not change the selection if the caret is not inside an exception', () => {
+					setModelData( model, '<paragraph>foo ba[]r baz</paragraph>' );
+
+					// no markers
+					// <paragraph>foo ba[]r baz</paragraph>
+
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.notCalled( evtData.preventDefault );
+					sinon.assert.notCalled( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>foo ba[]r baz</paragraph>' );
+				} );
+
+				it( 'should not extend the selection outside an exception when press Ctrl+A second time', () => {
+					setModelData( model, '<paragraph>foo b[]ar baz</paragraph>' );
+
+					const paragraph = model.document.getRoot().getChild( 0 );
+
+					// <paragraph>foo <marker>b[]ar</marker> baz</paragraph>
+					addExceptionMarker( 4, 7, paragraph );
+
+					view.document.fire( 'keydown', evtData );
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.calledTwice( evtData.preventDefault );
+					sinon.assert.calledTwice( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>foo [bar] baz</paragraph>' );
+				} );
+			} );
+
+			describe( 'non-collapsed selection', () => {
+				it( 'should select text within an exception when a whole selection range is inside an exception', () => {
+					setModelData( model, '<paragraph>fo[o ba]r baz</paragraph>' );
+
+					const paragraph = model.document.getRoot().getChild( 0 );
+
+					// <paragraph><marker>fo[o ba]r</marker> baz</paragraph>
+					addExceptionMarker( 0, 7, paragraph );
+
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.calledOnce( evtData.preventDefault );
+					sinon.assert.calledOnce( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>[foo bar] baz</paragraph>' );
+				} );
+
+				it( 'should select text within an exception when end of selection range is equal exception end', () => {
+					setModelData( model, '<paragraph>foo b[ar] baz</paragraph>' );
+
+					const paragraph = model.document.getRoot().getChild( 0 );
+
+					// <paragraph>foo <marker>b[ar]</marker> baz</paragraph>
+					addExceptionMarker( 4, 7, paragraph );
+
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.calledOnce( evtData.preventDefault );
+					sinon.assert.calledOnce( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>foo [bar] baz</paragraph>' );
+				} );
+
+				it( 'should select text within an exception when start of selection range is equal exception start', () => {
+					setModelData( model, '<paragraph>foo [ba]r baz</paragraph>' );
+
+					const paragraph = model.document.getRoot().getChild( 0 );
+
+					// <paragraph>foo <marker>[ba]r</marker> baz</paragraph>
+					addExceptionMarker( 4, 7, paragraph );
+
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.calledOnce( evtData.preventDefault );
+					sinon.assert.calledOnce( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>foo [bar] baz</paragraph>' );
+				} );
+
+				it( 'should not select text within an exception when a part of the selection range is outside an exception', () => {
+					setModelData( model, '<paragraph>fo[o ba]r baz</paragraph>' );
+
+					const paragraph = model.document.getRoot().getChild( 0 );
+
+					// <paragraph>fo[o <marker>ba]r</marker> baz</paragraph>
+					addExceptionMarker( 4, 7, paragraph );
+
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.notCalled( evtData.preventDefault );
+					sinon.assert.notCalled( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>fo[o ba]r baz</paragraph>' );
+				} );
+
+				it( 'should not extend the selection outside an exception when press Ctrl+A second time', () => {
+					setModelData( model, '<paragraph>foo [bar] baz</paragraph>' );
+
+					const paragraph = model.document.getRoot().getChild( 0 );
+
+					// <paragraph>foo <marker>[bar]</marker> baz</paragraph>
+					addExceptionMarker( 4, 7, paragraph );
+
+					view.document.fire( 'keydown', evtData );
+					view.document.fire( 'keydown', evtData );
+
+					sinon.assert.calledTwice( evtData.preventDefault );
+					sinon.assert.calledTwice( evtData.stopPropagation );
+					expect( getModelData( model ) ).to.be.equal( '<paragraph>foo [bar] baz</paragraph>' );
+				} );
+			} );
+		} );
+	} );
+
 	// Helper method that creates an exception marker inside given parent.
 	// Marker range is set to given position offsets (start, end).
 	function addExceptionMarker( startOffset, endOffset = startOffset, parent, id = 1 ) {