Using Plugins

The Editor allows you to extend and customize its functionality and features by using plugins.

Popup Tools

You can implement a plugin that renders a popup in the Editor and shows additional tools or supplementary information based on user selection.

import React from 'react';
import ReactDOM from 'react-dom';

import { Editor, EditorTools, ProseMirror } from '@progress/kendo-react-editor';
import { ButtonGroup } from '@progress/kendo-react-buttons';

const { Bold, Italic, Underline } = EditorTools;

class App extends React.Component {
    state = { view: undefined, showTools: false, position: {} };
    toolSize = { width: 36, height: 36 };
    toolsCount = 3;
    offset = { top: -5, left: 0 };

    selectionToolsPlugin = () => (
        new ProseMirror.Plugin({
            key: new ProseMirror.PluginKey('selection-tools'),
            view: () => ({
                update: (view, _prevState) => {
                    const state = view.state;
                    let newState = { view, showTools: false, position: {} };
                    const selectionCollapsed = state.selection.empty;

                    if (!selectionCollapsed) {
                        let { from, to } = state.selection;
                        let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
                        let left = Math.max((start.left + end.left) / 2, start.left);
                        newState.showTools = true;
                        newState.position = {
                            top: start.top - this.toolSize.height + this.offset.top,
                            left: left - (this.toolSize.width * this.toolsCount / 2) + this.offset.left
                        };
                    }
                    this.setState(newState);
                }
            })
        })
    );

    onMount = event => {
        const state = event.viewProps.state;
        const plugins = [
            ...state.plugins,
            this.selectionToolsPlugin()
        ];

        return new ProseMirror.EditorView(
            { mount: event.dom }, {
                ...event.viewProps,
                state: ProseMirror.EditorState.create({ doc: state.doc, plugins })
            }
        );
    }

    onScroll = () => this.setState({ showTools: false });

    render() {
        return (
            <div
                onScroll={this.onScroll}
            >
                <Editor
                    contentStyle={{ height: 220 }}
                    defaultContent="<p>Select any text and Bold, Italic and Underline tools will appear above the selection.</p>"
                    defaultEditMode="div"
                    onMount={this.onMount}
                />
                {this.renderTools()}
            </div>
        );
    }

    renderTools() {
        const toolProps = {
            view: this.state.view,
            style: { width: this.toolSize.width, height: this.toolSize.height }
        };

        return (
            this.state.showTools && (
                <span
                    style={{
                        position: 'absolute',
                        backgroundColor: 'white',
                        ...this.state.position
                    }}
                >
                    <ButtonGroup>
                        <Bold {...toolProps}/>
                        <Italic {...toolProps}/>
                        <Underline {...toolProps}/>
                    </ButtonGroup>
                    <span className="k-callout k-callout-s" style={{ color: '#ededed' }} />
                </span>
            )
        );
    }
}

ReactDOM.render(
    <App />,
    document.querySelector('my-app')
);

Placeholders

You can use a plugin to provide a placeholder functionality for the Editor which is similar to the placeholder attribute of the HTML input element.

import React from 'react';
import ReactDOM from 'react-dom';
import { placeholder, styles } from './placeholder';
import { Editor, EditorTools, ProseMirror } from '@progress/kendo-react-editor';
const { Bold, Italic, Underline } = EditorTools;

class App extends React.Component {
    onMount = event => {
        const state = event.viewProps.state;
        const plugins = [
            ...state.plugins,
            placeholder('Enter some content ...')
        ];

        const documnt = event.dom.ownerDocument;
        documnt.querySelector('style').appendChild(documnt.createTextNode(styles));

        return new ProseMirror.EditorView(
            { mount: event.dom }, {
                ...event.viewProps,
                state: ProseMirror.EditorState.create({ doc: state.doc, plugins })
            }
        );
    }

    render() {
        return (
            <Editor
                tools={[ [ Bold, Italic, Underline ] ]}
                contentStyle={{ height: 200 }}
                onMount={this.onMount}
            />
        );
    }
}

ReactDOM.render(
    <App />,
    document.querySelector('my-app')
);

Input Rules

Input rules provide an option for modifying the input of the user on the fly and represent pairs of matches and corresponding actions. When the user input matches an input rule, the typed text is transformed based on the action of that rule.

The following example demonstrates how to implement input rules and attach them to the Editor.

import React from 'react';
import ReactDOM from 'react-dom';
import { Editor, EditorTools, ProseMirror } from '@progress/kendo-react-editor';

const { Bold, Italic, Underline } = EditorTools;
const { InputRule, inputRules, wrappingInputRule, textblockTypeInputRule } = ProseMirror;

class App extends React.Component {
    inputRule = (nodes) => {
        const { ordered_list, bullet_list, heading, blockquote, code_block } = nodes;

        return inputRules({
            rules: [
                // Converts double dashes to an emdash.
                new InputRule(/--$/, '—'),

                // Converts three dots to an ellipsis character.
                new InputRule(/\.\.\.$/, '…'),

                // Converts '# ', '## ', '### ', '#### ', '##### ', '###### '
                // into heading 1, 2, 3, 4, 5, and 6, according to the '#' characters count.
                textblockTypeInputRule(/^(#{1,6})\s$/, heading, match => ({ level: match[1].length })),

                // Converts '> ' into a blockquote.
                wrappingInputRule(/^\s*>\s$/, blockquote),

                // Converts three backticks to a code block.
                textblockTypeInputRule(/^```$/, code_block),

                // Converts '- ' or '+ ' to a bullet list.
                wrappingInputRule(/^\s*([-+*])\s$/, bullet_list),

                // Converts '1. ' to an ordered list.
                wrappingInputRule(/^(\d+)\.\s$/, ordered_list, match => ({ order: Number(match[1]) }),
                          (match, node) => node.childCount + node.attrs.order === match[1])
            ]
        });
    }

    onMount = event => {
        const state = event.viewProps.state;
        const plugins = [
            ...state.plugins,
            this.inputRule(state.schema.nodes)
        ];

        return new ProseMirror.EditorView(
            { mount: event.dom }, {
                ...event.viewProps,
                state: ProseMirror.EditorState.create({ doc: state.doc, plugins })
            }
        );
    }

    render() {
        return (
            <Editor
                tools={[ [ Bold, Italic, Underline ] ]}
                contentStyle={{ height: 300 }}
                onMount={this.onMount}
            />
        );
    }
}

ReactDOM.render(
    <App />,
    document.querySelector('my-app')
);
 /