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

Merge pull request #97 from ckeditor/t/96

Feature: Introduced `Editor#isReadOnly` property which disables all commands and prevents from modifying the document. Closes #96. Closes https://github.com/ckeditor/ckeditor5/issues/492.
Piotrek Koszuliński 8 лет назад
Родитель
Сommit
48d1e67fc9

+ 19 - 0
packages/ckeditor5-core/src/command.js

@@ -20,6 +20,8 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix';
  * Instances of registered commands can be retrieved from {@link module:core/editor/editor~Editor#commands}.
  * The easiest way to execute a command is through {@link module:core/editor/editor~Editor#execute}.
  *
+ * By default commands are disabled when the editor is in {@link module:core/editor/editor~Editor#isReadOnly read-only} mode.
+ *
  * @mixes module:utils/observablemixin~ObservableMixin
  */
 export default class Command {
@@ -73,6 +75,23 @@ export default class Command {
 				evt.stop();
 			}
 		}, { priority: 'high' } );
+
+		// By default commands are disabled when the editor is in read-only mode.
+		this.listenTo( editor, 'change:isReadOnly', ( evt, name, value ) => {
+			if ( value ) {
+				// See a ticket about overriding observable properties
+				// https://github.com/ckeditor/ckeditor5-utils/issues/171.
+				this.on( 'change:isEnabled', forceDisable, { priority: 'lowest' } );
+				this.isEnabled = false;
+			} else {
+				this.off( 'change:isEnabled', forceDisable );
+				this.refresh();
+			}
+		} );
+
+		function forceDisable() {
+			this.isEnabled = false;
+		}
 	}
 
 	/**

+ 29 - 7
packages/ckeditor5-core/src/editor/editor.js

@@ -14,7 +14,7 @@ import Locale from '@ckeditor/ckeditor5-utils/src/locale';
 import DataController from '@ckeditor/ckeditor5-engine/src/controller/datacontroller';
 import Document from '@ckeditor/ckeditor5-engine/src/model/document';
 
-import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
+import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 
 /**
@@ -22,7 +22,7 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix';
  *
  * See also {@link module:core/editor/standardeditor~StandardEditor}.
  *
- * @mixes module:utils/emittermixin~EmitterMixin
+ * @mixes module:utils/observablemixin~ObservableMixin
  */
 export default class Editor {
 	/**
@@ -55,7 +55,7 @@ export default class Editor {
 		 * Commands registered to the editor.
 		 *
 		 * @readonly
-		 * @member {module:core/command/commandcollection~CommandCollection}
+		 * @member {module:core/commandcollection~CommandCollection}
 		 */
 		this.commands = new CommandCollection();
 
@@ -74,7 +74,18 @@ export default class Editor {
 		this.t = this.locale.t;
 
 		/**
-		 * Tree Model document managed by this editor.
+		 * The editor's model document.
+		 *
+		 * The center of the editor's abstract data model. The document contains
+		 * {@link module:engine/model/document~Document#getRoot all editing roots},
+		 * {@link module:engine/model/document~Document#selection} and allows
+		 * applying changes to through the {@link module:engine/model/document~Document#batch batch interface}.
+		 *
+		 * Besides the model document, the editor usually contains two controllers –
+		 * {@link #data data controller} and {@link #editing editing controller}.
+		 * The former is used e.g. when setting or retrieving editor data and contains a useful
+		 * set of methods for operating on the content. The latter controls user input and rendering
+		 * the content for editing.
 		 *
 		 * @readonly
 		 * @member {module:engine/model/document~Document}
@@ -82,7 +93,7 @@ export default class Editor {
 		this.document = new Document();
 
 		/**
-		 * Instance of the {@link module:engine/controller/datacontroller~DataController data controller}.
+		 * The {@link module:engine/controller/datacontroller~DataController data controller}.
 		 *
 		 * @readonly
 		 * @member {module:engine/controller/datacontroller~DataController}
@@ -90,7 +101,18 @@ export default class Editor {
 		this.data = new DataController( this.document );
 
 		/**
-		 * Instance of the {@link module:engine/controller/editingcontroller~EditingController editing controller}.
+		 * Defines whether this editor is in read-only mode.
+		 *
+		 * In read-only mode the editor {@link #commands commands} are disabled so it is not possible
+		 * to modify document using them.
+		 *
+		 * @observable
+		 * @member {Boolean} #isReadOnly
+		 */
+		this.set( 'isReadOnly', false );
+
+		/**
+		 * The {@link module:engine/controller/editingcontroller~EditingController editing controller}.
 		 *
 		 * This property is set by more specialized editor classes (such as {@link module:core/editor/standardeditor~StandardEditor}),
 		 * however, it's required for features to work as their engine-related parts will try to connect converters.
@@ -194,7 +216,7 @@ export default class Editor {
 	}
 }
 
-mix( Editor, EmitterMixin );
+mix( Editor, ObservableMixin );
 
 /**
  * Fired after {@link #initPlugins plugins are initialized}.

+ 33 - 0
packages/ckeditor5-core/tests/command.js

@@ -65,6 +65,39 @@ describe( 'Command', () => {
 
 			expect( spy.calledOnce ).to.be.true;
 		} );
+
+		it( 'is always falsy when the editor is in read-only mode', () => {
+			editor.isReadOnly = false;
+			command.isEnabled = true;
+
+			editor.isReadOnly = true;
+
+			// Is false.
+			expect( command.isEnabled ).to.false;
+
+			command.refresh();
+
+			// Still false.
+			expect( command.isEnabled ).to.false;
+
+			editor.isReadOnly = false;
+
+			// And is back to true.
+			expect( command.isEnabled ).to.true;
+		} );
+
+		it( 'is observable when is overridden', () => {
+			editor.isReadOnly = false;
+			command.isEnabled = true;
+
+			editor.bind( 'something' ).to( command, 'isEnabled' );
+
+			expect( editor.something ).to.true;
+
+			editor.isReadOnly = true;
+
+			expect( editor.something ).to.false;
+		} );
 	} );
 
 	describe( 'execute()', () => {

+ 19 - 0
packages/ckeditor5-core/tests/editor/editor.js

@@ -158,6 +158,25 @@ describe( 'Editor', () => {
 		} );
 	} );
 
+	describe( 'isReadOnly', () => {
+		it( 'is false initially', () => {
+			const editor = new Editor();
+
+			expect( editor.isReadOnly ).to.false;
+		} );
+
+		it( 'is observable', () => {
+			const editor = new Editor();
+			const spy = sinon.spy();
+
+			editor.on( 'change:isReadOnly', spy );
+
+			editor.isReadOnly = true;
+
+			sinon.assert.calledOnce( spy );
+		} );
+	} );
+
 	describe( 'destroy()', () => {
 		it( 'should fire "destroy"', () => {
 			const editor = new Editor();

+ 34 - 0
packages/ckeditor5-core/tests/manual/readonly.html

@@ -0,0 +1,34 @@
+<button id="read-only">Turn on read-only mode</button>
+
+<div id="editor">
+	<h2>Heading 1</h2>
+	<p>Paragraph</p>
+	<p><strong>Bold</strong> <i>Italic</i> <a href="foo">Link</a></p>
+	<ul>
+		<li>UL List item 1</li>
+		<li>UL List item 2</li>
+	</ul>
+	<ol>
+		<li>OL List item 1</li>
+		<li>OL List item 2</li>
+	</ol>
+	<figure class="image image-style-side">
+		<img alt="bar" src="sample.jpg">
+		<figcaption>Caption</figcaption>
+	</figure>
+	<blockquote>
+		<p>Quote</p>
+		<ul>
+			<li>Quoted UL List item 1</li>
+			<li>Quoted UL List item 2</li>
+		</ul>
+		<p>Quote</p>
+	</blockquote>
+</div>
+
+<style>
+	#read-only {
+		margin: 10px 0 15px;
+		font-size: 16px;
+	}
+</style>

+ 34 - 0
packages/ckeditor5-core/tests/manual/readonly.js

@@ -0,0 +1,34 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+
+import ArticlePreset from '@ckeditor/ckeditor5-presets/src/article';
+import ContextualToolbar from '@ckeditor/ckeditor5-ui/src/toolbar/contextual/contextualtoolbar';
+
+ClassicEditor.create( document.querySelector( '#editor' ), {
+	plugins: [ ArticlePreset, ContextualToolbar ],
+	toolbar: [ 'headings', 'bold', 'italic', 'link', 'unlink', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo' ],
+	image: {
+		toolbar: [ 'imageStyleFull', 'imageStyleSide', '|', 'imageTextAlternative' ],
+	},
+	contextualToolbar: [ 'bold', 'italic', 'link' ]
+} )
+.then( editor => {
+	window.editor = editor;
+
+	const button = document.querySelector( '#read-only' );
+
+	button.addEventListener( 'click', () => {
+		editor.isReadOnly = !editor.isReadOnly;
+		button.textContent = editor.isReadOnly ? 'Turn off read-only mode' : 'Turn on read-only mode';
+	} );
+} )
+.catch( err => {
+	console.error( err.stack );
+} );
+

+ 11 - 0
packages/ckeditor5-core/tests/manual/readonly.md

@@ -0,0 +1,11 @@
+## Read-only
+
+1. Change a content in the editor.
+2. Turn on read-only mode by clicking the button above the editor.
+3. Check if undo/redo (by shortcuts) is disabled.
+4. Check if modifying content is blocked (input, delete, paste) for collapsed and non-collapsed selection.
+5. Check if toolbar buttons are disabled.
+6. Check if toolbar buttons reflect selection attributes (eg. when selection is in bold text then bold button should be on).
+7. Check 5 and 6 for the ContextualToolbar.
+8. Check if link balloon panel opens in read-only mode.
+9. Check if image text alternative balloon opens in read-only mode.

BIN
packages/ckeditor5-core/tests/manual/sample.jpg