springload/draftail

Name: draftail

Owner: Springload

Description: :memo::cocktail: A configurable rich text editor based on Draft.js, built for Wagtail

Created: 2016-11-11 13:23:26.0

Updated: 2018-01-19 22:24:09.0

Pushed: 2018-01-20 12:23:28.0

Homepage: https://springload.github.io/draftail/

Size: 2329

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Draftail npm Build Status Coverage Status Wagtail

:memo::cocktail: A configurable rich text editor based on Draft.js, built for Wagtail.

Screenshot of Draftail

It?s developed alongside our Python Draft.js exporter, for integration into Wagtail. Check out the online demo!

Features

This project adheres to Semantic Versioning, and measures performance and code coverage.

Draftail aims for a mouse-free, keyboard-centric experience. Most formatting can be done by using common keyboard shortcuts, inspired by Google Docs and Markdown.

Here are important features worth highlighting:

Using Draftail

Have a look at the user guide.

Draftail is meant to be used in scenarios where not all formatting should be available, and where custom formatting can be necessary. Available formats, built-in and custom, can be specificed declaratively for each editor instance.

Built-in formats

Draftail does not come with built-in controls for things like images and links, so you can build your own exactly as you wish. This is particularly useful when integrating with content sources, like a CMS, an API, or other tools with a fixed schema.

Custom formats

Your mileage may vary! There is good support for custom block-level and inline formatting. Custom entities or decorators require knowledge of the Draft.js API, which is very low-level.

Getting started

First, grab the package from npm:

aftail's peerDependencies:
install --save draft-js@^0.10.4 react react-dom prop-types
install --save draftail

Import the styles for Draft.js, and the editor:

ort 'draft-js/dist/Draft';
ort 'draftail/dist/draftail';

Then, import the editor and use it in your code. Here is a simple example:

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

rt { DraftailEditor, BLOCK_TYPE, INLINE_STYLE } from 'draftail';

t initial = JSON.parse(sessionStorage.getItem('draftail:content'));

