Kaynağa Gözat

Add configuration option to define commands allowed in non-restricted region.

Maciej Gołaszewski 6 yıl önce
ebeveyn
işleme
dc7e052ca4

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

@@ -38,3 +38,46 @@ export default class RestrictedEditingMode extends Plugin {
 		return [ RestrictedEditingModeEditing, RestrictedEditingModeUI ];
 		return [ RestrictedEditingModeEditing, RestrictedEditingModeUI ];
 	}
 	}
 }
 }
+
+/**
+ * The configuration of the restricted editing mode feature. Introduced by the
+ * {@link module:restricted-editing/restrictededitingmode~RestrictedEditingMode} feature.
+ *
+ * Read more in {@link module:restricted-editing/restrictededitingmode~RestrictedEditingModeConfig}.
+ *
+ * @member {module:restricted-editing/restrictededitingmode~RestrictedEditingModeConfig}
+ * module:core/editor/editorconfig~EditorConfig#restrictedEditing
+ */
+
+/**
+ * The configuration of the restricted editing mode feature.
+ * The option is used by the {@link module:restricted-editing/restrictededitingmode~RestrictedEditingMode} feature.
+ *
+ *		ClassicEditor
+ *			.create( {
+ * 				restrictedEditing: ... // Restricted editing mode feature config.
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
+ *
+ * @interface module:restricted-editing/restrictededitingmode~RestrictedEditingModeConfig
+ */
+
+/**
+ * The command names allowed in non-restricted areas of the content.
+ *
+ * Define which feature commands should be enabled in restricted editing mode. The commands used for typing and deleting text
+ * (`'input'`, `'delete'` and `'forwardDelete'`) are allowed inside non-restricted regions.
+ *
+ * **Note**: The restricted editing mode always allows to use restricted mode navigation commands as well as `'undo'` and `'redo'` commands.
+ *
+ * The default value is:
+ *
+ *		const restrictedEditingConfig = {
+ *			allowedCommands: [ 'bold', 'italic', 'link' ]
+ *		};
+ *
+ * @member {Array.<String>} module:restricted-editing/restrictededitingmode~RestrictedEditingModeConfig#allowedCommands
+ */

+ 26 - 5
packages/ckeditor5-restricted-editing/src/restrictededitingmodeediting.js

