0.3.17 • Published 12 hours ago

ds-react-rte v0.3.17

Weekly downloads
-
License
ISC
Repository
github
Last release
12 hours ago

Getting Started

$ npm install --save ds-react-rte

RichTextEditor is the main editor component. It is comprised of the Draft.js <Editor>, some UI components (e.g. toolbar) and some helpful abstractions around getting and setting content with HTML/Markdown.

RichTextEditor is designed to be used like a textarea except that instead of value being a string, it is an object with toString on it. Creating a value from a string is also easy using createValueFromString(markup, 'html').

Required Webpack configuration

If you are not using Webpack, you can skip this section. Webpack is required for isomorphic/server-side rendering support in a Node.js environment.

'react-rte' contains a bundle that is already built (with CSS) using webpack and is not intended to be consumed again by webpack. So, if you are using webpack you must import RichTextEditor from react-rte/lib/RichTextEditor in order to get the un-bundled script which webpack can bundle with your app.

If you are using webpack you must add a css loader or else your webpack build will fail. For example:

  {
    test: /\.css$/,
    loaders: [
      'style-loader',
      'css-loader?modules'
    ]
  },

Example Usage:

This example uses newer JavaScript and JSX. For an example in old JavaScript, see below.

import React, {Component, PropTypes} from 'react';
import RichTextEditor from 'react-rte';

class MyStatefulEditor extends Component {
  static propTypes = {
    onChange: PropTypes.func
  };

  state = {
    value: RichTextEditor.createEmptyValue()
  }

  onChange = (value) => {
    this.setState({value});
    if (this.props.onChange) {
      // Send the changes up to the parent component as an HTML string.
      // This is here to demonstrate using `.toString()` but in a real app it
      // would be better to avoid generating a string on each change.
      this.props.onChange(
        value.toString('html')
      );
    }
  };

  render () {
    return (
      <RichTextEditor
        value={this.state.value}
        onChange={this.onChange}
      />
    );
  }
}

Toolbar Customization

render() {
  // The toolbarConfig object allows you to specify custom buttons, reorder buttons and to add custom css classes.
  // Supported inline styles: https://github.com/facebook/draft-js/blob/master/docs/Advanced-Topics-Inline-Styles.md
  // Supported block types: https://github.com/facebook/draft-js/blob/master/docs/Advanced-Topics-Custom-Block-Render.md#draft-default-block-render-map
  const toolbarConfig = {
    // Optionally specify the groups to display (displayed in the order listed).
    display: ['INLINE_STYLE_BUTTONS', 'BLOCK_TYPE_BUTTONS', 'LINK_BUTTONS', 'BLOCK_TYPE_DROPDOWN', 'HISTORY_BUTTONS'],
    INLINE_STYLE_BUTTONS: [
      {label: 'Bold', style: 'BOLD', className: 'custom-css-class'},
      {label: 'Italic', style: 'ITALIC'},
      {label: 'Underline', style: 'UNDERLINE'}
    ],
    BLOCK_TYPE_DROPDOWN: [
      {label: 'Normal', style: 'unstyled'},
      {label: 'Heading Large', style: 'header-one'},
      {label: 'Heading Medium', style: 'header-two'},
      {label: 'Heading Small', style: 'header-three'}
    ],
    BLOCK_TYPE_BUTTONS: [
      {label: 'UL', style: 'unordered-list-item'},
      {label: 'OL', style: 'ordered-list-item'}
    ],
    COLOR_DROPDOWN: [
      add: () => {},
      remove: () => {}
    ],
  };
  const colorStyleMap = {
    'red-dropdown_option': {
      color: 'rgba(255, 0, 0, 1.0)',
    },
    'orange-dropdown_option': {
      color: 'rgba(255, 127, 0, 1.0)',
    },
    ...
  };
  return (
    <RichTextEditor toolbarConfig={toolbarConfig} />
  );
}

Features

  • Pure React and fully declarative
  • Supported formats: HTML and Markdown (coming soon: extensible support for custom formats)
  • Document Model represents your document in a sane way that will deterministically convert to clean markup regardless of your format choice
  • Takes full advantage of Immutable.js and the excellent performance characteristics that come with it.
  • Reliable undo/redo without a large memory footprint
  • Modern browser support

Deterministic Output

Unlike typical rich text editors (such as CKEditor and TinyMCE) we keep our content state in a well-architected data model instead of in the view. One important advantage of separating our data model from our view is deterministic output.

Say, for instance, you select some text and add bold style. Then you add italic style. Or what if you add italic first and then bold. The result should be the same either way: the text range has both bold and italic style. But in the browser's view (Document Object Model) is this represented with a <strong> inside of an <em> or vice versa? Does it depend on the order in which you added the styles? In many web-based editors the HTML output does depend on the order of your actions. That means your output is non-deterministic. Two documents that look exactly the same in the editor will have different, sometimes unpredictable, HTML representations.

