Skip to content

Commit

Permalink
Merge pull request #46 from ckeditor/t/44
Browse files Browse the repository at this point in the history
Feature: Allowed attaching the inspector collapsed using a configuration passed to `CKEditorInspector#attach()`. Implemented the `CKEditorInspector#destroy()` method. Unified `CKEditorInspector#attach()` arguments syntax and allowed attaching to multiple instances at a time. Closes #44. Closes #42. Closes #48.

BREAKING CHANGE: The `CKEditorInspector.attach( 'editor-name', editor );` syntax was deprecated and replaced by an object literal `CKEditorInspector.attach( { 'editor-name': editor, ... } );`.
  • Loading branch information
ma2ciek committed Sep 20, 2019
2 parents 6fa93b8 + ae6e773 commit 69ad014
Show file tree
Hide file tree
Showing 14 changed files with 910 additions and 473 deletions.
69 changes: 59 additions & 10 deletions README.md
Expand Up @@ -24,22 +24,71 @@ Include the script to load the inspector:
<script src="path/to/inspector.js"></script>
```

Call `CKEditorInspector.attach( name, editor )` when editor instance is ready:
Call `CKEditorInspector.attach( editor )` when editor instance is ready:

```js
ClassicEditor
.create( ... )
.then( editor => {
CKEditorInspector.attach( 'editor-name', editor );
} )
.catch( error => {
console.error( error );
} );
.create( ... )
.then( editor => {
CKEditorInspector.attach( editor );
} )
.catch( error => {
console.error( error );
} );
```

**Note**: You can attach multiple editors to the inspector. Select the editor instance in the drop–down inside the inspector panel to switch context.
**Note**: You can attach to multiple editors under unique names at a time. Then you can select the editor instance in the drop–down inside the inspector panel to switch context.

Call `CKEditorInspector.detach( name )` to detach an instance from the inspector.
```js
CKEditorInspector.attach( {
'header-editor': editor1,
'footer-editor': editor2,
// ...
} );
```

Call `CKEditorInspector.detach( name )` to detach the inspector from an editor instance.

**Tip**: `CKEditorInspector.attach()` returns the generated name of the editor if it was not provided.

```js
// Attach the inspector to two editor instances:
const generatedName = CKEditorInspector.attach( editor1 );
CKEditorInspector.attach( { myEditor: editor2 } );

// ...

// Detach from the instances:
CKEditorInspector.detach( generatedName );
CKEditorInspector.detach( 'myEditor' );
```

### Configuration

You can pass configuration options to the `CKEditorInspector.attach()` method as the last argument:

```js
CKEditorInspector.attach( editor, {
// configuration options
} );

CKEditorInspector.attach( { 'editor-name': editor }, {
// configuration options
} );
```

#### `isCollapsed`

To attach the inspector with a collapsed UI, use the `options.isCollapsed` option.

**Note**: This option works when `CKEditorInspector.attach()` is called for the first time only.

```js
CKEditorInspector.attach( { 'editor-name': editor }, {
// Attach the inspector to the "editor" but the UI will be collapsed.
isCollapsed: true
} );
```

## Development

Expand Down
14 changes: 7 additions & 7 deletions package.json
Expand Up @@ -41,13 +41,13 @@
"@babel/cli": "^7.2.3",
"@babel/core": "^7.2.2",
"@babel/preset-react": "^7.0.0",
"@ckeditor/ckeditor5-basic-styles": "^11.0.0",
"@ckeditor/ckeditor5-build-decoupled-document": "^12.0.0",
"@ckeditor/ckeditor5-core": "^12.0.0",
"@ckeditor/ckeditor5-dev-env": "^13.0.2",
"@ckeditor/ckeditor5-paragraph": "^11.0.0",
"@ckeditor/ckeditor5-ui": "^12.0.0",
"@ckeditor/ckeditor5-utils": "^12.0.0",
"@ckeditor/ckeditor5-basic-styles": "^11.1.4",
"@ckeditor/ckeditor5-build-decoupled-document": "^12.4.0",
"@ckeditor/ckeditor5-core": "^12.3.0",
"@ckeditor/ckeditor5-dev-env": "^16.0.0",
"@ckeditor/ckeditor5-paragraph": "^11.0.5",
"@ckeditor/ckeditor5-ui": "^14.0.0",
"@ckeditor/ckeditor5-utils": "^14.0.0",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.5",
"chai": "^4.1.2",
Expand Down
2 changes: 1 addition & 1 deletion sample/index.html
Expand Up @@ -81,7 +81,7 @@ <h2>Second editor</h2>
.then( editor => {
window.firstEditor = editor;
document.body.insertBefore( editor.ui.view.toolbar.element, editor.ui.getEditableElement() );
CKEditorInspector.attach( 'first-editor', editor );
CKEditorInspector.attach( { 'first-editor': editor } );
} )
.catch( error => {
console.error( error );
Expand Down
152 changes: 108 additions & 44 deletions src/ckeditorinspector.js
Expand Up @@ -10,16 +10,12 @@ import ReactDOM from 'react-dom';

import InspectorUI from './components/ui';
import Logger from './logger';
import { normalizeArguments } from './utils';
import './ckeditorinspector.css';

// From changelog -> webpack.
window.CKEDITOR_INSPECTOR_VERSION = CKEDITOR_INSPECTOR_VERSION;

const container = document.createElement( 'div' );
container.className = 'ck-inspector-wrapper';

let unnamedEditorCount = 0;

export default class CKEditorInspector {
/**
* Attaches the inspector to an editor instance.
Expand All @@ -28,69 +24,137 @@ export default class CKEditorInspector {
* .create( ... )
* .then( editor => {
* CKEditorInspector.attach( editor );
*
* // Alternatively:
* // CKEditorInspector.attach( 'my-editor', editor );
* } )
* .catch( error => {
* console.error( error );
* } );
*
* @param {Editor|String} editorOrName When an unique string is provided, the editor will be listed in the inspector
* under a name (the instance passed as a second argument). If an editor instance is passed, the editor with be
* attached and assigned a generated name.
* @param {Editor} [editor] An instance of the editor, if the first argument was specified as a string.
* @returns {String} The unique name of the editor in the inspector. Useful when using `CKEditorInspector.detach()`.
* **Note:** You can attach to multiple editors at a time under unique names:
*
* CKEditorInspector.attach( {
* 'header-editor': editor1,
* 'footer-editor': editor2,
* // ...
* } );
*
* **Note:** You can pass global configuration options when attaching:
*
* CKEditorInspector.attach( editor, { option: 'value', ... } );
* CKEditorInspector.attach( {
* 'header-editor': editor1,
* 'footer-editor': editor2
* }, { option: 'value', ... } );
*
* @param {Editor|Object} editorOrEditors If an editor instance is passed, the inspect will attach to the editor
* with an auto–generated name. It is possible to pass an object with `name: instance` pairs to attach to
* multiple editors at a time with unique names.
* @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 attach( editorOrName, editor ) {
let name, instance;

if ( typeof editorOrName === 'string' ) {
name = editorOrName;
instance = editor;
} else {
name = `editor-${ ++unnamedEditorCount }`;
instance = editorOrName;
}
static attach( ...args ) {
const { editors, options } = normalizeArguments( args );

Logger.group( '%cAttached the inspector to a CKEditor 5 instance. To learn more, visit https://ckeditor.com/docs/ckeditor5.',
'font-weight: bold;' );
Logger.log( `Editor instance "${ name }"`, instance );
Logger.groupEnd();
for ( const editorName in editors ) {
const editorInstance = editors[ editorName ];

CKEditorInspector._editors.set( name, instance );
Logger.group( '%cAttached the inspector to a CKEditor 5 instance. To learn more, visit https://ckeditor.com/docs/ckeditor5.',
'font-weight: bold;' );
Logger.log( `Editor instance "${ editorName }"`, editorInstance );
Logger.groupEnd();

instance.on( 'destroy', () => {
CKEditorInspector.detach( name );
} );
CKEditorInspector._editors.set( editorName, editorInstance );

if ( !container.parentNode ) {
document.body.appendChild( container );
editorInstance.on( 'destroy', () => {
CKEditorInspector.detach( editorName );
} );

ReactDOM.render(
<InspectorUI
ref={CKEditorInspector._inspectorRef}
editors={CKEditorInspector._editors}
/>,
container );
CKEditorInspector._mount( options );
CKEditorInspector._updateEditorsState();
}

CKEditorInspector._updateState();

return name;
return Object.keys( editors );
}

/**
* Detaches the inspector from an editor instance.
*
* CKEditorInspector.attach( { 'my-editor': editor } );
*
* // The inspector will no longer inspect the "editor".
* CKEditorInspector.detach( 'my-editor' );
*
* @param {String} string Name of the editor to detach.
*/
static detach( name ) {
CKEditorInspector._editors.delete( name );
CKEditorInspector._updateState();
CKEditorInspector._updateEditorsState();
}

static _updateState() {
/**
* Destroys the entire inspector application and removes it from DOM.
*/
static destroy() {
if ( !CKEditorInspector._wrapper ) {
return;
}

ReactDOM.unmountComponentAtNode( CKEditorInspector._wrapper );
CKEditorInspector._editors.clear();
CKEditorInspector._wrapper.remove();
CKEditorInspector._wrapper = null;
}

static _updateEditorsState() {
// Don't update state if the application was destroy()ed.
if ( !CKEditorInspector._isMounted ) {
return;
}

CKEditorInspector._inspectorRef.current.setState( {
editors: CKEditorInspector._editors
} );
}

static _mount( options ) {
if ( CKEditorInspector._wrapper ) {
return;
}

const container = CKEditorInspector._wrapper = document.createElement( 'div' );
container.className = 'ck-inspector-wrapper';
document.body.appendChild( container );

ReactDOM.render(
<InspectorUI
ref={CKEditorInspector._inspectorRef}
editors={CKEditorInspector._editors}
isCollapsed={options.isCollapsed}
/>,
container );
}

static get _isMounted() {
return !!CKEditorInspector._inspectorRef.current;
}
}

CKEditorInspector._editors = new Map();
CKEditorInspector._inspectorRef = React.createRef();
CKEditorInspector._wrapper = null;

/**
* The configuration options of the inspector.
*
* @interface CKEditorInspectorConfig
*/

/**
* Controls the initial collapsed state of the inspector. Allows attaching to an editor instance without
* expanding the UI.
*
* **Note**: Works when `attach()` is called for the first time only.
*
* @member {Boolean} CKEditorInspectorConfig#isCollapsed
*/
6 changes: 5 additions & 1 deletion src/components/ui.js
Expand Up @@ -36,8 +36,12 @@ export default class InspectorUI extends Component {

const height = StorageManager.get( LOCAL_STORAGE_INSPECTOR_HEIGHT ) || INSPECTOR_DEFAULT_HEIGHT;

// The collapsed state can be either configured by passing an option to `attach()`
// or retrieved from the last "session".
const isCollapsed = this.props.isCollapsed === true || StorageManager.get( LOCAL_STORAGE_IS_COLLAPSED ) === 'true';

this.state = {
isCollapsed: StorageManager.get( LOCAL_STORAGE_IS_COLLAPSED ) === 'true',
isCollapsed,
height,
editors: null,
currentEditorName: null,
Expand Down
4 changes: 4 additions & 0 deletions src/logger.js
Expand Up @@ -17,4 +17,8 @@ export default class Logger {
static log( ...args ) {
console.log( ...args );
}

static warn( ...args ) {
console.warn( ...args );
}
}
51 changes: 51 additions & 0 deletions src/utils.js
@@ -0,0 +1,51 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

import Logger from './logger';

let unnamedEditorCount = 0;

export function normalizeArguments( args ) {
const normalized = {
editors: {},
options: {}
};

// Deprecated // attach( 'name', editor );
if ( typeof args[ 0 ] === 'string' ) {
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.`
);

normalized.editors[ args[ 0 ] ] = args[ 1 ];
} else {
// attach( editor );
if ( isEditorInstance( args[ 0 ] ) ) {
normalized.editors[ getNextEditorName() ] = args[ 0 ];
}
// attach( { foo: editor1, bar: editor2, ... } );
else {
for ( const name in args[ 0 ] ) {
normalized.editors[ name ] = args[ 0 ][ name ];
}
}

// attach( ..., { options } );
normalized.options = args[ 1 ] || normalized.options;
}

return normalized;
}

function getNextEditorName() {
return `editor-${ ++unnamedEditorCount }`;
}

function isEditorInstance( arg ) {
// Quack! 🦆
return !!arg.model;
}

0 comments on commit 69ad014

Please sign in to comment.