diff --git a/src/command.js b/src/command.js index 7eeb4ba6..23649bf0 100644 --- a/src/command.js +++ b/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; + } } /** diff --git a/src/editor/editor.js b/src/editor/editor.js index 83eb502f..b478f0df 100644 --- a/src/editor/editor.js +++ b/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}. diff --git a/tests/command.js b/tests/command.js index a6faee40..2dcaaf4f 100644 --- a/tests/command.js +++ b/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()', () => { diff --git a/tests/editor/editor.js b/tests/editor/editor.js index f25ad82b..fa78000e 100644 --- a/tests/editor/editor.js +++ b/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(); diff --git a/tests/manual/readonly.html b/tests/manual/readonly.html new file mode 100644 index 00000000..e34fc418 --- /dev/null +++ b/tests/manual/readonly.html @@ -0,0 +1,34 @@ + + +
Paragraph
+Bold Italic Link
+++Quote
++
+- Quoted UL List item 1
+- Quoted UL List item 2
+Quote
+