In this editor we use a pure, deterministic function to convert document state to HTML output. No matter how you arrived at the state, the output will be predictable. This makes everything easier to reason about. In our case, the <strong> will go inside the <em> every time.

API

Required Props

  • value: Used to represent the content/state of the editor. Initially you will probably want to create an instance using a provided helper such as RichTextEditor.createEmptyValue() or RichTextEditor.createValueFromString(markup, 'html').
  • onChange: A function that will be called with the "value" of the editor whenever it is changed. The value has a toString method which accepts a single format argument (either 'html' or 'markdown').

Other Props

All the props you can pass to Draft.js Editor can be passed to RichTextEditor (with the exception of editorState which will be generated internally based on the value prop).

  • autoFocus: Setting this to true will automatically focus input into the editor when the component is mounted
  • placeholder: A string to use as placeholder text for the RichTextEditor.
  • readOnly: A boolean that determines if the RichTextEditor should render static html.

EditorValue Class

In Draft.js EditorState contains not only the document contents but the entire state of the editor including cursor position and selection. This is helpful for many reasons including undo/redo. To make things easier for you, we have wrapped the state of the editor in an EditorValue instance with helpful methods to convert to/from a HTML or Markdown. An instance of this class should be passed to RichTextEditor in the value prop.

The EditorValue class has certain optimizations built in. So let's say you are showing the HTML of the editor contents in your view. If you change your cursor position, that will trigger an onChange event (because, remember, cursor position is part of EditorState) and you will need to call toString() to render your view. However, EditorValue is smart enough to know that the content didn't actually change since last toString() so it will return a cached version of the HTML.

Optimization tip: Try to call editorValue.toString() only when you actually need to convert it to a string. If you can keep passing around the editorValue without calling toString it will be very performant.

Example with ES5 and no JSX

var React = require('react');
var RichTextEditor = require('react-rte');

React.createClass({
  propTypes: {
    onChange: React.PropTypes.func
  },

  getInitialState: function() {
    return {
      value: RichTextEditor.createEmptyValue()
    };
  },

  render: function() {
    return React.createElement(RichTextEditor, {
      value: this.state.value,
      onChange: this.onChange
    });
  },

  onChange: function(value) {
    this.setState({value: value});
    if (this.props.onChange) {
      // Send the changes up to the parent component as an HTML string.
      // This is here to demonstrate using `.toString()` but in a real app it
      // would be better to avoid generating a string on each change.
      this.props.onChange(
        value.toString('html')
      );
    }
  }

});

Known Limitations

Currently the biggest limitation is that images are not supported. There is a plan to support inline images (using decorators) and eventually Medium-style block-level images (using a custom block renderer).

Other limitations include missing features such as: text-alignment and text color. These are coming soon.

React prior v15 will log the following superfluous warning:

A component is contentEditable and contains children managed by React. It is now your responsibility to guarantee that none of those nodes are unexpectedly modified or duplicated. This is probably not intentional.

As all nodes are managed internally by Draft, this is not a problem and this warning can be safely ignored. You can suppress this warning's display completely by duck-punching console.error before instantiating your component:

console.error = (function(_error) {
  return function(message) {
    if (typeof message !== 'string' || message.indexOf('component is `contentEditable`') === -1) {
      _error.apply(console, arguments);
    }
  };
})(console.error);

License

This software is ISC Licensed.

0.3.17

12 hours ago

0.3.16

1 day ago

0.3.15

8 days ago

0.3.14

9 days ago

0.3.13

2 months ago

0.3.12

3 months ago

0.3.9

3 months ago

0.3.11

3 months ago

0.3.10

3 months ago

0.3.8

3 months ago

0.3.7

3 months ago

0.3.0

3 months ago

0.3.6

3 months ago

0.2.7

3 months ago

0.3.5

3 months ago

0.2.9

3 months ago

0.2.8

3 months ago

0.3.2

3 months ago

0.3.1

3 months ago

0.3.4

3 months ago

0.3.3

3 months ago

0.2.6

3 months ago

0.2.5

3 months ago

0.2.3

4 months ago

0.2.4

4 months ago

0.2.1

4 months ago

0.2.2

4 months ago

0.2.0

4 months ago

0.1.8

5 months ago

0.1.7

5 months ago

0.1.9

4 months ago

0.1.6

7 months ago

0.1.4

8 months ago

0.1.3

8 months ago

0.1.5

8 months ago

0.1.2

9 months ago

0.1.1

9 months ago

0.1.0

11 months ago

0.0.9

1 year ago

0.0.8

1 year ago

0.0.7

1 year ago

0.0.5

2 years ago

0.0.6

2 years ago

0.0.4

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago