Skip to content

Commit

Permalink
Merge 19da6dc into 6fa93b8
Browse files Browse the repository at this point in the history
  • Loading branch information
oleq committed Sep 20, 2019
2 parents 6fa93b8 + 19da6dc commit 3f83bbf
Show file tree
Hide file tree
Showing 14 changed files with 1,030 additions and 477 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
193 changes: 150 additions & 43 deletions src/ckeditorinspector.js
Expand Up @@ -10,87 +10,194 @@ 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 {
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.
*
* ClassicEditor
* .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 Object.keys( editors );
}

return name;
/**
* 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.
*
* 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;
}

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

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 );
}
}

0 comments on commit 3f83bbf

Please sign in to comment.