diff --git a/package.json b/package.json index dff5971..f7e7336 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,16 @@ "description": "Inline editor for CKEditor 5.", "keywords": [], "dependencies": { + "@ckeditor/ckeditor5-core": "*", + "@ckeditor/ckeditor5-engine": "*", + "@ckeditor/ckeditor5-ui": "*", + "@ckeditor/ckeditor5-utils": "*" }, "devDependencies": { + "@ckeditor/ckeditor5-basic-styles": "*", "@ckeditor/ckeditor5-dev-lint": "^2.0.0", + "@ckeditor/ckeditor5-paragraph": "*", + "@ckeditor/ckeditor5-presets": "*", "gulp": "^3.9.0", "guppy-pre-commit": "^0.4.0" }, diff --git a/src/inline.js b/src/inline.js new file mode 100644 index 0000000..a881045 --- /dev/null +++ b/src/inline.js @@ -0,0 +1,87 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module editor-inline/inline + */ + +import StandardEditor from '@ckeditor/ckeditor5-core/src/editor/standardeditor'; +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; +import InlineEditorUI from './inlineeditorui'; +import InlineEditorUIView from './inlineeditoruiview'; + +import '../theme/theme.scss'; + +/** + * Inline editor. Uses an inline editable and a floating toolbar. + * + * @extends module:core/editor/standardeditor~StandardEditor + */ +export default class InlineEditor extends StandardEditor { + /** + * Creates an instance of the inline editor. + * + * @param {HTMLElement} element The DOM element that will be the source for the created editor. + * @param {Object} config The editor configuration. + */ + constructor( element, config ) { + super( element, config ); + + this.document.createRoot(); + this.data.processor = new HtmlDataProcessor(); + this.ui = new InlineEditorUI( this, new InlineEditorUIView( this.locale, element ) ); + } + + /** + * Destroys the editor instance, releasing all resources used by it. + * + * Updates the original editor element with the data. + * + * @returns {Promise} + */ + destroy() { + this.updateEditorElement(); + + return this.ui.destroy() + .then( () => super.destroy() ); + } + + /** + * Creates an inline editor instance. + * + * InlineEditor.create( document.querySelector( '#editor' ), { + * plugins: [ Delete, Enter, Typing, Paragraph, Undo, Bold, Italic ], + * toolbar: [ 'bold', 'italic', 'undo', 'redo' ] + * } ) + * .then( editor => { + * console.log( 'Editor was initialized', editor ); + * } ) + * .catch( err => { + * console.error( err.stack ); + * } ); + * + * @param {HTMLElement} element See {@link module:editor-inline/inline~InlineEditor#constructor}'s parameters. + * @param {Object} config See {@link module:editor-inline/inline~InlineEditor#constructor}'s parameters. + * @returns {Promise} A promise resolved once the editor is ready. + * @returns {module:core/editor/standardeditor~StandardEditor} return.editor The editor instance. + */ + static create( element, config ) { + return new Promise( ( resolve ) => { + const editor = new InlineEditor( element, config ); + + resolve( + editor.initPlugins() + .then( () => editor.ui.init() ) + .then( () => editor.fire( 'uiReady' ) ) + .then( () => editor.loadDataFromEditorElement() ) + .then( () => { + editor.fire( 'dataReady' ); + editor.fire( 'ready' ); + } ) + .then( () => editor ) + ); + } ); + } +} diff --git a/src/inlineeditorui.js b/src/inlineeditorui.js new file mode 100644 index 0000000..ec2d40a --- /dev/null +++ b/src/inlineeditorui.js @@ -0,0 +1,93 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module editor-inline/inlineeditorui + */ + +import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory'; +import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; +import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus'; + +/** + * The inline editor UI class. + * + * @implements module:core/editor/editorui~EditorUI + */ +export default class InlineEditorUI { + /** + * Creates an instance of the editor UI class. + * + * @param {module:core/editor/editor~Editor} editor The editor instance. + * @param {module:ui/editorui/editoruiview~EditorUIView} view View of the ui. + */ + constructor( editor, view ) { + /** + * @inheritDoc + */ + this.editor = editor; + + /** + * @inheritDoc + */ + this.view = view; + + /** + * @inheritDoc + */ + this.componentFactory = new ComponentFactory( editor ); + + /** + * @inheritDoc + */ + this.focusTracker = new FocusTracker(); + + // Set–up the view#panel. + view.panel.bind( 'isActive' ).to( this.focusTracker, 'isFocused' ); + view.panel.targetElement = view.editableElement; + + // Setup the editable. + const editingRoot = editor.editing.createRoot( view.editableElement ); + view.editable.bind( 'isReadOnly' ).to( editingRoot ); + + // Bind to focusTracker instead of editor.editing.view because otherwise + // focused editable styles disappear when view#toolbar is focused. + view.editable.bind( 'isFocused' ).to( this.focusTracker ); + view.editable.name = editingRoot.rootName; + + this.focusTracker.add( view.editableElement ); + } + + /** + * Initializes the UI. + * + * @returns {Promise} A Promise resolved when the initialization process is finished. + */ + init() { + const editor = this.editor; + + return this.view.init() + .then( () => { + return this.view.toolbar.fillFromConfig( editor.config.get( 'toolbar' ), this.componentFactory ); + } ) + .then( () => { + enableToolbarKeyboardFocus( { + origin: editor.editing.view, + originFocusTracker: this.focusTracker, + originKeystrokeHandler: editor.keystrokes, + toolbar: this.view.toolbar + } ); + } ); + } + + /** + * Destroys the UI. + * + * @returns {Promise} A Promise resolved when the destruction process is finished. + */ + destroy() { + return this.view.destroy(); + } +} diff --git a/src/inlineeditoruiview.js b/src/inlineeditoruiview.js new file mode 100644 index 0000000..cbabade --- /dev/null +++ b/src/inlineeditoruiview.js @@ -0,0 +1,78 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module editor-inline/inlineeditoruiview + */ + +import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview'; +import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview'; +import FloatingPanelView from '@ckeditor/ckeditor5-ui/src/panel/floating/floatingpanelview'; +import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; +import Template from '@ckeditor/ckeditor5-ui/src/template'; + +/** + * Inline editor UI view. Uses inline editable and floating toolbar. + * + * @extends module:ui/editorui/editoruiview~EditorUIView + */ +export default class InlineEditorUIView extends EditorUIView { + /** + * Creates an instance of the inline editor UI view. + * + * @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance. + */ + constructor( locale, editableElement ) { + super( locale ); + + /** + * A floating toolbar view instance. + * + * @readonly + * @member {module:ui/toolbar/toolbarview~ToolbarView} + */ + this.toolbar = new ToolbarView( locale ); + + /** + * A floating panel view instance. + * + * @readonly + * @member {module:ui/panel/floating/floatingpanelview~FloatingPanelView} + */ + this.panel = new FloatingPanelView( locale ); + + Template.extend( this.panel.template, { + attributes: { + class: 'ck-toolbar__container' + } + } ); + + /** + * Editable UI view. + * + * @readonly + * @member {module:ui/editableui/inline/inlineeditableuiview~InlineEditableUIView} + */ + this.editable = new InlineEditableUIView( locale, editableElement ); + + this.body.add( this.panel ); + this.addChildren( this.editable ); + } + + /** + * @inheritDoc + */ + init() { + return super.init() + .then( () => this.panel.content.add( this.toolbar ) ); + } + + /** + * @inheritDoc + */ + get editableElement() { + return this.editable.element; + } +} diff --git a/tests/.jshintrc b/tests/.jshintrc new file mode 100644 index 0000000..912ffcd --- /dev/null +++ b/tests/.jshintrc @@ -0,0 +1,22 @@ +{ + "esnext": true, + "expr": true, + "immed": true, + "loopfunc": true, + "noarg": true, + "nonbsp": true, + "strict": "implied", + "undef": true, + "unused": true, + "varstmt": true, + "globals": { + "after": false, + "afterEach": false, + "before": false, + "beforeEach": false, + "describe": false, + "expect": false, + "it": false, + "sinon": false + } +} diff --git a/tests/inline.js b/tests/inline.js new file mode 100644 index 0000000..889b5e3 --- /dev/null +++ b/tests/inline.js @@ -0,0 +1,183 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document */ + +import InlineEditorUI from '../src/inlineeditorui'; +import InlineEditorUIView from '../src/inlineeditoruiview'; + +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; + +import InlineEditor from '../src/inline'; +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; + +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import count from '@ckeditor/ckeditor5-utils/src/count'; + +testUtils.createSinonSandbox(); + +describe( 'InlineEditor', () => { + let editor, editorElement; + + beforeEach( () => { + editorElement = document.createElement( 'div' ); + editorElement.innerHTML = '

foo bar

'; + + document.body.appendChild( editorElement ); + } ); + + afterEach( () => { + editorElement.remove(); + } ); + + describe( 'constructor()', () => { + beforeEach( () => { + editor = new InlineEditor( editorElement ); + } ); + + it( 'creates a single div editable root in the view', () => { + expect( editor.editing.view.getRoot() ).to.have.property( 'name', 'div' ); + } ); + + it( 'creates a single document root', () => { + expect( count( editor.document.getRootNames() ) ).to.equal( 1 ); + expect( editor.document.getRoot() ).to.have.property( 'name', '$root' ); + } ); + + it( 'creates the UI using BoxedEditorUI classes', () => { + expect( editor.ui ).to.be.instanceof( InlineEditorUI ); + expect( editor.ui.view ).to.be.instanceof( InlineEditorUIView ); + } ); + + it( 'uses HTMLDataProcessor', () => { + expect( editor.data.processor ).to.be.instanceof( HtmlDataProcessor ); + } ); + } ); + + describe( 'create()', () => { + beforeEach( function() { + return InlineEditor.create( editorElement, { + plugins: [ Paragraph, Bold ] + } ) + .then( newEditor => { + editor = newEditor; + } ); + } ); + + afterEach( () => { + return editor.destroy(); + } ); + + it( 'creates an instance which inherits from the InlineEditor', () => { + expect( editor ).to.be.instanceof( InlineEditor ); + } ); + + it( 'creates element–less UI view', () => { + expect( editor.ui.view.element ).to.be.null; + } ); + + it( 'attaches editable UI as view\'s DOM root', () => { + expect( editor.editing.view.getDomRoot() ).to.equal( editor.ui.view.editable.element ); + } ); + + it( 'loads data from the editor element', () => { + expect( editor.getData() ).to.equal( '

foo bar

' ); + } ); + } ); + + describe( 'create - events', () => { + afterEach( () => { + return editor.destroy(); + } ); + + 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 InlineEditor.create( editorElement, { + plugins: [ EventWatcher ] + } ) + .then( ( newEditor ) => { + expect( fired ).to.deep.equal( [ 'pluginsReady', 'uiReady', 'dataReady', 'ready' ] ); + + editor = newEditor; + } ); + } ); + + it( 'fires dataReady once data is loaded', () => { + let data; + + class EventWatcher extends Plugin { + init() { + this.editor.on( 'dataReady', () => { + data = this.editor.getData(); + } ); + } + } + + return InlineEditor.create( editorElement, { + plugins: [ EventWatcher, Paragraph, Bold ] + } ) + .then( ( newEditor ) => { + expect( data ).to.equal( '

foo bar

' ); + + editor = newEditor; + } ); + } ); + + it( 'fires uiReady once UI is ready', () => { + let isReady; + + class EventWatcher extends Plugin { + init() { + this.editor.on( 'uiReady', () => { + isReady = this.editor.ui.view.ready; + } ); + } + } + + return InlineEditor.create( editorElement, { + plugins: [ EventWatcher ] + } ) + .then( ( newEditor ) => { + expect( isReady ).to.be.true; + + editor = newEditor; + } ); + } ); + } ); + + describe( 'destroy', () => { + beforeEach( function() { + return InlineEditor.create( editorElement, { plugins: [ Paragraph ] } ) + .then( newEditor => { + editor = newEditor; + } ); + } ); + + it( 'sets the data back to the editor element', () => { + editor.setData( '

foo

' ); + + return editor.destroy() + .then( () => { + expect( editorElement.innerHTML ).to.equal( '

foo

' ); + } ); + } ); + } ); +} ); diff --git a/tests/inlineeditorui.js b/tests/inlineeditorui.js new file mode 100644 index 0000000..f8a6dae --- /dev/null +++ b/tests/inlineeditorui.js @@ -0,0 +1,189 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document, Event */ + +import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory'; +import View from '@ckeditor/ckeditor5-ui/src/view'; + +import InlineEditorUI from '../src/inlineeditorui'; +import InlineEditorUIView from '../src/inlineeditoruiview'; +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; + +import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; + +import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import utils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + +testUtils.createSinonSandbox(); + +describe( 'InlineEditorUI', () => { + let editorElement, editor, editable, view, ui; + + beforeEach( () => { + editorElement = document.createElement( 'div' ); + document.body.appendChild( editorElement ); + + editor = new ClassicTestEditor( editorElement, { + toolbar: [ 'foo', 'bar' ] + } ); + + view = new InlineEditorUIView( editor.locale ); + ui = new InlineEditorUI( editor, view ); + editable = editor.editing.view.getRoot(); + + ui.componentFactory.add( 'foo', viewCreator( 'foo' ) ); + ui.componentFactory.add( 'bar', viewCreator( 'bar' ) ); + } ); + + describe( 'constructor()', () => { + it( 'sets #editor', () => { + expect( ui.editor ).to.equal( editor ); + } ); + + it( 'sets #view', () => { + expect( ui.view ).to.equal( view ); + } ); + + it( 'creates #componentFactory factory', () => { + expect( ui.componentFactory ).to.be.instanceOf( ComponentFactory ); + } ); + + it( 'creates #focusTracker', () => { + expect( ui.focusTracker ).to.be.instanceOf( FocusTracker ); + } ); + + describe( 'panel', () => { + it( 'binds view.panel#isActive to editor.ui#focusTracker', () => { + ui.focusTracker.isFocused = false; + expect( view.panel.isActive ).to.be.false; + + ui.focusTracker.isFocused = true; + expect( view.panel.isActive ).to.be.true; + } ); + + it( 'sets view.panel#targetElement', () => { + expect( view.panel.targetElement ).to.equal( view.editableElement ); + } ); + } ); + + describe( 'editable', () => { + it( 'registers view.editable#element in editor focus tracker', () => { + ui.focusTracker.isFocused = false; + + view.editable.element.dispatchEvent( new Event( 'focus' ) ); + expect( ui.focusTracker.isFocused ).to.true; + } ); + + it( 'sets view.editable#name', () => { + expect( view.editable.name ).to.equal( editable.rootName ); + } ); + + it( 'binds view.editable#isFocused', () => { + utils.assertBinding( + view.editable, + { isFocused: false }, + [ + [ ui.focusTracker, { isFocused: true } ] + ], + { isFocused: true } + ); + } ); + + it( 'binds view.editable#isReadOnly', () => { + utils.assertBinding( + view.editable, + { isReadOnly: false }, + [ + [ editable, { isReadOnly: true } ] + ], + { isReadOnly: true } + ); + } ); + } ); + } ); + + describe( 'init()', () => { + afterEach( () => { + return ui.destroy(); + } ); + + it( 'returns a promise', () => { + const promise = ui.init().then( () => { + expect( promise ).to.be.instanceof( Promise ); + } ); + + return promise; + } ); + + it( 'initializes the #view', () => { + const spy = sinon.spy( view, 'init' ); + + return ui.init().then( () => { + sinon.assert.calledOnce( spy ); + } ); + } ); + + it( 'fills view.toolbar#items with editor config', () => { + const spy = testUtils.sinon.spy( view.toolbar, 'fillFromConfig' ); + + return ui.init().then( () => { + sinon.assert.calledWithExactly( spy, editor.config.get( 'toolbar' ), ui.componentFactory ); + } ); + } ); + + it( 'initializes keyboard navigation between view#toolbar and view#editable', () => { + const spy = testUtils.sinon.spy( view.toolbar, 'focus' ); + + return ui.init().then( () => { + ui.focusTracker.isFocused = true; + ui.view.toolbar.focusTracker.isFocused = false; + + editor.keystrokes.press( { + keyCode: keyCodes.f10, + altKey: true, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + } ); + + sinon.assert.calledOnce( spy ); + } ); + } ); + } ); + + describe( 'destroy()', () => { + it( 'returns a promise', () => { + return ui.init().then( () => { + const promise = ui.destroy().then( () => { + expect( promise ).to.be.instanceof( Promise ); + } ); + + return promise; + } ); + } ); + + it( 'destroys the #view', () => { + const spy = sinon.spy( view, 'destroy' ); + + return ui.init() + .then( () => ui.destroy() ) + .then( () => { + sinon.assert.calledOnce( spy ); + } ); + } ); + } ); +} ); + +function viewCreator( name ) { + return ( locale ) => { + const view = new View( locale ); + + view.name = name; + view.element = document.createElement( 'a' ); + + return view; + }; +} diff --git a/tests/inlineeditoruiview.js b/tests/inlineeditoruiview.js new file mode 100644 index 0000000..df4eeb9 --- /dev/null +++ b/tests/inlineeditoruiview.js @@ -0,0 +1,91 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import InlineEditorUIView from '../src/inlineeditoruiview'; +import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; +import FloatingPanelView from '@ckeditor/ckeditor5-ui/src/panel/floating/floatingpanelview'; +import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview'; +import Locale from '@ckeditor/ckeditor5-utils/src/locale'; + +describe( 'InlineEditorUIView', () => { + let locale, view; + + beforeEach( () => { + locale = new Locale( 'en' ); + view = new InlineEditorUIView( locale ); + } ); + + describe( 'constructor()', () => { + describe( '#toolbar', () => { + it( 'is created', () => { + expect( view.toolbar ).to.be.instanceof( ToolbarView ); + } ); + + it( 'is given a locale object', () => { + expect( view.toolbar.locale ).to.equal( locale ); + } ); + } ); + + describe( '#panel', () => { + it( 'is created', () => { + expect( view.panel ).to.be.instanceof( FloatingPanelView ); + } ); + + it( 'is given a locale object', () => { + expect( view.panel.locale ).to.equal( locale ); + } ); + + it( 'is given the right CSS class', () => { + expect( view.panel.element.classList.contains( 'ck-toolbar__container' ) ).to.be.true; + } ); + + it( 'is put into the #body collection', () => { + expect( view.body.get( 0 ) ).to.equal( view.panel ); + } ); + } ); + + describe( '#editable', () => { + it( 'is created', () => { + expect( view.editable ).to.be.instanceof( InlineEditableUIView ); + } ); + + it( 'is given a locate object', () => { + expect( view.editable.locale ).to.equal( locale ); + } ); + + it( 'is registered as a child', () => { + const spy = sinon.spy( view.editable, 'destroy' ); + + return view.init() + .then( () => view.destroy() ) + .then( () => { + sinon.assert.calledOnce( spy ); + } ); + } ); + } ); + } ); + + describe( 'init', () => { + it( 'appends #toolbar to panel#content', () => { + expect( view.panel.content ).to.have.length( 0 ); + + return view.init() + .then( () => { + expect( view.panel.content.get( 0 ) ).to.equal( view.toolbar ); + } ) + .then( () => view.destroy() ); + } ); + } ); + + describe( 'editableElement', () => { + it( 'returns editable\'s view element', () => { + return view.init() + .then( () => { + expect( view.editableElement.getAttribute( 'contentEditable' ) ).to.equal( 'true' ); + } ) + .then( () => view.destroy() ); + } ); + } ); +} ); diff --git a/tests/manual/inline.html b/tests/manual/inline.html new file mode 100644 index 0000000..d989da6 --- /dev/null +++ b/tests/manual/inline.html @@ -0,0 +1,32 @@ +

+ + +

+ +
+

Editor 1

+

This is an editor instance.

+
+ +
+

Editor 2

+

This is another editor instance.

+

+ Unlike Editor 1 it doesn't have contenteditable=true initially. + Check if it's editable after initializing the editors and back to non-editable after destroying them. +

+
+ + diff --git a/tests/manual/inline.js b/tests/manual/inline.js new file mode 100644 index 0000000..96f33c4 --- /dev/null +++ b/tests/manual/inline.js @@ -0,0 +1,64 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals console:false, document, window */ + +import InlineEditor from '../../src/inline'; +import ArticlePreset from '@ckeditor/ckeditor5-presets/src/article'; +import testUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + +window.editors = {}; +window.editables = []; +window._observers = []; + +function initEditors() { + init( '#editor-1' ); + init( '#editor-2' ); + + function init( selector ) { + InlineEditor.create( document.querySelector( selector ), { + plugins: [ ArticlePreset ], + toolbar: [ 'headings', 'bold', 'italic', 'undo', 'redo', 'link', 'unlink' ] + } ) + .then( editor => { + console.log( `${ selector } has been initialized`, editor ); + console.log( 'It has been added to global `editors` and `editables`.' ); + + window.editors[ selector ] = editor; + window.editables.push( editor.editing.view.getRoot() ); + + let observer = testUtils.createObserver(); + + observer.observe( + `${ selector }.ui.focusTracker`, + editor.ui.focusTracker, + [ 'isFocused' ] + ); + + window._observers.push( observer ); + } ) + .catch( err => { + console.error( err.stack ); + } ); + } +} + +function destroyEditors() { + for ( let selector in window.editors ) { + window.editors[ selector ].destroy().then( () => { + console.log( `${ selector } was destroyed.` ); + } ); + } + + for ( let observer of window._observers ) { + observer.stopListening(); + } + + window.editors = {}; + window.editables.length = window._observers.length = 0; +} + +document.getElementById( 'initEditors' ).addEventListener( 'click', initEditors ); +document.getElementById( 'destroyEditors' ).addEventListener( 'click', destroyEditors ); diff --git a/tests/manual/inline.md b/tests/manual/inline.md new file mode 100644 index 0000000..623bf0f --- /dev/null +++ b/tests/manual/inline.md @@ -0,0 +1,27 @@ +1. Click "Init editors". +2. Expected: + * Two inline editor should be created. + * Elements used as editables should remain visible. + * They should preserve `.custom-class` and `custom-attr="foo"`. + * There should be floating toolbars with "Bold", "Italic", "Undo", "Redo", "Link" and "Unlink" buttons. +3. Scroll the webpage. +4. Expected: + * Focused editor's toolbar should float around but always stick to editable. + * Focused editor's toolbar should stick to the bottom of the editable if there's not enough space above. +5. Press Alt+F10 when focusing the editor. +6. Expected: + * Toolbar should gain focus. Editable should keep its styling. +7. Click "Destroy editors". +8. Expected: + * Editors should be destroyed. + * Element used as editables should remain visible. + * They should preserve `.custom-class` and `custom-attr="foo"`. + * Elements should contain its data (updated). + * `.ck-body` regions should be removed from ``. + +## Notes: + +* You can play with: + * `window.editables[ N ].isReadOnly`, +* Changes to `window.editors[ name ].focusTracker.isFocused` should be logged to the console. +* Features should work. diff --git a/theme/theme.scss b/theme/theme.scss new file mode 100644 index 0000000..eb525df --- /dev/null +++ b/theme/theme.scss @@ -0,0 +1,21 @@ +// Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. +// For licensing, see LICENSE.md or http://ckeditor.com/license + +@import '~@ckeditor/ckeditor5-theme-lark/theme/theme.scss'; + +// TODO move to make a common style with editor-classic. +.ck-editor__editable { + &.ck-focused { + @include ck-focus-ring( 'outline' ); + @include ck-box-shadow( $ck-inner-shadow ); + } + + &_inline { + overflow: auto; + padding: 0 ck-spacing(); + } +} + +.ck-body .ck-toolbar { + @include ck-editor-toolbar(); +}