@@ -40,8 +40,28 @@ export default class RestrictedEditingModeEditing extends Plugin {
 	constructor( editor ) {
 	constructor( editor ) {
 		super( editor );
 		super( editor );
 
 
+		editor.config.define( 'restrictedEditing', {
+			allowedCommands: [ 'bold', 'italic', 'link' ]
+		} );
+
+		/**
+		 * Command names that are enabled outside non-restricted regions.
+		 *
+		 * @type {Set<string>}
+		 * @private
+		 */
 		this._alwaysEnabled = new Set( [ 'undo', 'redo', 'goToPreviousRestrictedEditingRegion', 'goToNextRestrictedEditingRegion' ] );
 		this._alwaysEnabled = new Set( [ 'undo', 'redo', 'goToPreviousRestrictedEditingRegion', 'goToNextRestrictedEditingRegion' ] );
-		this._allowedInException = new Set( [ 'bold', 'italic', 'link', 'input', 'delete', 'forwardDelete' ] );
+
+		/**
+		 * Commands allowed in non-restricted areas.
+		 *
+		 * Commands always enabled combines typing feature commands: `'typing'`, `'delete'` and `'forwardDelete'` with commands defined
+		 * in the feature configuration.
+		 *
+		 * @type {Set<string>}
+		 * @private
+		 */
+		this._allowedInException = new Set( [ 'input', 'delete', 'forwardDelete' ] );
 	}
 	}
 
 
 	/**
 	/**
@@ -50,6 +70,10 @@ export default class RestrictedEditingModeEditing extends Plugin {
 	init() {
 	init() {
 		const editor = this.editor;
 		const editor = this.editor;
 
 
+		const allowedCommands = editor.config.get( 'restrictedEditing.allowedCommands' );
+
+		allowedCommands.forEach( commandName => this._allowedInException.add( commandName ) );
+
 		this._setupConversion();
 		this._setupConversion();
 		this._setupCommandsToggling();
 		this._setupCommandsToggling();
 
 
@@ -131,10 +155,7 @@ export default class RestrictedEditingModeEditing extends Plugin {
 	}
 	}
 
 
 	/**
 	/**
-	 * Setups the commands handling:
-	 *
-	 * * exposes the navigation commands
-	 * *
+	 * Setups the commands toggling - enables or disables commands based on user selection.
 	 *
 	 *
 	 * @private
 	 * @private
 	 */
 	 */

+ 275 - 112
packages/ckeditor5-restricted-editing/tests/restrictededitingmodeediting-commands.js

@@ -90,156 +90,150 @@ describe( 'RestrictedEditingEditing - commands', () => {
 		} );
 		} );
 
 
 		describe( 'commands enabled in exception marker', () => {
 		describe( 'commands enabled in exception marker', () => {
-			const commands = [
-				'input', 'bold', 'link', 'italic'
-			];
-
-			for ( const commandName of commands ) {
-				describe( commandName, () => {
-					beforeEach( () => {
-						editor.commands.add( commandName, buildFakeCommand( editor ) );
+			describe( 'input', () => {
+				beforeEach( () => {
+					editor.commands.add( 'input', buildFakeCommand( editor ) );
 
 
-						model.change( writer => {
-							writer.setSelection( firstParagraph, 'end' );
-						} );
+					model.change( writer => {
+						writer.setSelection( firstParagraph, 'end' );
 					} );
 					} );
+				} );
 
 
-					it( 'should be disabled when caret is outside exception marker', () => {
-						model.change( writer => {
-							writer.setSelection( firstParagraph, 1 );
-						} );
-
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.false;
+				it( 'should be disabled when caret is outside exception marker', () => {
+					model.change( writer => {
+						writer.setSelection( firstParagraph, 1 );
 					} );
 					} );
 
 
-					it( 'should be enabled when caret is inside exception marker (not touching boundaries)', () => {
-						model.change( writer => {
-							writer.setSelection( firstParagraph, 5 );
-						} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.true;
+				it( 'should be enabled when caret is inside exception marker (not touching boundaries)', () => {
+					model.change( writer => {
+						writer.setSelection( firstParagraph, 5 );
 					} );
 					} );
 
 
-					it( 'should be disabled when caret is inside other marker', () => {
-						model.change( writer => {
-							writer.addMarker( 'foo-bar:1', {
-								range: writer.createRange(
-									writer.createPositionAt( firstParagraph, 0 ),
-									writer.createPositionAt( firstParagraph, 3 ) ),
-								usingOperation: true,
-								affectsData: true
-							} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
+				} );
 
 
-							writer.setSelection( firstParagraph, 1 );
+				it( 'should be disabled when caret is inside other marker', () => {
+					model.change( writer => {
+						writer.addMarker( 'foo-bar:1', {
+							range: writer.createRange(
+								writer.createPositionAt( firstParagraph, 0 ),
+								writer.createPositionAt( firstParagraph, 3 ) ),
+							usingOperation: true,
+							affectsData: true
 						} );
 						} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.false;
+						writer.setSelection( firstParagraph, 1 );
 					} );
 					} );
 
 
-					it( 'should be enabled when caret is inside exception marker (start boundary)', () => {
-						model.change( writer => {
-							writer.setSelection( firstParagraph, 4 );
-						} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.true;
+				it( 'should be enabled when caret is inside exception marker (start boundary)', () => {
+					model.change( writer => {
+						writer.setSelection( firstParagraph, 4 );
 					} );
 					} );
 
 
-					it( 'should be enabled when caret is inside exception marker (end boundary)', () => {
-						model.change( writer => {
-							writer.setSelection( firstParagraph, 7 );
-						} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.true;
+				it( 'should be enabled when caret is inside exception marker (end boundary)', () => {
+					model.change( writer => {
+						writer.setSelection( firstParagraph, 7 );
 					} );
 					} );
 
 
-					it( 'should be disabled for multi-range selection (collapsed ranges)', () => {
-						model.change( writer => {
-							writer.setSelection( [
-								writer.createRange(
-									writer.createPositionAt( firstParagraph, 5 )
-								),
-								writer.createRange(
-									writer.createPositionAt( firstParagraph, 9 )
-								)
-							] );
-						} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.false;
+				it( 'should be disabled for multi-range selection (collapsed ranges)', () => {
+					model.change( writer => {
+						writer.setSelection( [
+							writer.createRange(
+								writer.createPositionAt( firstParagraph, 5 )
+							),
+							writer.createRange(
+								writer.createPositionAt( firstParagraph, 9 )
+							)
+						] );
 					} );
 					} );
 
 
-					it( 'should be disabled for non-collapsed selection that expands over exception marker', () => {
-						model.change( writer => {
-							writer.setSelection( writer.createRange(
-								writer.createPositionAt( firstParagraph, 0 ),
-								writer.createPositionAt( firstParagraph, 5 )
-							) );
-						} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.false;
+				it( 'should be disabled for non-collapsed selection that expands over exception marker', () => {
+					model.change( writer => {
+						writer.setSelection( writer.createRange(
+							writer.createPositionAt( firstParagraph, 0 ),
+							writer.createPositionAt( firstParagraph, 5 )
+						) );
 					} );
 					} );
 
 
-					it( 'should be enabled for non-collapsed selection that is fully contained inside exception marker', () => {
-						model.change( writer => {
-							writer.setSelection( writer.createRange(
-								writer.createPositionAt( firstParagraph, 5 ),
-								writer.createPositionAt( firstParagraph, 6 )
-							) );
-						} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.true;
+				it( 'should be enabled for non-collapsed selection that is fully contained inside exception marker', () => {
+					model.change( writer => {
+						writer.setSelection( writer.createRange(
+							writer.createPositionAt( firstParagraph, 5 ),
+							writer.createPositionAt( firstParagraph, 6 )
+						) );
 					} );
 					} );
 
 
-					it( 'should be enabled for non-collapsed selection inside exception marker (start position on marker boundary)', () => {
-						model.change( writer => {
-							writer.setSelection( writer.createRange(
-								writer.createPositionAt( firstParagraph, 4 ),
-								writer.createPositionAt( firstParagraph, 6 )
-							) );
-						} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.true;
+				it( 'should be enabled for non-collapsed selection inside exception marker (start position on marker boundary)', () => {
+					model.change( writer => {
+						writer.setSelection( writer.createRange(
+							writer.createPositionAt( firstParagraph, 4 ),
+							writer.createPositionAt( firstParagraph, 6 )
+						) );
 					} );
 					} );
 
 
-					it( 'should be enabled for non-collapsed selection inside exception marker (end position on marker boundary)', () => {
-						model.change( writer => {
-							writer.setSelection( writer.createRange(
-								writer.createPositionAt( firstParagraph, 5 ),
-								writer.createPositionAt( firstParagraph, 7 )
-							) );
-						} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.true;
+				it( 'should be enabled for non-collapsed selection inside exception marker (end position on marker boundary)', () => {
+					model.change( writer => {
+						writer.setSelection( writer.createRange(
+							writer.createPositionAt( firstParagraph, 5 ),
+							writer.createPositionAt( firstParagraph, 7 )
+						) );
 					} );
 					} );
 
 
-					it( 'should be enabled for non-collapsed selection is equal to exception marker', () => {
-						model.change( writer => {
-							writer.setSelection( writer.createRange(
-								writer.createPositionAt( firstParagraph, 4 ),
-								writer.createPositionAt( firstParagraph, 7 )
-							) );
-						} );
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.true;
-					} );
-
-					it( 'should be disabled for non-collapsed selection with more then one range', () => {
-						model.change( writer => {
-							writer.setSelection( [
-								writer.createRange(
-									writer.createPositionAt( firstParagraph, 5 ),
-									writer.createPositionAt( firstParagraph, 6 )
-								),
-								writer.createRange(
-									writer.createPositionAt( firstParagraph, 8 ),
-									writer.createPositionAt( firstParagraph, 9 )
-								)
-							] );
-						} );
+				it( 'should be enabled for non-collapsed selection is equal to exception marker', () => {
+					model.change( writer => {
+						writer.setSelection( writer.createRange(
+							writer.createPositionAt( firstParagraph, 4 ),
+							writer.createPositionAt( firstParagraph, 7 )
+						) );
+					} );
+
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
+				} );
 
 
-						expect( editor.commands.get( commandName ).isEnabled ).to.be.false;
+				it( 'should be disabled for non-collapsed selection with more then one range', () => {
+					model.change( writer => {
+						writer.setSelection( [
+							writer.createRange(
+								writer.createPositionAt( firstParagraph, 5 ),
+								writer.createPositionAt( firstParagraph, 6 )
+							),
+							writer.createRange(
+								writer.createPositionAt( firstParagraph, 8 ),
+								writer.createPositionAt( firstParagraph, 9 )
+							)
+						] );
 					} );
 					} );
+
+					expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
 				} );
 				} );
-			}
+			} );
 
 
 			describe( 'delete', () => {
 			describe( 'delete', () => {
 				beforeEach( () => {
 				beforeEach( () => {
@@ -545,6 +539,175 @@ describe( 'RestrictedEditingEditing - commands', () => {
 		} );
 		} );
 	} );
 	} );
 
 
+	describe( 'commands enabled in exception marker by configuration', () => {
+		let model, firstParagraph;
+
+		beforeEach( async () => {
+			editor = await VirtualTestEditor.create( {
+				plugins: [ Paragraph, Typing, RestrictedEditingModeEditing ],
+				restrictedEditing: {
+					allowedCommands: [ 'allowed' ]
+				}
+			} );
+			model = editor.model;
+			editor.commands.add( 'allowed', buildFakeCommand( editor ) );
+
+			setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
+			firstParagraph = model.document.getRoot().getChild( 0 );
+
+			model.change( writer => {
+				writer.addMarker( 'restricted-editing-exception:1', {
+					range: writer.createRange(
+						writer.createPositionAt( firstParagraph, 4 ),
+						writer.createPositionAt( firstParagraph, 7 ) ),
+					usingOperation: true,
+					affectsData: true
+				} );
+
+				writer.setSelection( firstParagraph, 'end' );
+			} );
+		} );
+
+		afterEach( async () => {
+			await editor.destroy();
+		} );
+
+		it( 'should be disabled when caret is outside exception marker', () => {
+			model.change( writer => {
+				writer.setSelection( firstParagraph, 1 );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
+		} );
+
+		it( 'should be enabled when caret is inside exception marker (not touching boundaries)', () => {
+			model.change( writer => {
+				writer.setSelection( firstParagraph, 5 );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
+		} );
+
+		it( 'should be disabled when caret is inside other marker', () => {
+			model.change( writer => {
+				writer.addMarker( 'foo-bar:1', {
+					range: writer.createRange(
+						writer.createPositionAt( firstParagraph, 0 ),
+						writer.createPositionAt( firstParagraph, 3 ) ),
+					usingOperation: true,
+					affectsData: true
+				} );
+
+				writer.setSelection( firstParagraph, 1 );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
+		} );
+
+		it( 'should be enabled when caret is inside exception marker (start boundary)', () => {
+			model.change( writer => {
+				writer.setSelection( firstParagraph, 4 );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
+		} );
+
+		it( 'should be enabled when caret is inside exception marker (end boundary)', () => {
+			model.change( writer => {
+				writer.setSelection( firstParagraph, 7 );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
+		} );
+
+		it( 'should be disabled for multi-range selection (collapsed ranges)', () => {
+			model.change( writer => {
+				writer.setSelection( [
+					writer.createRange(
+						writer.createPositionAt( firstParagraph, 5 )
+					),
+					writer.createRange(
+						writer.createPositionAt( firstParagraph, 9 )
+					)
+				] );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
+		} );
+
+		it( 'should be disabled for non-collapsed selection that expands over exception marker', () => {
+			model.change( writer => {
+				writer.setSelection( writer.createRange(
+					writer.createPositionAt( firstParagraph, 0 ),
+					writer.createPositionAt( firstParagraph, 5 )
+				) );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
+		} );
+
+		it( 'should be enabled for non-collapsed selection that is fully contained inside exception marker', () => {
+			model.change( writer => {
+				writer.setSelection( writer.createRange(
+					writer.createPositionAt( firstParagraph, 5 ),
+					writer.createPositionAt( firstParagraph, 6 )
+				) );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
+		} );
+
+		it( 'should be enabled for non-collapsed selection inside exception marker (start position on marker boundary)', () => {
+			model.change( writer => {
+				writer.setSelection( writer.createRange(
+					writer.createPositionAt( firstParagraph, 4 ),
+					writer.createPositionAt( firstParagraph, 6 )
+				) );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
+		} );
+
+		it( 'should be enabled for non-collapsed selection inside exception marker (end position on marker boundary)', () => {
+			model.change( writer => {
+				writer.setSelection( writer.createRange(
+					writer.createPositionAt( firstParagraph, 5 ),
+					writer.createPositionAt( firstParagraph, 7 )
+				) );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
+		} );
+
+		it( 'should be enabled for non-collapsed selection is equal to exception marker', () => {
+			model.change( writer => {
+				writer.setSelection( writer.createRange(
+					writer.createPositionAt( firstParagraph, 4 ),
+					writer.createPositionAt( firstParagraph, 7 )
+				) );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
+		} );
+
+		it( 'should be disabled for non-collapsed selection with more then one range', () => {
+			model.change( writer => {
+				writer.setSelection( [
+					writer.createRange(
+						writer.createPositionAt( firstParagraph, 5 ),
+						writer.createPositionAt( firstParagraph, 6 )
+					),
+					writer.createRange(
+						writer.createPositionAt( firstParagraph, 8 ),
+						writer.createPositionAt( firstParagraph, 9 )
+					)
+				] );
+			} );
+
+			expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
+		} );
+	} );
+
 	describe( 'integration', () => {
 	describe( 'integration', () => {
 		let model, firstParagraph;
 		let model, firstParagraph;