Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #97 from ckeditor/t/96
Browse files Browse the repository at this point in the history
Feature: Introduced `Editor#isReadOnly` property which disables all commands and prevents from modifying the document. Closes #96. Closes ckeditor/ckeditor5#492.
  • Loading branch information
Reinmar authored Jul 7, 2017
2 parents 4d768df + 599681a commit 1ca5608
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 7 deletions.
19 changes: 19 additions & 0 deletions src/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
}

/**
Expand Down
36 changes: 29 additions & 7 deletions src/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ 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';

/**
* Class representing a basic editor. It contains a base architecture, without much additional logic.
*
* See also {@link module:core/editor/standardeditor~StandardEditor}.
*
* @mixes module:utils/emittermixin~EmitterMixin
* @mixes module:utils/observablemixin~ObservableMixin
*/
export default class Editor {
/**
Expand Down Expand Up @@ -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();

Expand All @@ -74,23 +74,45 @@ 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}
*/
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}
*/
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.
Expand Down Expand Up @@ -194,7 +216,7 @@ export default class Editor {
}
}

mix( Editor, EmitterMixin );
mix( Editor, ObservableMixin );

/**
* Fired after {@link #initPlugins plugins are initialized}.
Expand Down
33 changes: 33 additions & 0 deletions tests/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()', () => {
Expand Down
19 changes: 19 additions & 0 deletions tests/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
34 changes: 34 additions & 0 deletions tests/manual/readonly.html
Original file line number Diff line number Diff line change
@@ -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 changes: 34 additions & 0 deletions tests/manual/readonly.js
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions tests/manual/readonly.md
Original file line number Diff line number Diff line change
@@ -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.
Binary file added tests/manual/sample.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1ca5608

Please sign in to comment.