t onSave = content => {
console.log('saving', content);
sessionStorage.setItem('draftail:content', JSON.stringify(content));


t editor = (
<DraftailEditor
    rawContentState={initial || null}
    onSave={onSave}
    blockTypes={[
        { type: BLOCK_TYPE.HEADER_THREE, label: 'H3' },
        { type: BLOCK_TYPE.UNORDERED_LIST_ITEM, label: 'UL' },
    ]}
    inlineStyles={[
        { type: INLINE_STYLE.BOLD, label: 'B' },
        { type: INLINE_STYLE.ITALIC, label: 'I' },
    ]}
/>


tDOM.render(editor, document.querySelector('[data-mount]'));

Finally, be sure to check out the required polyfills.

Options and configuration

To change the behavior of the editor, pass props to DraftailEditor. Here are the available props, and their default values:

nitial content of the editor. Use this to edit pre-existing content.
ontentState: null,
alled when changes occured. Use this to persist editor content.
ve: () => {},
isplayed when the editor is empty. Hidden if the user changes styling.
eholder: null,
nable the use of horizontal rules in the editor.
leHorizontalRule: false,
nable the use of line breaks in the editor.
leLineBreak: false,
how undo control in the toolbar.
UndoControl: false,
how redo control in the toolbar.
RedoControl: false,
isable copy/paste of rich text in the editor.
pPastedStyles: true,
et whether spellcheck is turned on for your editor.
ee https://draftjs.org/docs/api-reference-editor.html#spellcheck.
lCheck: false,
ptionally set the overriding text alignment for this editor.
ee https://draftjs.org/docs/api-reference-editor.html#textalignment.
Alignment: null,
ptionally set the overriding text directionality for this editor.
ee https://draftjs.org/docs/api-reference-editor.html#textdirectionality.
Directionality: null,
et if auto capitalization is turned on and how it behaves.
ee https://draftjs.org/docs/api-reference-editor.html#autocapitalize-string.
Capitalize: null,
et if auto complete is turned on and how it behaves.
ee https://draftjs.org/docs/api-reference-editor.html#autocomplete-string.
Complete: null,
et if auto correct is turned on and how it behaves.
ee https://draftjs.org/docs/api-reference-editor.html#autocorrect-string.
Correct: null,
ist of the available block types.
kTypes: [],
ist of the available inline styles.
neStyles: [],
ist of the available entity types.
tyTypes: [],
ist of active decorators.
rators: [],
dditional React components to render in the toolbar.
rols: [],
ax level of nesting for list items. 0 = no nesting. Maximum = 10.
istNesting: 1,
requency at which to call the save callback (ms).
eSaveInterval: 250,
Formatting options

Draftail, like Draft.js, distinguishes between 3 content formats:

Configuring available formats

By default, the editor provides the least amount of rich text features. Formats have to be explicitly enabled by the developer, so they have as much control over what rich content is available as possible.

To use a given format, add it to the corresponding list, following the options detailed in the next sections.

ist of the available block types.
kTypes: [],
ist of the available inline styles.
neStyles: [],
ist of the available entity types.
tyTypes: [],
Blocks
nique type shared between block instances.
: PropTypes.string.isRequired,
escribes the block in the editor UI, concisely.
l: PropTypes.string,
escribes the block in the editor UI.
ription: PropTypes.string,
epresents the block in the editor UI.
: [...], // see documentation.
OM element used to display the block within the editor area.
ent: PropTypes.string,
Inline styles
nique type shared between inline style instances.
: PropTypes.string.isRequired,
escribes the inline style in the editor UI, concisely.
l: PropTypes.string,
escribes the inline style in the editor UI.
ription: PropTypes.string,
epresents the inline style in the editor UI.
: [...], // see documentation.
SS properties (in JS format) to apply for styling within the editor area.
e: PropTypes.Object,
Entities
nique type shared between entity instances.
: PropTypes.string.isRequired,
escribes the entity in the editor UI, concisely.
l: PropTypes.string,
escribes the entity in the editor UI.
ription: PropTypes.string,
epresents the entity in the editor UI.
: [...], // see documentation.
eact component providing the UI to manage entities of this type.
ce: PropTypes.func.isRequired,
eact component to display inline entities.
rator: PropTypes.func,
eact component to display block-level entities.
k: PropTypes.func,
rray of attributes the entity uses, to preserve when filtering entities on paste.
f undefined, all entity data is preserved.
ibutes: PropTypes.arrayOf(PropTypes.string),
ttribute - regex mapping, to whitelist entities based on their data on paste.
or example, { url: '^https:' } will only preserve links that point to HTTPS URLs.
elist: PropTypes.object,
Decorators
etermines which pieces of content are to be decorated.
tegy: PropTypes.func,
eact component to display the decoration.
onent: PropTypes.func,
Custom formats

Draftail is meant to provide a consistent editing experience regardless of what formats (blocks, inline styles, entities) are available. It should be simple for developers to enable/disable a certain format, or to create new ones.

Here are quick questions to help you determine which formatting to use, depending on the use case:

| In order to… | Use | | ————————————- | ————- | | Format a portion of a line | Inline styles | | Indicate the structure of the content | Blocks | | Enter additional data/metadata | Entities |

Custom inline styles

Custom inline styles only require a style prop, defining which CSS properties to apply when the format is active.

Here is a basic example:

neStyles={[
{
    label: 'Redacted',
    type: 'REDACTED',
    style: {
        backgroundColor: 'currentcolor',
    },
},

It is also possible to override the styling of predefined inline styles:

neStyles={[
{
    label: 'Bold',
    type: INLINE_STYLE.BOLD,
    style: {
        fontWeight: 'bold',
        textShadow: '1px 1px 1px black',
    },
},

Custom blocks

Simple blocks are very easy to create. Add a new block type to blockTypes, specifying which element to display the block as.

Here is an example, creating a “Tiny text” block:

kTypes={[
{
    type: 'tiny-text',
    label: 'Tiny',
    element: 'div',
},

You may also use CSS to style the block, via the Draftail-block--tiny-text class:

ftail-block--tiny-text {
font-size: 11px;
font-style: italic;

More advanced blocks, requiring custom React components, aren't supported at the moment. If you need this, feel free to create an issue.

Custom entity types

Creating custom entity types is a bit more involved because entities aren't simply on/off: they often need additional data (thus a UI to enter this data), and can be edited. The entity API is at a much lower level of abstraction than that of blocks and styles, and knowledge of the Draft.js API is expected.

Apart from the usual type/label/description/icon options, entities need:

Optionally, entities can also take an attributes and whitelist props. These can be used to determine whitelisting rules when pasting content into the editor, to only keep the entities considered valid. If undefined, all entities are always preserved with all of their data.


type: ENTITY_TYPE.IMAGE,
icon: '#icon-image',
// Preserve the src and alt attributes and no other.
attributes: ['src', 'alt'],
// Preserve images for which the src starts with "http".
whitelist: {
    src: '^http',
},

Sources

Sources are responsible for creating and editing entities, and are toggled when requested from the toolbar, or from a decorator or block. Here is a simple image source which uses window.prompt to ask the user for an image's src, then creates an entity and its atomic block:

rt { Component } from 'react';
rt { AtomicBlockUtils } from 'draft-js';

s ImageSource extends Component {
componentDidMount() {
    const { editorState, entityType, onComplete } = this.props;

    const src = window.prompt('Image URL');

    if (src) {
        const content = editorState.getCurrentContent();
        const contentWithEntity = content.createEntity(
            entityType.type,
            'IMMUTABLE',
            { src },
        );
        const entityKey = contentWithEntity.getLastCreatedEntityKey();
        const nextState = AtomicBlockUtils.insertAtomicBlock(
            editorState,
            entityKey,
            ' ',
        );

        onComplete(nextState);
    } else {
        onComplete(editorState);
    }
}

render() {
    return null;
}

The source component is given the following props:

he editorState is available for arbitrary content manipulation.
orState: PropTypes.object.isRequired,
akes the updated editorState, or null if there are no changes.
mplete: PropTypes.func.isRequired,
loses the source, without focusing the editor again.
ose: PropTypes.func.isRequired,
hole entityType configuration, as provided to the editor.
tyType: PropTypes.object.isRequired,
urrent entityKey to edit, if any.
tyKey: PropTypes.string,
urrent entity to edit, if any.
ty: PropTypes.object,
Decorators

Decorators render inline entities based on their data.

t Link = ({ entityKey, contentState, children }) => {
const { url } = contentState.getEntity(entityKey).getData();

return (
    <span title={url} className="Link">
        {children}
    </span>
);

They receive the following props:

ey of the entity being decorated.
tyKey: PropTypes.string.isRequired,
ull contentState, read-only.
entState: PropTypes.object.isRequired,
he decorated nodes / entity text.
dren: PropTypes.node.isRequired,
all with the entityKey to trigger the entity source.
it: PropTypes.func.isRequired,
all with the entityKey to remove the entity.
move: PropTypes.func.isRequired,

The onEdit and onRemove props are meant so decorators can also serve in managing entities, eg. to build tooltips to edit links.

Blocks

Blocks render block-level entities based on their data, and can contain editing controls. Here is a simple image block, rendering images with src and alt attributes:

rt React, { Component } from 'react';

s ImageBlock extends Component {
render() {
    const { blockProps } = this.props;
    const { entity } = blockProps;
    const { src, alt } = entity.getData();

    return <img className="ImageBlock" src={src} alt={alt} width="256" />;
}

They receive the following props:

he current atomic block.
k: PropTypes.object.isRequired,
kProps: PropTypes.shape({
// The editorState is available for arbitrary content manipulation.
editorState: PropTypes.object.isRequired,
// Current entity to manage.
entity: PropTypes.object.isRequired,
// Current entityKey to manage.
entityKey: PropTypes.string.isRequired,
// Whole entityType configuration, as provided to the editor.
entityType: PropTypes.object.isRequired,
// Make the whole editor read-only, except for the block.
lockEditor: PropTypes.func.isRequired,
// Make the editor editable again.
unlockEditor: PropTypes.func.isRequired,
// Shorthand to edit entity data.
onEditEntity: PropTypes.func.isRequired,
// Shorthand to remove an entity, and the related block.
onRemoveEntity: PropTypes.func.isRequired,
// Update the editorState with arbitrary changes.
onChange: PropTypes.func.isRequired,
sRequired,
Custom text decorators

Custom decorators follow the Draft.js CompositeDecorator API.

Other controls

Finally, Draftail also has an API to add arbitrary controls in the toolbar, via the controls prop. This prop takes an array of React components, which will be given a getEditorState function and the onChange handler as props.

Those controls can import the Icon and ToolbarButton components from Draftail if necessary.

UI and styling

Without custom controls, the editor has a very simple UI and its styles are relatively straightforward. To make sure everything works, use a CSS reset, like Normalize.css.

To tweak the editor UI, Draftail uses old-fashioned stable, namespaced class names that you are free to add more styles to. For example, the toolbar uses .Draftail-Toolbar.

Draftail also makes its Sass stylesheets available for extension:

ncrease the default editor z-index.
ftail-editor-z-index: 100;

mport all of the styles in your build.
ort 'draftail/lib/index';

lternatively, only import the constants to reuse them elsewhere in your project.
ort 'draftail/lib/api/constants';
Icons

Draftail can use icons to display each format in the toolbar. There are multiple possible formats:

The SVG has to use a 1024x1024 viewbox that will be downscaled to 16x16.

To deal with more complex requirements, the icon can be an arbitrary React component: <Icon icon={<CustomIcon icon="square" />.

Theming

If using Sass, you can get very far by adding new default values for the variables in _constants.scss. For example,

emove the editor's border and make the toolbar free-floating
ftail-editor-border: 0;
ftail-editor-padding: 0;
ftail-toolbar-radius: 5px;

ort 'draftail/lib/index';

Draftail also has a draftail-richtext-styles mixin you can use to make sure styles on rich text content don't leak to other parts of your site:

lude draftail-richtext-styles {
// Style Draft.js blockquotes.
blockquote {
    border-left: 1px solid #e5e5e5;
    padding: 0 0 0 20px;
    margin-left: 0;
    font-style: italic;
}

Without Sass, refer to class names starting with Draftail- or Draft / public-Draft (Draft.js)..

Other APIs
i18n - Internationalization

The editor comes with default labels in english, but all are overridable.

Depending on the target language, you may also need to use the textDirectionality prop (LTR or RTL). See the full Draft.js documentation for more details.

Managing focus

Draftail has a focus() API like that of Draft.js. Use it to imperatively move focus to the editor.

Browser support and polyfills

Supported browser / device versions:

| Browser | Device/OS | Version | | ————- | ————– | ——- | | Chrome | Windows, macOS | latest | | Firefox | Windows, macOS | latest | | MS Edge | Windows | latest | | Safari | macOS | latest | | Mobile Safari | iOS Phone | latest | | Mobile Safari | iOS Tablet | latest | | Chrome | Android | latest |

Polyfills

Draft.js and Draftail build upon ES6 language features. If targeting browsers that do not support them, have a look at:

The Draftail demo site lists minimum polyfills for IE11 support: examples/utils/polyfills.js.

Contributing

See anything you like in here? Anything missing? We welcome all support, whether on bug reports, feature requests, code, design, reviews, tests, documentation, and more. Please have a look at our contribution guidelines.

If you just want to set up the project on your own computer, the contribution guidelines also contain all of the setup commands.

Credits

Draftail is made possible by the work of Springload, a New Zealand digital agency, and core contributors to the Wagtail CMS. The beautiful demo site is the work of @thibaudcolas.

The demo site?s icons come from IcoMoon.

View the full list of contributors. MIT licensed. Website content available as CC0.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.