Skip to content

Commit

Permalink
Merge pull request #58 from ckeditor/t/56
Browse files Browse the repository at this point in the history
Feature: Implemented the `CKEditorInspector#attachToAll()` method. Closes #56.
  • Loading branch information
ma2ciek committed Sep 20, 2019
2 parents 69ad014 + 7cabf03 commit 8a3c7ea
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 42 deletions.
27 changes: 26 additions & 1 deletion sample/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ <h2>Second editor</h2>
<p>foo</p>
</div>

<h2>Actions</h2>
<button id="attach-inspector">Attach inspector to all editors</button>
<button id="detach-inspector-from-editors">Detach inspector from editors</button>
<button id="destroy-inspector">Destroy inspector</button>

<script>
class DummyUploadAdapter {
constructor( loader ) {
Expand Down Expand Up @@ -74,6 +79,24 @@ <h2>Second editor</h2>
};
}

let editorNames = [];

document.querySelector( '#destroy-inspector' ).addEventListener( 'click', () => {
CKEditorInspector.destroy();
editorNames = [];
} );

document.querySelector( '#attach-inspector' ).addEventListener( 'click', () => {
const names = CKEditorInspector.attachToAll();

editorNames.push( ...names );
} );

document.querySelector( '#detach-inspector-from-editors' ).addEventListener( 'click', () => {
editorNames.forEach( name => CKEditorInspector.detach( name ) );
editorNames = [];
} );

