From 7e084babad91b22295eab08d994e77a009d4aa10 Mon Sep 17 00:00:00 2001 From: Alex Eckermann Date: Fri, 6 Jul 2018 13:57:40 +0930 Subject: [PATCH 1/4] Fixes #5. Changed implementation to use the editors editable UI view, raises log messages if it cant support the given editor. --- README.md | 9 +- package.json | 8 +- src/emptyness.js | 13 +- tests/_utils/incompatibletesteditor.js | 26 +++ tests/_utils/incompatibletesteditoruiview.js | 16 ++ tests/emptyness.js | 192 ++++++++++++++----- tests/manual/emptyness.html | 1 - 7 files changed, 210 insertions(+), 55 deletions(-) create mode 100644 tests/_utils/incompatibletesteditor.js create mode 100644 tests/_utils/incompatibletesteditoruiview.js diff --git a/README.md b/README.md index 0ed435c..425fa44 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ MyEditor.build = { ``` ```css -.ck-editor__is-empty .ck-content.ck-editor__editable::before, .ck-editor__is-empty.ck-content.ck-editor__editable::before { content: 'The editor is empty'; position: absolute; @@ -46,12 +45,14 @@ MyEditor.build = { } ``` -**_Whats with the weird CSS scope?_** - -Depending on the editor, the element used for content may not be the same as the one used when initialising the editor. Editors with toolbars will generate a new element structure. To cover all bases the scope is what it is -- where the `ck-editor__is-empty` class name is on the same element as the `ck-content` element or on a parent element. +The empty class will be applied to the editable view for the editor, this may be different to a container element that particular editors may create and use. ## Changelog +### v0.2.1 - 6 July 2018 + +- Fix [alexeckermann/ckeditor5-emptyness#5](https://github.com/alexeckermann/ckeditor5-emptyness/issues/5): The plugin tested against a editor which performs differently to the non-test editor. This lead to an incorrect asumption about what view to use for the empty CSS class. As a result the plugin will now detect if it is integrating with an editor it supports, logs error and warning messages if needed. This also simplifies the CSS targeting to just the content editable element. + ### v0.2.0 - 4 July 2018 - Updating event listener to use `change:data` event on the editors document. diff --git a/package.json b/package.json index b9bda6d..86d4f3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ckeditor5-emptyness", - "version": "0.2.0", + "version": "0.2.1", "description": "How empty is your CKEditor instance? Adds observable isEmpty property to editors.", "keywords": [ "ckeditor5", @@ -8,13 +8,13 @@ ], "dependencies": { "@ckeditor/ckeditor5-core": "^10.1.0", - "@ckeditor/ckeditor5-engine": "^10.1.0" + "@ckeditor/ckeditor5-engine": "^10.1.0", + "@ckeditor/ckeditor5-utils": "^10.1.0" }, "devDependencies": { "@ckeditor/ckeditor5-editor-classic": "^10.0.1", "@ckeditor/ckeditor5-essentials": "^10.1.0", - "@ckeditor/ckeditor5-paragraph": "^10.1.0", - "@ckeditor/ckeditor5-utils": "^10.1.0", + "@ckeditor/ckeditor5-paragraph": "^10.0.1", "eslint": "^4.15.0", "eslint-config-ckeditor5": "^1.0.7", "husky": "^0.14.3", diff --git a/src/emptyness.js b/src/emptyness.js index f3c00b9..15d2222 100644 --- a/src/emptyness.js +++ b/src/emptyness.js @@ -1,5 +1,6 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import Template from '@ckeditor/ckeditor5-ui/src/template'; +import log from '@ckeditor/ckeditor5-utils/src/log'; export default class Emptyness extends Plugin { @@ -10,7 +11,12 @@ export default class Emptyness extends Plugin { init() { const editor = this.editor; const doc = editor.model.document; - const view = editor.ui.view; + const view = editor.ui.view.editable; + + if ( !view ) { + log.error( 'emptyness-view-not-found: expected editable view not found for this editor (expects editor.ui.view.editable to be defined)' ); + return; + } editor.set( 'isEmpty', !documentHasContent(doc) ); @@ -18,6 +24,11 @@ export default class Emptyness extends Plugin { editor.set( 'isEmpty', !documentHasContent(doc) ); } ); + if ( view.isRendered === true ) { + log.warn( 'emptyness-view-is-rendered: can not extend the editor UI view after its been rendered, class name will be unavailable (see: template-extend-render)' ); + return; + } + const bind = Template.bind( editor, editor ); view.extendTemplate( { diff --git a/tests/_utils/incompatibletesteditor.js b/tests/_utils/incompatibletesteditor.js new file mode 100644 index 0000000..95afcb2 --- /dev/null +++ b/tests/_utils/incompatibletesteditor.js @@ -0,0 +1,26 @@ +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui'; +import IncompatibleTestEditorUIVIew from './incompatibletesteditoruiview'; + +export default class IncompatibleTestEditor extends ClassicTestEditor { + /** + * @inheritDoc + */ + constructor( element, config ) { + super( element, config ); + + if ( config._test.missingEditable === true ) { + // Intentially remove the editable property to trip up the plugin. + this.ui = new EditorUI( this, new IncompatibleTestEditorUIVIew( this.locale ) ); + this.ui.view.editable = null; + } + + if ( config._test.renderView === true ) { + // Render the editable view so that its template can not be extended + this.ui.view.editable.render(); + } + + + } + +} \ No newline at end of file diff --git a/tests/_utils/incompatibletesteditoruiview.js b/tests/_utils/incompatibletesteditoruiview.js new file mode 100644 index 0000000..d238ae4 --- /dev/null +++ b/tests/_utils/incompatibletesteditoruiview.js @@ -0,0 +1,16 @@ +import BoxedEditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/boxed/boxededitoruiview'; +import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview'; + +export default class IncompatibleTestEditorUIView extends BoxedEditorUIView { + + render() { + + this.editable = new InlineEditableUIView( this.locale ); + + super.render(); + + } + + + +} \ No newline at end of file diff --git a/tests/emptyness.js b/tests/emptyness.js index 9b787f6..b10334c 100644 --- a/tests/emptyness.js +++ b/tests/emptyness.js @@ -4,10 +4,13 @@ import ModelText from '@ckeditor/ckeditor5-engine/src/model/text'; import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range'; import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import IncompatibleTestEditor from './_utils/incompatibletesteditor'; + import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import log from '@ckeditor/ckeditor5-utils/src/log'; testUtils.createSinonSandbox(); @@ -32,79 +35,178 @@ describe( 'Emptyness', () => { }; - beforeEach( () => { - editorElement = document.createElement( 'div' ); - document.body.appendChild( editorElement ); - - return ClassicTestEditor - .create( editorElement, { - plugins: [ Emptyness, Paragraph ] - } ) - .then( newEditor => { - editor = newEditor; - } ); - } ); + describe( 'with a compliant editor view structure', () => { + + beforeEach( () => { + editorElement = document.createElement( 'div' ); + document.body.appendChild( editorElement ); + + return ClassicTestEditor + .create( editorElement, { + plugins: [ Emptyness, Paragraph ] + } ) + .then( newEditor => { + editor = newEditor; + } ); + } ); - afterEach( () => { - editorElement.remove(); + afterEach( () => { + editorElement.remove(); - return editor.destroy(); - } ); + return editor.destroy(); + } ); - it( 'should be loaded', () => { - expect( editor.plugins.get( Emptyness ) ).to.be.instanceOf( Emptyness ); - } ); + it( 'should be loaded', () => { + expect( editor.plugins.get( Emptyness ) ).to.be.instanceOf( Emptyness ); + } ); - describe( 'init()', () => { + describe( 'init()', () => { - it( 'should set the isEmpty property', () => { - expect( editor.isEmpty ).to.be.true; - } ); + it( 'should set the isEmpty property', () => { + expect( editor.isEmpty ).to.be.true; + } ); - } ); + } ); - describe( 'isEmpty lifecycle', () => { + describe( 'isEmpty lifecycle', () => { - it( 'should be false when content is inserted', () => { - const setSpy = testUtils.sinon.spy( editor, 'set' ); + it( 'should be false when content is inserted', () => { + const setSpy = testUtils.sinon.spy( editor, 'set' ); - insertContent(); + insertContent(); - sinon.assert.calledWithExactly( setSpy, 'isEmpty', false ); + sinon.assert.calledWithExactly( setSpy, 'isEmpty', false ); + } ); + + it( 'should be true when all content is emptied', () => { + setData( editor.model, 'test{}' ); + + const setSpy = testUtils.sinon.spy( editor, 'set' ); + + removeAllContent(); + + sinon.assert.calledWithExactly( setSpy, 'isEmpty', true ); + } ); + } ); + + describe( 'view class lifecycle', () => { - it( 'should be true when all content is emptied', () => { - setData( editor.model, 'test{}' ); + it( 'should not have the empty class content is inserted', () => { + let element = editor.ui.view.editable.editableElement; - const setSpy = testUtils.sinon.spy( editor, 'set' ); + expect( element.classList.contains('ck-editor__is-empty') ).to.be.true; - removeAllContent(); + insertContent(); - sinon.assert.calledWithExactly( setSpy, 'isEmpty', true ); + expect( element.classList.contains('ck-editor__is-empty') ).to.be.false; + } ); + + it( 'should add the empty class when all content is emptied', () => { + setData( editor.model, 'test{}' ); + + let element = editor.ui.view.editable.editableElement; + + expect( element.classList.contains('ck-editor__is-empty') ).to.be.false; + + removeAllContent(); + + expect( element.classList.contains('ck-editor__is-empty') ).to.be.true; + } ); + } ); } ); - describe( 'view class lifecycle', () => { + describe( 'with an incompatible editor', () => { - it( 'should not have the empty class content is inserted', () => { - expect( editor.ui.view.element.classList.contains('ck-editor__is-empty') ).to.be.true; - - insertContent(); + let errorSpy; + + beforeEach( () => { + editorElement = document.createElement( 'div' ); + document.body.appendChild( editorElement ); - expect( editor.ui.view.element.classList.contains('ck-editor__is-empty') ).to.be.false; + errorSpy = testUtils.sinon.stub( log, 'error' ); + + return IncompatibleTestEditor + .create( editorElement, { + plugins: [ Emptyness, Paragraph ], + _test: { missingEditable: true } + } ) + .then( newEditor => { + editor = newEditor; + } ); + } ); + + afterEach( () => { + editorElement.remove(); + + return editor.destroy(); + } ); + + it( 'should be loaded', () => { + expect( editor.plugins.get( Emptyness ) ).to.be.instanceOf( Emptyness ); } ); + + describe( 'init()', () => { - it( 'should add the empty class when all content is emptied', () => { - setData( editor.model, 'test{}' ); + it( 'should not set the isEmpty property', () => { + expect( editor.isEmpty ).to.be.undefined; + } ); - expect( editor.ui.view.element.classList.contains('ck-editor__is-empty') ).to.be.false; + it( 'should log an error', () => { + sinon.assert.calledOnce( errorSpy ); + sinon.assert.calledWithMatch( errorSpy, /^emptyness-view-not-found/ ); + } ); + + } ); + + } ); + + describe( 'with an editor view that is rendered', () => { + + let warnSpy; + + beforeEach( () => { + editorElement = document.createElement( 'div' ); + document.body.appendChild( editorElement ); - removeAllContent(); + warnSpy = testUtils.sinon.stub( log, 'warn' ); + + return IncompatibleTestEditor + .create( editorElement, { + plugins: [ Emptyness, Paragraph ], + _test: { renderView: true } + } ) + .then( newEditor => { + editor = newEditor; + } ); + } ); + + afterEach( () => { + editorElement.remove(); + + return editor.destroy(); + } ); + + it( 'should be loaded', () => { + expect( editor.plugins.get( Emptyness ) ).to.be.instanceOf( Emptyness ); + } ); + + describe( 'init()', () => { + + it( 'should set the isEmpty property', () => { + expect( editor.isEmpty ).not.to.be.undefined; + } ); - expect( editor.ui.view.element.classList.contains('ck-editor__is-empty') ).to.be.true; + it( 'should log a warning', () => { + sinon.assert.calledOnce( warnSpy ); + sinon.assert.calledWithMatch( warnSpy, /^emptyness-view-is-rendered/ ); + } ); + } ); } ); + } ); \ No newline at end of file diff --git a/tests/manual/emptyness.html b/tests/manual/emptyness.html index 8db292e..c1b3b33 100644 --- a/tests/manual/emptyness.html +++ b/tests/manual/emptyness.html @@ -1,6 +1,5 @@ + + +
+
\ No newline at end of file diff --git a/tests/manual/emptyness-balloon.js b/tests/manual/emptyness-balloon.js new file mode 100644 index 0000000..b1f76dd --- /dev/null +++ b/tests/manual/emptyness-balloon.js @@ -0,0 +1,16 @@ +import BalloonEditor from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor'; +import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; + +import Template from '@ckeditor/ckeditor5-ui/src/template'; +import Emptyness from '../../src/emptyness'; + +BalloonEditor + .create( document.querySelector( '#editor' ), { + plugins: [ Essentials, Paragraph, Bold, Emptyness ], + toolbar: [ 'bold' ] + } ) + .then( editor => { + window.editor = editor; + } ); \ No newline at end of file diff --git a/tests/manual/emptyness-balloon.md b/tests/manual/emptyness-balloon.md new file mode 100644 index 0000000..35dd76a --- /dev/null +++ b/tests/manual/emptyness-balloon.md @@ -0,0 +1,5 @@ +1. Upon loading the page the editor should contain the placeholder text: `The editor is empty`. + +2. Type something and notice that the placeholder text disappears as expected. + +3. Remove all contentent in the editor and see the placeholder reappear. \ No newline at end of file From 7b7ba8fa77fc0495818f6b3708775f3549ad5e32 Mon Sep 17 00:00:00 2001 From: Alex Eckermann Date: Thu, 11 Oct 2018 13:42:00 +1030 Subject: [PATCH 3/4] Updating package.json for CK5 v11.1 compatible dependencies --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index a5ba39c..5ed19e8 100644 --- a/package.json +++ b/package.json @@ -7,15 +7,15 @@ "ckeditor5-plugin" ], "dependencies": { - "@ckeditor/ckeditor5-core": "^10.1.0", - "@ckeditor/ckeditor5-engine": "^10.1.0", - "@ckeditor/ckeditor5-utils": "^10.1.0" + "@ckeditor/ckeditor5-core": "^11.0.1", + "@ckeditor/ckeditor5-engine": "^11.0.0", + "@ckeditor/ckeditor5-utils": "^11.0.0" }, "devDependencies": { - "@ckeditor/ckeditor5-editor-classic": "^10.0.1", - "@ckeditor/ckeditor5-essentials": "^10.1.0", - "@ckeditor/ckeditor5-paragraph": "^10.0.1", - "eslint": "^4.15.0", + "@ckeditor/ckeditor5-editor-classic": "^11.0.1", + "@ckeditor/ckeditor5-essentials": "^10.1.2", + "@ckeditor/ckeditor5-paragraph": "^10.0.3", + "eslint": "^5.5.0", "eslint-config-ckeditor5": "^1.0.7", "husky": "^0.14.3", "lint-staged": "^7.0.0" From 9e35479171bfb7d7f2f9ae3799eaea72017208a0 Mon Sep 17 00:00:00 2001 From: Alex Eckermann Date: Thu, 11 Oct 2018 13:43:46 +1030 Subject: [PATCH 4/4] Updating README changelog --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 425fa44..6159286 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,10 @@ The empty class will be applied to the editable view for the editor, this may be ## Changelog -### v0.2.1 - 6 July 2018 +### v0.2.1 - 11 October 2018 - Fix [alexeckermann/ckeditor5-emptyness#5](https://github.com/alexeckermann/ckeditor5-emptyness/issues/5): The plugin tested against a editor which performs differently to the non-test editor. This lead to an incorrect asumption about what view to use for the empty CSS class. As a result the plugin will now detect if it is integrating with an editor it supports, logs error and warning messages if needed. This also simplifies the CSS targeting to just the content editable element. +- Updated dependencies to target CK5 v11.1 compatible dependencies. ### v0.2.0 - 4 July 2018