diff --git a/.travis.yml b/.travis.yml
index b9bdae0..a7b4930 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,7 +24,5 @@ after_success:
- ckeditor5-dev-tests-save-revision
env:
global:
- - secure: yGjqvN+FNsbRuAneBgIi9AyraRbhCSm8MEd7FPq/exSTjfsWofOq5i9O7o7/02bJVZ8rveD71OaqAKQCWVG8BD4umbpu0N0PpkJbjVi8jmytzN28mUxgrmZkFbr96C5YnGqvdxf4AB9wT5ExtzTfp5XUO383/WoGPu9qt6rmb5II1PHwhT1WzXql/pCU5eGLccCYtlEr3ybWD9n3wiC4fM90M5axuSu9EwpvTEfMJQbeqEv7jSu+qlPUAJ4rSTmgt1pGIMxJgUpDi76qnn4VdTxRUZy+4heAFT9c/96tPV0QAz8cpVM3qGKSTuF8FAY71sddF4PX8wfG/1GjO+dHEmbop5Kz8UXnS8rnp9gx4Q5be8iW0DfPCqjc8a2ULWgUMr4jBnbZKpMHNT83Li1TfGflIiDksh68EKMRHHoVzxKJ2ysBSMzPqLfLXSyY9Jl/j5OBAiVl1n22oq5t9AlT6JEm0bYcgIwVIRpJqPMZMkZBCoLZHSyVfdIqdT+bcNgSEG5iLrXS9FlqsOS9FB4U3DbvPZbNBq+uoEuAyk5w+T5OkqIhXBddUG9emaa6TuQNNcATyucwWxB3HC1UAK8s5XTWVUzQ86nUShzH7aHJky+PJN0hkYAxwjldwtzTWwKdw+SBjk3Cl5h8JEepJSX0Lqtwci4XGoHsJ4JL5UCU5OM=
- - secure: KDj0ramEp45sMIyRogmNesjH5O2q7k5kopvDjG5+O1PWjMc+moxoSLYdcqknINLdHkid/SfwyfDppO6rvsVuLR9nvq7SPsM/xDGtLMoLYwUoWPrK57tQ1exl1FUWHzZs/7UJOwC+GnqqtL6bN6PAAj4aSLoTai/xDiDBTRMrVzDmYGeHaHihI44jtqsfBEkCGd9vfB0JDZvhncZqQrEubTwFnh+QwZXTnVR837p+EWhT87HvH/OPioXPnEF3O3N72u/WpqmQKrgM2vUWOfjHzZTEleBBJZnbrFFKzoOczig2a4I0hLiaqtK/bsSpjMpyKVzW+Jh+bQiKLk2B3DlUL+/6RH/dioo0DM438dI5sT46dNDl8k3mXCfJyGpn1by3EKdjEkkgLz095YzaCIEKBLVqKky4FhqRduEGpmXXMJw5xJQuaLOqC8jg0bgAl/E6q0uMyVPJuaC1ewGOHSx33j09ZXrOCAk57KzXgJzVi3BJR2kf4x2KygRqqVtFVYjOyvdAS2EieytMKwMVfQfrtmgJU5KCcbYzNX6ZEKEdS7SxTlyPtDdJssxvuuRU8xxgkswBxTkFGFG77dy0BTsa0xmAdiJnb/Z3F9hJP3VySOKj/lnATO3Yzr5v5l0jD1SQqfG9IYvDE/fDULs7/cFuj46voHIVBkziQ+IAqv/3vYo=
- - secure: Gx267GbmaD2jMWCirha+1wQ/A7kJnHMGIkzUUBgzsy8CszGfQv8WZe8KnSeiLjV/+/gXg9v+/0LPULDjs9e2d8FdpGdA+7iE3HPCXijR/LWCJq9ZiMN6QrCSaLI5o6mpkUB29sfgKOVfC6CEjyd00sK3LzcjSJeApSgPY90g1FOsbMg/t1PIzFUrJytvFnjUr2cOPay2ZeXrXFLox2je8n1YvtCmWt6QgMGmT0bBQRjYFHhiw7IsGZ9aT36Um4qx9hXADixIXfE+RRxhdrVcFhcmESfr7US9inZKMDGQa48VYUX8kdPr073bi9Wh8c5WsmiIT4cspviyLhWfHvq5hwhGt1FTbfcuQIeq172hFoLRphwWujENrzJnXjSbKIDFHqv08p0TJJLe/Q2af0imeWSL9+hszIN7Z7S12VYxbixAMmxaB9DXhqdVN7qhNKV4e/KhZv+60DLUPIrP7iLYNpb2TK1D9tr5rnF3v0FbJTCCr1XyCRbZSdGi9HnAm5bw7n5rllCW7NB6QUIPrBwBu7cJ37PqFeJXZDIXlmBEGP+ZBV4u7vIBBO8CXaaVCEvflIGqE15PbGvSc8wPiP4X4tLjnpgUY6OP67sCNRbuH0XeuvhGPRf3C15GNp14qM7cFtmoKSKxy+sUQBtx5LCpESI/JMEUiotqk232l0vqyR8=
- - secure: eqj8DaCp7F6/XXy6npJ4YO7jfQaAbMBYo2rsvYAk0lubYSL0fJsbtMR1Qm8kbXFENxFKhpcU6fI9pAPKBT2fvn3/dnIHTng4xklpWWdl8CgojGJ8wPu/RjTa9UxDXvsDaiixipg/uPfc9oReCzEhulNVB6V6iq9jHeBJaQqgI5R1b3GB3U/sIF+Y7vWhICjM9s7WE/ESqADVHMpSQnzMIAiFJwJgqB1zu9bkwSsjhMgNscgifWuB8+AoXVeCesSYyJQsG7PHSoY+JlzGUw05xtSc6x+3bivs7dyrt68bkaFkVN2YmAstxl45R0Itd5dgPt/bvXKo63CTtkNAsyvUrK8Z3F2VqnqqTJ+3exQbkHCxHW2Pd7QOyxVAy00OCdIcjEAtSQp5HrI9ZAipP/9JJq5KzdaNzpSLwuL6jt9WysnMQBF3LMB1ONWRlrCcGdkUfTt2cjBMR3yiaaPJK+Bk93eSNPDJ9BaQbIGtsI6p2aojlgYzz7Uvon9d1lY9mlZi5BnyWwiOzfpmdALZlnl9yl6Bh/PI7SfW0zWRYOWS33pSWt9nkoNre9uTOdAhlJpS1PohNraEzw1AYR1j6hRZszqpf8JpMIrdoUOdf4QMh1LIJZW7EbEULsvU9fDzLZucjQyYpZLvFjXQ15UR+u4iTsC8c9rNGdDJkuBAsg81zis=
+ - secure: czAq3wmkippaXuAaoiibg+6vtcfF6ylU6QgzLIABhegKQaOARSB6Cae7WeveCBKu03aiVxWSTzPtAkNnq4qGK8mxEh012r1F50zKBInR9QiEB8HHkiUrkO2cVt21hX/PzdCJ+Ys4Cjthm1xsQwZ8vrfXsZnyahkQCzLxqkQR7mYNiQGPRaGvxaJmgMlfNNgoRFO7KzcmTNX9OeU+4k2BJOuua8NuoZx01aCktaC6qGpYLFE9Kj+oDpWMcQXp8y20hIDeY7GOh2zRDlQqXQV9FslxOykHE0uJ2dRa6Wvf65PTQknrpkBLHQgdaIM0e/E4+xtKMxl7676P1gbpQAntyCD8QybE3N+ldJqrOzzLmQ5hTxKjHxzU3/rtXqowRCtJS1a9/gV6ABbE3PKzfBi1fhkx7uyzc84owKTuR8VMTuXwPqjAuBbbD2Limv1GbS+Of6VPlaM7FGg22ZIjWr5H/kEIydvB9+9ffIOCjwi+5LObss1MiqZTn6tYwJvq6ELTj4a/6rXF4r2G4GLqPfheNxad3FXrTGsT2BYkrp8J1YiNwHVPnaec16S8tzRS7dQjKQiTinp/3wtOsdJ87XY+Z0wKFwv1nvn9wFWpTLo2Lh9a1grxW05HvrrKr0MIwlVdieHHsjtaBMYbwS1WtIoOMA1znsghM6ZPUVGzYX5mn5w=
+ - secure: VJkWZMdkuuvwdHMyhJdTiJ9jjAtuVOQn7sQjpBAPzfLyjE7DliaS0gYHz5KG8RLs1Qn99ShNMr6PXcuZ7tMVF4YMSLU4AHh4+wg+EYXxWDbiiz3AbSPa2ScRVQrqayEdvdHv77ydPSXRj7wY2wWzpSQml6zdPHNhLRi9czKxPmehEloZKZfBiJY9/CmeiIF/Fv1KK/OsuOgafyOOG/ffJeqiI4agYW8PgxrMRl71YITl9IOWDZw093x4AqlXFG8JUxYk0JqiSJDWJCSxID7A/12lOe/ExsPPRQ69EKXMsMxsVLKhNlHpo3GHMXO0bf461Y0Y/1exCbeKP+F2ILaBJOKxhPD6ulWLTBcB/58XV/ke8G8cwYyFKl3nNxcejK1bR8fE9/4q+vsjnCTNq4tJxRN1RW324uC7AbL8f/Mur5TIWUGftmjhFLU+bjKkuTADhbwB9XYU2KhmAN96fpEvQ4RIz4/nIs9dbfKY/Hj2YAyblfXeWLMPxmhOXTScR7dkw+TWDiU2gUCw2MjU8AyOhB3Iwenxgpe+jGObvt+zPZz8UWgyJYb+KtZsNlmr+vvISa5m+jAukOcY2afH0tH/mMvwdXntLH7A+X40BWrP/14cA609clyw5ou5Jaxo+XPVX0GAsR/B99NkJbUGLKUgGP4fWf3WfGra1WVZZ9PKmuo=
diff --git a/docs/framework/guides/document-editor.md b/docs/framework/guides/document-editor.md
index 76ec3d5..92a8cd2 100644
--- a/docs/framework/guides/document-editor.md
+++ b/docs/framework/guides/document-editor.md
@@ -5,7 +5,7 @@ order: 30
# Document editor
-The {@link examples/builds/document-editor document editor example} showcases the {@link builds/guides/quick-start#document-editor document editor build} designed for document editing with a customized UI representing the layout of a sheet of paper. It was created on top of the {@link module:editor-decoupled/decouplededitor~DecoupledEditor `DecoupledEditor`} and makes the best of what it offers: the freedom to choose the location of the crucial UI elements in the application.
+The {@link examples/builds/document-editor document editor example} showcases the {@link builds/guides/quick-start#document-editor document editor build} designed for document editing with a customized UI representing the layout of a sheet of paper. It was created on top of the {@link module:editor-decoupled/decouplededitor~DecoupledEditor `DecoupledEditor`} class and makes the best of what it offers: the freedom to choose the location of the crucial UI elements in the application.
In this tutorial you will learn how to create your own document editor with a customized user interface, step–by–step.
@@ -13,27 +13,32 @@ In this tutorial you will learn how to create your own document editor with a cu
## The editor
-The `DecoupledDocumentEditor` includes all the necessary features for the task. All you need to do is import it and create a new instance.
+The document editor build includes all the necessary features for the task. All you need to do is import it and create a new instance.
Initial editor data.
', { - toolbarContainer: document.querySelector( '.document-editor__toolbar' ), - editableContainer: document.querySelector( '.document-editor__editable' ), - + .create( document.querySelector( '.document-editor__editable' ), { cloudServices: { .... } } ) .then( editor => { + const toolbarContainer = document.querySelector( '.document-editor__toolbar' ); + + toolbarContainer.appendChild( editor.ui.view.toolbar.element ); + window.editor = editor; } ) .catch( err => { @@ -41,9 +46,7 @@ DecoupledDocumentEditor } ); ``` -You may have noticed two configuration options used here: {@link module:core/editor/editorconfig~EditorConfig#toolbarContainer `config.toolbarContainer`} and {@link module:core/editor/editorconfig~EditorConfig#editableContainer `config.editableContainer`}. They specify the location of the editor toolbar and editable in your application. - -If you do not specify these configuration options, you have to make sure the editor UI is injected into your application after it fires the {@link module:core/editor/editorwithui~EditorWithUI#event:uiReady `uiReady`} event. The toolbar element is accessible via `editor.ui.view.toolbar.element` and the editable element can be found under `editor.ui.view.editable.element`. +You may have noticed that you have to make sure the editor UI is injected into your application after it fires the {@link module:core/editor/editorwithui~EditorWithUI#event:uiReady `Editor#uiReady`} event. The toolbar element can be found under `editor.ui.view.toolbar.element`.The initial editor data.
+Editor data
', { - * // The location of the toolbar in the DOM. - * toolbarContainer: document.querySelector( 'body div.toolbar-container' ), - * - * // The location of the editable in the DOM. - * editableContainer: document.querySelector( 'body div.editable-container' ) - * } ) + * .create( document.querySelector( '#editor' ) ) * .then( editor => { + * // Append the toolbar to the element. + * document.body.appendChild( editor.ui.view.toolbar.element ); + * * console.log( 'Editor was initialized', editor ); * } ) * .catch( err => { @@ -107,48 +146,48 @@ export default class DecoupledEditor extends Editor { * import ... * * DecoupledEditor - * .create( 'Editor data
', { + * .create( document.querySelector( '#editor' ), { * plugins: [ Essentials, Bold, Italic, ... ], - * toolbar: [ 'bold', 'italic', ... ], - * - * // The location of the toolbar in the DOM. - * toolbarContainer: document.querySelector( 'div.toolbar-container' ), - * - * // The location of the editable in the DOM. - * editableContainer: document.querySelector( 'div.editable-container' ) + * toolbar: [ 'bold', 'italic', ... ] * } ) * .then( editor => { + * // Append the toolbar to the element. + * document.body.appendChild( editor.ui.view.toolbar.element ); + * * console.log( 'Editor was initialized', editor ); * } ) * .catch( err => { * console.error( err.stack ); * } ); * - * **Note**: The {@link module:core/editor/editorconfig~EditorConfig#toolbarContainer `config.toolbarContainer`} and - * {@link module:core/editor/editorconfig~EditorConfig#editableContainer `config.editableContainer`} settings are optional. - * It is possible to define the location of the UI elements manually once the editor is up and running: + * **Note**: It is possible to create the editor out of a pure data string. The editor will then render + * an editable element that must be inserted into the DOM for the editor to work properly: * * DecoupledEditor * .create( 'Editor data
' ) * .then( editor => { - * console.log( 'Editor was initialized', editor ); - * - * // Append the toolbar and editable straight into the element. + * // Append the toolbar to the element. * document.body.appendChild( editor.ui.view.toolbar.element ); + * + * // Append the editable to the element. * document.body.appendChild( editor.ui.view.editable.element ); + * + * console.log( 'Editor was initialized', editor ); * } ) * .catch( err => { * console.error( err.stack ); * } ); * - * @param {String} data The data to be loaded into the editor. + * @param {HTMLElement|String} elementOrData The DOM element that serves as an editable. + * The data will be loaded from it and loaded back to it once the editor is destroyed. + * Alternatively, a data string to be loaded into the editor. * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration. * @returns {Promise} A promise resolved once the editor is ready. * The promise returns the created {@link module:editor-decoupled/decouplededitor~DecoupledEditor} instance. */ - static create( data, config ) { + static create( elementOrData, config ) { return new Promise( resolve => { - const editor = new this( config ); + const editor = new this( elementOrData, config ); resolve( editor.initPlugins() @@ -156,8 +195,9 @@ export default class DecoupledEditor extends Editor { editor.ui.init(); editor.fire( 'uiReady' ); } ) - .then( () => editor.editing.view.attachDomRoot( editor.ui.view.editableElement ) ) - .then( () => editor.data.init( data ) ) + .then( () => { + editor.data.init( editor.element ? getDataFromElement( editor.element ) : elementOrData ); + } ) .then( () => { editor.fire( 'dataReady' ); editor.fire( 'ready' ); @@ -169,51 +209,3 @@ export default class DecoupledEditor extends Editor { } mix( DecoupledEditor, DataApiMixin ); - -/** - * The configuration of the {@link module:editor-decoupled/decouplededitor~DecoupledEditor}. - * - * When specified, it controls the location of the {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#toolbar}: - * - * DecoupledEditor - * .create( 'Hello world!
', { - * // Append the toolbar to the element. - * toolbarContainer: document.body - * } ) - * .then( editor => { - * console.log( editor ); - * } ) - * .catch( error => { - * console.error( error ); - * } ); - * - * **Note**: If not specified, the toolbar must be manually injected into the DOM. See - * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`} - * to learn more. - * - * @member {HTMLElement} module:core/editor/editorconfig~EditorConfig#toolbarContainer - */ - -/** - * The configuration of the {@link module:editor-decoupled/decouplededitor~DecoupledEditor}. - * - * When specified, it controls the location of the {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#editable}: - * - * DecoupledEditor - * .create( 'Hello world!
', { - * // Append the editable to the element. - * editableContainer: document.body - * } ) - * .then( editor => { - * console.log( editor ); - * } ) - * .catch( error => { - * console.error( error ); - * } ); - * - * **Note**: If not specified, the editable must be manually injected into the DOM. See - * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`} - * to learn more. - * - * @member {HTMLElement} module:core/editor/editorconfig~EditorConfig#editableContainer - */ diff --git a/src/decouplededitorui.js b/src/decouplededitorui.js index f0ae78b..c36a581 100644 --- a/src/decouplededitorui.js +++ b/src/decouplededitorui.js @@ -52,22 +52,6 @@ export default class DecoupledEditorUI { * @private */ this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) ); - - /** - * The container for the {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#toolbar}. - * - * @type {HTMLElement|String} - * @private - */ - this._toolbarContainer = editor.config.get( 'toolbarContainer' ); - - /** - * The container for the {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#editable}. - * - * @type {HTMLElement|String} - * @private - */ - this._editableContainer = editor.config.get( 'editableContainer' ); } /** @@ -83,19 +67,12 @@ export default class DecoupledEditorUI { const editingRoot = editor.editing.view.document.getRoot(); view.editable.bind( 'isReadOnly' ).to( editingRoot ); view.editable.bind( 'isFocused' ).to( editor.editing.view.document ); + editor.editing.view.attachDomRoot( view.editableElement ); view.editable.name = editingRoot.rootName; this.focusTracker.add( this.view.editableElement ); this.view.toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory ); - if ( this._toolbarContainer ) { - this._toolbarContainer.appendChild( view.toolbar.element ); - } - - if ( this._editableContainer ) { - this._editableContainer.appendChild( view.editable.element ); - } - enableToolbarKeyboardFocus( { origin: editor.editing.view, originFocusTracker: this.focusTracker, @@ -108,6 +85,6 @@ export default class DecoupledEditorUI { * Destroys the UI. */ destroy() { - this.view.destroy( !!this._toolbarContainer, !!this._editableContainer ); + this.view.destroy(); } } diff --git a/src/decouplededitoruiview.js b/src/decouplededitoruiview.js index 4e8dd74..0b6ab77 100644 --- a/src/decouplededitoruiview.js +++ b/src/decouplededitoruiview.js @@ -18,9 +18,8 @@ import Template from '@ckeditor/ckeditor5-ui/src/template'; * {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#toolbar}, but without any * specific arrangement of the components in the DOM. * - * See {@link module:core/editor/editorconfig~EditorConfig#toolbarContainer `config.toolbarContainer`} and - * {@link module:core/editor/editorconfig~EditorConfig#editableContainer `config.editableContainer`} to - * learn more about the UI of the decoupled editor. + * See {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`} + * to learn more about this view. * * @extends module:ui/editorui/editoruiview~EditorUIView */ @@ -29,8 +28,9 @@ export default class DecoupledEditorUIView extends EditorUIView { * Creates an instance of the decoupled editor UI view. * * @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance. + * @param {HTMLElement} [editableElement] The DOM element to be used as editable. */ - constructor( locale ) { + constructor( locale, editableElement ) { super( locale ); /** @@ -47,37 +47,22 @@ export default class DecoupledEditorUIView extends EditorUIView { * @readonly * @member {module:ui/editableui/inline/inlineeditableuiview~InlineEditableUIView} */ - this.editable = new InlineEditableUIView( locale ); + this.editable = new InlineEditableUIView( locale, editableElement ); // This toolbar may be placed anywhere in the page so things like font size need to be reset in it. + // Also because of the above, make sure the toolbar supports rounded corners. Template.extend( this.toolbar.template, { attributes: { - class: 'ck-reset_all' + class: [ + 'ck-reset_all', + 'ck-rounded-corners' + ] } } ); this.registerChildren( [ this.toolbar, this.editable ] ); } - /** - * Destroys the view and removes the {@link #toolbar} and {@link #editable} - * {@link module:ui/view~View#element `element`} from the DOM, if required. - * - * @param {Boolean} [removeToolbar] When `true`, remove the {@link #toolbar} element from the DOM. - * @param {Boolean} [removeEditable] When `true`, remove the {@link #editable} element from the DOM. - */ - destroy( removeToolbar, removeEditable ) { - super.destroy(); - - if ( removeToolbar ) { - this.toolbar.element.remove(); - } - - if ( removeEditable ) { - this.editable.element.remove(); - } - } - /** * @inheritDoc */ diff --git a/tests/decouplededitor.js b/tests/decouplededitor.js index b96d651..7f2450b 100644 --- a/tests/decouplededitor.js +++ b/tests/decouplededitor.js @@ -3,6 +3,8 @@ * For licensing, see LICENSE.md. */ +/* globals document */ + import DecoupledEditorUI from '../src/decouplededitorui'; import DecoupledEditorUIView from '../src/decouplededitoruiview'; @@ -19,16 +21,19 @@ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; testUtils.createSinonSandbox(); -describe( 'DecoupledEditor', () => { - let editor, editorData; +const editorData = 'foo bar
'; - beforeEach( () => { - editorData = 'foo bar
'; - } ); +describe( 'DecoupledEditor', () => { + let editor; describe( 'constructor()', () => { beforeEach( () => { editor = new DecoupledEditor(); + editor.ui.init(); + } ); + + afterEach( () => { + return editor.destroy(); } ); it( 'uses HTMLDataProcessor', () => { @@ -52,173 +57,214 @@ describe( 'DecoupledEditor', () => { } ); describe( 'create()', () => { - beforeEach( () => { - return DecoupledEditor - .create( editorData, { - plugins: [ Paragraph, Bold ] - } ) - .then( newEditor => { - editor = newEditor; - } ); + describe( 'editor with data', () => { + test( () => editorData ); } ); - afterEach( () => { - return editor.destroy(); - } ); + describe( 'editor with editable element', () => { + let editableElement; - it( 'creates an instance which inherits from the DecoupledEditor', () => { - expect( editor ).to.be.instanceof( DecoupledEditor ); - } ); + beforeEach( () => { + editableElement = document.createElement( 'div' ); + editableElement.innerHTML = editorData; + } ); - it( 'loads the initial data', () => { - expect( editor.getData() ).to.equal( 'foo bar
' ); + test( () => editableElement ); } ); - // #53 - it( 'creates an instance of a DecoupledEditor child class', () => { - class CustomDecoupledEditor extends DecoupledEditor {} - - return CustomDecoupledEditor - .create( editorData, { - plugins: [ Paragraph, Bold ] - } ) - .then( newEditor => { - expect( newEditor ).to.be.instanceof( CustomDecoupledEditor ); - expect( newEditor ).to.be.instanceof( DecoupledEditor ); + function test( getElementOrData ) { + it( 'creates an instance which inherits from the DecoupledEditor', () => { + return DecoupledEditor + .create( getElementOrData(), { + plugins: [ Paragraph, Bold ] + } ) + .then( newEditor => { + expect( newEditor ).to.be.instanceof( DecoupledEditor ); - expect( newEditor.getData() ).to.equal( 'foo bar
' ); - - return newEditor.destroy(); - } ); - } ); - - // https://github.com/ckeditor/ckeditor5-editor-decoupled/issues/3 - it( 'initializes the data controller', () => { - let dataInitSpy; + return newEditor.destroy(); + } ); + } ); - class DataInitAssertPlugin extends Plugin { - constructor( editor ) { - super(); + it( 'loads the initial data', () => { + return DecoupledEditor + .create( getElementOrData(), { + plugins: [ Paragraph, Bold ] + } ) + .then( newEditor => { + expect( newEditor.getData() ).to.equal( 'foo bar
' ); - this.editor = editor; - } + return newEditor.destroy(); + } ); + } ); - init() { - dataInitSpy = sinon.spy( this.editor.data, 'init' ); - } - } + // https://github.com/ckeditor/ckeditor5-editor-classic/issues/53 + it( 'creates an instance of a DecoupledEditor child class', () => { + class CustomDecoupledEditor extends DecoupledEditor {} - return DecoupledEditor - .create( editorData, { - plugins: [ Paragraph, Bold, DataInitAssertPlugin ] - } ) - .then( newEditor => { - sinon.assert.calledOnce( dataInitSpy ); + return CustomDecoupledEditor + .create( getElementOrData(), { + plugins: [ Paragraph, Bold ] + } ) + .then( newEditor => { + expect( newEditor ).to.be.instanceof( CustomDecoupledEditor ); + expect( newEditor ).to.be.instanceof( DecoupledEditor ); - return newEditor.destroy(); - } ); - } ); + expect( newEditor.getData() ).to.equal( 'foo bar
' ); - describe( 'ui', () => { - it( 'attaches editable UI as view\'s DOM root', () => { - expect( editor.editing.view.getDomRoot() ).to.equal( editor.ui.view.editable.element ); + return newEditor.destroy(); + } ); } ); - } ); - } ); - describe( 'create - events', () => { - afterEach( () => { - return editor.destroy(); - } ); + // https://github.com/ckeditor/ckeditor5-editor-decoupled/issues/3 + it( 'initializes the data controller', () => { + let dataInitSpy; - it( 'fires all events in the right order', () => { - const fired = []; + class DataInitAssertPlugin extends Plugin { + constructor( editor ) { + super(); - function spy( evt ) { - fired.push( evt.name ); - } + this.editor = editor; + } - class EventWatcher extends Plugin { - init() { - this.editor.on( 'pluginsReady', spy ); - this.editor.on( 'uiReady', spy ); - this.editor.on( 'dataReady', spy ); - this.editor.on( 'ready', spy ); + init() { + dataInitSpy = sinon.spy( this.editor.data, 'init' ); + } } - } - return DecoupledEditor - .create( editorData, { - plugins: [ EventWatcher ] - } ) - .then( newEditor => { - expect( fired ).to.deep.equal( [ 'pluginsReady', 'uiReady', 'dataReady', 'ready' ] ); + return DecoupledEditor + .create( getElementOrData(), { + plugins: [ Paragraph, Bold, DataInitAssertPlugin ] + } ) + .then( newEditor => { + sinon.assert.calledOnce( dataInitSpy ); + + return newEditor.destroy(); + } ); + } ); - editor = newEditor; + describe( 'events', () => { + it( 'fires all events in the right order', () => { + const fired = []; + + function spy( evt ) { + fired.push( evt.name ); + } + + class EventWatcher extends Plugin { + init() { + this.editor.on( 'pluginsReady', spy ); + this.editor.on( 'uiReady', spy ); + this.editor.on( 'dataReady', spy ); + this.editor.on( 'ready', spy ); + } + } + + return DecoupledEditor + .create( getElementOrData(), { + plugins: [ EventWatcher ] + } ) + .then( newEditor => { + expect( fired ).to.deep.equal( [ 'pluginsReady', 'uiReady', 'dataReady', 'ready' ] ); + + return newEditor.destroy(); + } ); + } ); + + it( 'fires dataReady once data is loaded', () => { + let data; + + class EventWatcher extends Plugin { + init() { + this.editor.on( 'dataReady', () => { + data = this.editor.getData(); + } ); + } + } + + return DecoupledEditor + .create( getElementOrData(), { + plugins: [ EventWatcher, Paragraph, Bold ] + } ) + .then( newEditor => { + expect( data ).to.equal( 'foo bar
' ); + + return newEditor.destroy(); + } ); } ); - } ); - it( 'fires dataReady once data is loaded', () => { - let data; + it( 'fires uiReady once UI is rendered', () => { + let isReady; + + class EventWatcher extends Plugin { + init() { + this.editor.on( 'uiReady', () => { + isReady = this.editor.ui.view.isRendered; + } ); + } + } + + return DecoupledEditor + .create( getElementOrData(), { + plugins: [ EventWatcher ] + } ) + .then( newEditor => { + expect( isReady ).to.be.true; + + return newEditor.destroy(); + } ); + } ); + } ); + } + } ); - class EventWatcher extends Plugin { - init() { - this.editor.on( 'dataReady', () => { - data = this.editor.getData(); + describe( 'destroy', () => { + describe( 'editor with data', () => { + beforeEach( function() { + return DecoupledEditor + .create( editorData, { plugins: [ Paragraph ] } ) + .then( newEditor => { + editor = newEditor; } ); - } - } - - return DecoupledEditor - .create( editorData, { - plugins: [ EventWatcher, Paragraph, Bold ] - } ) - .then( newEditor => { - expect( data ).to.equal( 'foo bar
' ); + } ); - editor = newEditor; - } ); + test( () => editorData ); } ); - it( 'fires uiReady once UI is rendered', () => { - let isReady; + describe( 'editor with editable element', () => { + let editableElement; + + beforeEach( function() { + editableElement = document.createElement( 'div' ); + editableElement.innerHTML = editorData; - class EventWatcher extends Plugin { - init() { - this.editor.on( 'uiReady', () => { - isReady = this.editor.ui.view.isRendered; + return DecoupledEditor + .create( editableElement, { plugins: [ Paragraph ] } ) + .then( newEditor => { + editor = newEditor; } ); - } - } + } ); - return DecoupledEditor - .create( editorData, { - plugins: [ EventWatcher ] - } ) - .then( newEditor => { - expect( isReady ).to.be.true; + it( 'sets data back to the element', () => { + editor.setData( 'foo
' ); - editor = newEditor; - } ); - } ); - } ); + return editor.destroy() + .then( () => { + expect( editableElement.innerHTML ).to.equal( 'foo
' ); + } ); + } ); - describe( 'destroy', () => { - beforeEach( function() { - return DecoupledEditor - .create( editorData, { plugins: [ Paragraph ] } ) - .then( newEditor => { - editor = newEditor; - } ); + test( () => editableElement ); } ); - it( 'destroys the UI', () => { - const spy = sinon.spy( editor.ui, 'destroy' ); + function test() { + it( 'destroys the UI', () => { + const spy = sinon.spy( editor.ui, 'destroy' ); - return editor.destroy() - .then( () => { - sinon.assert.calledOnce( spy ); - } ); - } ); + return editor.destroy() + .then( () => { + sinon.assert.calledOnce( spy ); + } ); + } ); + } } ); } ); diff --git a/tests/decouplededitorui.js b/tests/decouplededitorui.js index 907a2fc..943477b 100644 --- a/tests/decouplededitorui.js +++ b/tests/decouplededitorui.js @@ -63,45 +63,6 @@ describe( 'DecoupledEditorUI', () => { expect( view.isRendered ).to.be.true; } ); - describe( 'config', () => { - it( 'does nothing if not specified', () => { - expect( view.toolbar.element.parentElement ).to.be.null; - expect( view.editable.element.parentElement ).to.be.null; - } ); - - it( 'allocates view#toolbar', () => { - return VirtualDecoupledTestEditor - .create( { - toolbar: [ 'foo', 'bar' ], - toolbarContainer: document.body - } ) - .then( newEditor => { - expect( newEditor.ui.view.toolbar.element.parentElement ).to.equal( document.body ); - - return newEditor; - } ) - .then( newEditor => { - newEditor.destroy(); - } ); - } ); - - it( 'allocates view#editable', () => { - return VirtualDecoupledTestEditor - .create( { - toolbar: [ 'foo', 'bar' ], - editableContainer: document.body - } ) - .then( newEditor => { - expect( newEditor.ui.view.editable.element.parentElement ).to.equal( document.body ); - - return newEditor; - } ) - .then( newEditor => { - newEditor.destroy(); - } ); - } ); - } ); - describe( 'editable', () => { it( 'registers view.editable#element in editor focus tracker', () => { ui.focusTracker.isFocused = false; @@ -139,6 +100,10 @@ describe( 'DecoupledEditorUI', () => { { isReadOnly: true } ); } ); + + it( 'attaches editable UI as view\'s DOM root', () => { + expect( editor.editing.view.getDomRoot() ).to.equal( view.editable.element ); + } ); } ); describe( 'view.toolbar#items', () => { @@ -207,43 +172,7 @@ describe( 'DecoupledEditorUI', () => { return editor.destroy() .then( () => { sinon.assert.calledOnce( spy ); - sinon.assert.calledWithExactly( spy, false, false ); - } ); - } ); - - it( 'removes view#toolbar from DOM, if config.toolbarContainer is specified', () => { - let spy; - - return VirtualDecoupledTestEditor - .create( { - toolbar: [ 'foo', 'bar' ], - toolbarContainer: document.body - } ) - .then( newEditor => { - spy = sinon.spy( newEditor.ui.view, 'destroy' ); - - newEditor.destroy(); - } ) - .then( () => { - sinon.assert.calledWithExactly( spy, true, false ); - } ); - } ); - - it( 'removes view#editable from DOM, if config.editableContainer is specified', () => { - let spy; - - return VirtualDecoupledTestEditor - .create( { - toolbar: [ 'foo', 'bar' ], - editableContainer: document.body - } ) - .then( newEditor => { - spy = sinon.spy( newEditor.ui.view, 'destroy' ); - - newEditor.destroy(); - } ) - .then( () => { - sinon.assert.calledWithExactly( spy, false, true ); + sinon.assert.calledWithExactly( spy ); } ); } ); } ); diff --git a/tests/decouplededitoruiview.js b/tests/decouplededitoruiview.js index bfbcf07..68b234d 100644 --- a/tests/decouplededitoruiview.js +++ b/tests/decouplededitoruiview.js @@ -43,8 +43,9 @@ describe( 'DecoupledEditorUIView', () => { expect( view.toolbar.element.parentElement ).to.be.null; } ); - it( 'gets the .ck-reset_all class', () => { + it( 'gets the CSS classes', () => { expect( view.toolbar.element.classList.contains( 'ck-reset_all' ) ).to.be.true; + expect( view.toolbar.element.classList.contains( 'ck-rounded-corners' ) ).to.be.true; } ); } ); @@ -53,7 +54,7 @@ describe( 'DecoupledEditorUIView', () => { expect( view.editable ).to.be.instanceof( InlineEditableUIView ); } ); - it( 'is given a locate object', () => { + it( 'is given a locale object', () => { expect( view.editable.locale ).to.equal( locale ); } ); @@ -61,6 +62,17 @@ describe( 'DecoupledEditorUIView', () => { expect( view.isRendered ).to.be.true; expect( view.editable.element.parentElement ).to.be.null; } ); + + it( 'can be created out of an existing DOM element', () => { + const editableElement = document.createElement( 'div' ); + const testView = new DecoupledEditorUIView( locale, editableElement ); + + testView.render(); + + expect( testView.editable.element ).to.equal( editableElement ); + + testView.destroy(); + } ); } ); } ); @@ -87,30 +99,6 @@ describe( 'DecoupledEditorUIView', () => { view.toolbar.element.remove(); view.editable.element.remove(); } ); - - it( 'removes toolbar#element on demand', () => { - document.body.appendChild( view.toolbar.element ); - document.body.appendChild( view.editable.element ); - - view.destroy( true ); - - expect( view.toolbar.element.parentElement ).to.be.null; - expect( view.editable.element.parentElement ).to.equal( document.body ); - - view.editable.element.remove(); - } ); - - it( 'removes editable#element on demand', () => { - document.body.appendChild( view.toolbar.element ); - document.body.appendChild( view.editable.element ); - - view.destroy( false, true ); - - expect( view.toolbar.element.parentElement ).to.equal( document.body ); - expect( view.editable.element.parentElement ).to.be.null; - - view.toolbar.element.remove(); - } ); } ); describe( 'editableElement', () => { diff --git a/tests/manual/decouplededitor-editable.html b/tests/manual/decouplededitor-editable.html new file mode 100644 index 0000000..25498f3 --- /dev/null +++ b/tests/manual/decouplededitor-editable.html @@ -0,0 +1,58 @@ ++ + +
+ +It has the initial editor data. It should keep it after the editor is destroyed too.
+