DecoupledEditor
.create( document.querySelector( '#first-editor-content' ), {
extraPlugins: [ UploadAdapter ]
Expand All @@ -82,6 +105,7 @@ <h2>Second editor</h2>
window.firstEditor = editor;
document.body.insertBefore( editor.ui.view.toolbar.element, editor.ui.getEditableElement() );
CKEditorInspector.attach( { 'first-editor': editor } );
editorNames.push( 'first-editor' );
} )
.catch( error => {
console.error( error );
Expand All @@ -94,7 +118,8 @@ <h2>Second editor</h2>
.then( editor => {
window.secondEditor = editor;
document.body.insertBefore( editor.ui.view.toolbar.element, editor.ui.getEditableElement() );
CKEditorInspector.attach( editor );
const [ name ] = CKEditorInspector.attach( editor );
editorNames.push( name );
} )
.catch( error => {
console.error( error );
Expand Down
45 changes: 44 additions & 1 deletion src/ckeditorinspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ import './ckeditorinspector.css';
window.CKEDITOR_INSPECTOR_VERSION = CKEDITOR_INSPECTOR_VERSION;

export default class CKEditorInspector {
constructor() {
Logger.warn(
'[CKEditorInspector] Whoops! Looks like you tried to create an instance of the CKEditorInspector class. ' +
'To attach the inspector, use the static CKEditorInspector.attach( editor ) method instead. ' +
'For the latest API, please refer to https://github.com/ckeditor/ckeditor5-inspector/blob/master/README.md. '
);
}

/**
* Attaches the inspector to an editor instance.
*
Expand Down Expand Up @@ -77,6 +85,37 @@ export default class CKEditorInspector {
return Object.keys( editors );
}

/**
* Attaches the inspector to all CKEditor instances discovered in DOM.
*
* Editor instances are named `editor-1`, `editor-2`, etc..
*
* **Note:** This method requires CKEditor 12.3.0 or later.
*
* **Note:** You can pass global configuration options when attaching:
*
* CKEditorInspector.attachToAll( { option: 'value', ... } );
*
* @param {CKEditorInspectorConfig} [options] An object of global configuration options controlling the
* behavior of the inspector.
* @returns {Array.<String>} Names of the editors the inspector attached to. Useful when using `CKEditorInspector.detach()`
* with generated editor names.
*/
static attachToAll( options ) {
const domEditables = document.querySelectorAll( '.ck.ck-content.ck-editor__editable' );
const attachedEditorNames = [];

for ( const domEditable of domEditables ) {
const editor = domEditable.ckeditorInstance;

if ( editor && !CKEditorInspector._isAttachedTo( editor ) ) {
attachedEditorNames.push( ...CKEditorInspector.attach( editor, options ) );
}
}

return attachedEditorNames;
}

/**
* Detaches the inspector from an editor instance.
*
Expand Down Expand Up @@ -107,7 +146,7 @@ export default class CKEditorInspector {
}

static _updateEditorsState() {
// Don't update state if the application was destroy()ed.
// Don't update state if the application was destroyed.
if ( !CKEditorInspector._isMounted ) {
return;
}
Expand Down Expand Up @@ -138,6 +177,10 @@ export default class CKEditorInspector {
static get _isMounted() {
return !!CKEditorInspector._inspectorRef.current;
}

static _isAttachedTo( editor ) {
return [ ...CKEditorInspector._editors.values() ].includes( editor );
}
}

CKEditorInspector._editors = new Map();
Expand Down
5 changes: 3 additions & 2 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export function normalizeArguments( args ) {
Logger.warn(
`[CKEditorInspector] The CKEditorInspector.attach( '${ args[ 0 ] }', editor ) syntax has been deprecated ` +
'and will be removed in the near future. To pass a name of an editor instance, use ' +
`CKEditorInspector.attach( { '${ args[ 0 ] }': editor } ) instead.`
`CKEditorInspector.attach( { '${ args[ 0 ] }': editor } ) instead. ` +
'Learn more in https://github.com/ckeditor/ckeditor5-inspector/blob/master/README.md.'
);

normalized.editors[ args[ 0 ] ] = args[ 1 ];
Expand Down Expand Up @@ -47,5 +48,5 @@ function getNextEditorName() {

function isEditorInstance( arg ) {
// Quack! 🦆
return !!arg.model;
return !!arg.model && !!arg.editing;
}
104 changes: 87 additions & 17 deletions tests/inspector/ckeditorinspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ describe( 'CKEditorInspector', () => {
} );

afterEach( () => {
Logger.log.restore();
Logger.warn.restore();

sinon.restore();
element.remove();

CKEditorInspector.destroy();
Expand All @@ -42,6 +40,14 @@ describe( 'CKEditorInspector', () => {
} );
} );

it( 'warns if called the constructor()', () => {
// eslint-disable-next-line no-unused-vars
const inspector = new CKEditorInspector();

sinon.assert.calledOnce( warnStub );
sinon.assert.calledWithMatch( warnStub, /^\[CKEditorInspector\]/ );
} );

describe( '#attach()', () => {
it( 'adds inspector to DOM', () => {
expect( document.querySelector( '.ck-inspector-wrapper' ) ).to.be.null;
Expand All @@ -60,18 +66,20 @@ describe( 'CKEditorInspector', () => {
} );

it( 'adds inspector to DOM only once when attaching to the first editor', () => {
return TestEditor.create( element )
const anotherEditorElement = document.createElement( 'div' );
document.body.appendChild( anotherEditorElement );

return TestEditor.create( anotherEditorElement )
.then( anotherEditor => {
CKEditorInspector.attach( { foo: editor } );
CKEditorInspector.attach( { bar: anotherEditor } );

expect( document.querySelectorAll( '.ck-inspector-wrapper' ) ).to.be.lengthOf( 1 );
expect( document.querySelectorAll( '.ck-inspector' ) ).to.be.lengthOf( 1 );

anotherEditorElement.remove();

return anotherEditor.destroy();
} )
.catch( err => {
throw err;
} );
} );

Expand All @@ -89,9 +97,6 @@ describe( 'CKEditorInspector', () => {
expect( secondNames ).to.have.members( [ 'editor-2' ] );

return anotherEditor.destroy();
} )
.catch( err => {
throw err;
} );
} );

Expand All @@ -103,6 +108,16 @@ describe( 'CKEditorInspector', () => {
expect( inspectorRef.state.editors.get( 'foo' ) ).to.equal( editor );
} );

it( 'attaches to a editor named like one of core editor properties (used in a duck typing)', () => {
CKEditorInspector.attach( { model: editor } );
CKEditorInspector.attach( { editing: editor } );

inspectorRef = CKEditorInspector._inspectorRef.current;

expect( inspectorRef.state.editors.get( 'model' ) ).to.equal( editor );
expect( inspectorRef.state.editors.get( 'editing' ) ).to.equal( editor );
} );

it( 'attaches to multiple editors at a time', () => {
return TestEditor.create( element )
.then( anotherEditor => {
Expand All @@ -115,9 +130,6 @@ describe( 'CKEditorInspector', () => {
expect( names ).to.have.members( [ 'foo', 'bar' ] );

return anotherEditor.destroy();
} )
.catch( err => {
throw err;
} );
} );

Expand Down Expand Up @@ -147,10 +159,6 @@ describe( 'CKEditorInspector', () => {

describe( 'options', () => {
describe( '#isCollapsed', () => {
beforeEach( () => {
CKEditorInspector.destroy();
} );

it( 'does nothing if unspecified', () => {
CKEditorInspector.attach( editor );

Expand Down Expand Up @@ -178,6 +186,68 @@ describe( 'CKEditorInspector', () => {
} );
} );

describe( '#attachToAll()', () => {
it( 'attaches to all editors', () => {
return TestEditor.create( element )
.then( anotherEditor => {
document.body.appendChild( editor.ui.view.element );
document.body.appendChild( anotherEditor.ui.view.element );

const editorNames = CKEditorInspector.attachToAll();

inspectorRef = CKEditorInspector._inspectorRef.current;

expect( inspectorRef.state.editors.size ).to.equal( 2 );
expect( inspectorRef.state.editors.get( 'editor-5' ) ).to.equal( editor );
expect( inspectorRef.state.editors.get( 'editor-6' ) ).to.equal( anotherEditor );
expect( editorNames ).to.have.members( [ 'editor-5', 'editor-6' ] );

return anotherEditor.destroy();
} );
} );

it( 'detects and prevents duplicates', () => {
return TestEditor.create( element )
.then( anotherEditor => {
document.body.appendChild( editor.ui.view.element );
document.body.appendChild( anotherEditor.ui.view.element );

CKEditorInspector.attachToAll();

inspectorRef = CKEditorInspector._inspectorRef.current;

expect( inspectorRef.state.editors.size ).to.equal( 2 );
expect( inspectorRef.state.editors.get( 'editor-7' ) ).to.equal( editor );
expect( inspectorRef.state.editors.get( 'editor-8' ) ).to.equal( anotherEditor );

CKEditorInspector.attachToAll();

inspectorRef = CKEditorInspector._inspectorRef.current;

expect( inspectorRef.state.editors.size ).to.equal( 2 );

return anotherEditor.destroy();
} );
} );

it( 'passes options to #attach()', () => {
return TestEditor.create( element )
.then( anotherEditor => {
document.body.appendChild( editor.ui.view.element );
document.body.appendChild( anotherEditor.ui.view.element );

const spy = sinon.spy( CKEditorInspector, 'attach' );
const options = { foo: true };
CKEditorInspector.attachToAll( options );

sinon.assert.calledWithExactly( spy.firstCall, editor, options );
sinon.assert.calledWithExactly( spy.secondCall, anotherEditor, options );

return anotherEditor.destroy();
} );
} );
} );

describe( '#detach()', () => {
it( 'detaches an editor', () => {
CKEditorInspector.attach( { foo: editor } );
Expand Down
3 changes: 1 addition & 2 deletions tests/inspector/components/commands/commandinspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe( '<CommandInspector />', () => {
afterEach( () => {
wrapper.unmount();
element.remove();
sinon.restore();

return editor.destroy();
} );
Expand Down Expand Up @@ -64,8 +65,6 @@ describe( '<CommandInspector />', () => {
logButton.simulate( 'click' );
sinon.assert.calledOnce( logSpy );

logSpy.restore();

wrapper.unmount();
} );

Expand Down
3 changes: 1 addition & 2 deletions tests/inspector/components/model/nodeinspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe( '<ModelNodeInspector />', () => {
afterEach( () => {
wrapper.unmount();
element.remove();
sinon.restore();

return editor.destroy();
} );
Expand Down Expand Up @@ -102,8 +103,6 @@ describe( '<ModelNodeInspector />', () => {

logNodeButton.simulate( 'click' );
sinon.assert.calledOnce( logSpy );

logSpy.restore();
} );

it( 'renders for a RootElement', () => {
Expand Down
3 changes: 1 addition & 2 deletions tests/inspector/components/model/selectioninspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe( '<ModelSelectionInspector />', () => {
afterEach( () => {
wrapper.unmount();
element.remove();
sinon.restore();

return editor.destroy();
} );
Expand All @@ -62,8 +63,6 @@ describe( '<ModelSelectionInspector />', () => {
logFocusButton.simulate( 'click' );
sinon.assert.calledThrice( logSpy );

logSpy.restore();

wrapper.unmount();
} );

Expand Down
3 changes: 1 addition & 2 deletions tests/inspector/components/view/nodeinspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe( '<ViewNodeInspector />', () => {
afterEach( () => {
wrapper.unmount();
element.remove();
sinon.restore();

return editor.destroy();
} );
Expand Down Expand Up @@ -102,8 +103,6 @@ describe( '<ViewNodeInspector />', () => {

logNodeButton.simulate( 'click' );
sinon.assert.calledOnce( logSpy );

logSpy.restore();
} );

it( 'renders for a RootElement', () => {
Expand Down
3 changes: 1 addition & 2 deletions tests/inspector/components/view/selectioninspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe( '<ViewSelectionInspector />', () => {
afterEach( () => {
wrapper.unmount();
element.remove();
sinon.restore();

return editor.destroy();
} );
Expand All @@ -62,8 +63,6 @@ describe( '<ViewSelectionInspector />', () => {
logFocusButton.simulate( 'click' );
sinon.assert.calledThrice( logSpy );

logSpy.restore();

wrapper.unmount();
} );

Expand Down

0 comments on commit 8a3c7ea

Please sign in to comment.