@4nduril/react-rte 中文文档教程
React Rich Text Editor
这是一个完全用 React 构建的 UI 组件,旨在成为一个完整的- 类似于 CKEditor、TinyMCE 和其他富文本“所见即所得”编辑器。 它基于来自 Facebook 的出色的开源 Draft.js,该文件经过了性能和生产测试。
Demo
在此处试用编辑器:react-rte.org/demo
Getting Started
$ npm install --save react-rte
RichTextEditor
为主编辑器成分。 它由 Draft.js
、一些 UI 组件(例如工具栏)和一些有关使用 HTML/Markdown 获取和设置内容的有用抽象组成。
RichTextEditor
被设计成像 textarea
一样使用,除了 value
不是一个字符串,它是一个带有 toString就可以了。 使用
createValueFromString(markup, 'html')
从字符串创建一个 value
也很容易。
Browser Compatibility
脚本由 Babel 转译为 ES6。 此外,该软件包的至少一个依赖项不支持 IE。 因此,为了支持 IE 和 back-plat,您需要在 HTML 中包含一些 polyfill(#74、#196、#203):
Required Webpack configuration
如果你没有使用Webpack,你可以跳过这部分。 Node.js 环境中的同构/服务器端渲染支持需要 Webpack。
'react-rte'
包含一个已经使用 webpack 构建(使用 CSS)的包,并且不打算被 webpack 再次使用。 因此,如果您使用的是 webpack,则必须从 react-rte/lib/RichTextEditor
导入 RichTextEditor,以便获得 webpack 可以与您的应用程序捆绑的未捆绑脚本。
如果你正在使用 webpack,你必须添加一个 css 加载器,否则你的 webpack 构建将失败。 例如:
{
test: /\.css$/,
loaders: [
'style-loader',
'css-loader?modules'
]
},
Example Usage:
这个例子使用了更新的 JavaScript 和 JSX。 有关旧 JavaScript 中的示例,请参见下文。
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'}
]
};
return (
<RichTextEditor toolbarConfig={toolbarConfig} />
);
}
Motivation
简而言之,这是一种 2016 年的富文本编辑方法,它建立在久经沙场的现代组件之上,重要的是,我们不将文档状态存储在 DOM 中,从而消除了整类常见的“所见即所得”问题。
此编辑器基于来自 Facebook 的 Draft.js 构建。 Draft.js 更像是一个低级框架(contentEditable
抽象),但是这个组件旨在成为一个完全完善的 UI 组件,当您需要替换 < 时,您可以使用它;textarea/>
在您的应用程序中支持粗体、斜体、链接、列表等
。Draft.js 中的数据模型允许我们以一种几乎与视图/渲染层无关的方式来表示文档或您选择的文本表示形式(html/markdown)。 此数据模型封装了编辑器的内容/状态,并且基于 Immutable.js 以兼顾性能和易于使用理由。
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
不同于典型的富文本编辑器(例如 CKEditor 和 TinyMCE) 我们将内容状态保存在结构良好的数据模型中,而不是在视图中。 将我们的数据模型与我们的视图分开的一个重要优势是确定性输出。
比方说,您选择了一些文本并添加了粗体样式。 然后你添加斜体样式。 或者,如果您先添加斜体然后再添加粗体会怎么样。 结果应该是相同的:文本范围同时具有粗体和斜体样式。 但是在浏览器的视图(文档对象模型)中,这是用 内部的
表示的,反之亦然? 这是否取决于您添加样式的顺序? 在许多基于网络的编辑器中,HTML 输出确实取决于您的操作顺序。 这意味着您的输出是不确定的。 在编辑器中看起来完全相同的两个文档将具有不同的、有时是不可预测的 HTML 表示。
在此编辑器中,我们使用纯确定性函数将文档状态转换为 HTML 输出。 无论您如何到达该状态,输出都是可预测的。 这使得一切都更容易推理。 在我们的例子中, 每次都会进入
。
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 asRichTextEditor.createEmptyValue()
orRichTextEditor.createValueFromString(markup, 'html')
.onChange
: A function that will be called with the "value" of the editor whenever it is changed. The value has atoString
method which accepts a singleformat
argument (either 'html' or 'markdown').
Other Props
您可以传递给 Draft.js Editor
的所有道具都可以传递给 RichTextEditor
(editorState
除外,它将根据内部生成value
属性)。
autoFocus
: Setting this to true will automatically focus input into the editor when the component is mountedplaceholder
: A string to use as placeholder text for theRichTextEditor
.readOnly
: A boolean that determines if theRichTextEditor
should render static html.
EditorValue Class
在 Draft.js 中,EditorState
不仅包含文档内容,还包含编辑器的整个状态,包括光标位置和选择。 这有很多好处,包括撤消/重做。 为了让您更轻松,我们将编辑器的状态包装在一个 EditorValue
实例中,并使用有用的方法将其转换为 HTML 或 Markdown。 此类的实例应传递给 value
属性中的 RichTextEditor
。
EditorValue
类内置了某些优化。假设您要在视图中显示编辑器内容的 HTML。 如果您更改光标位置,将触发 onChange
事件(因为请记住,光标位置是 EditorState
的一部分)并且您需要调用 toString( )
来呈现您的视图。 但是,EditorValue
足够聪明,知道自上次 toString()
以来 content 实际上并没有改变,因此它将返回缓存版本HTML。
优化提示:尝试仅在实际需要将其转换为字符串时才调用 editorValue.toString()
。 如果您可以在不调用 toString
的情况下继续传递 editorValue
,它将非常高效。
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')
);
}
}
});
TODO
- Support images
- Better test coverage
- Documentation for using this editor in your projects
- Fix some issues with Markdown parsing (migrate to
remark
parser) - Internationalization
- Better icons and overall design
Known Limitations
目前最大的限制是不支持图片。 有计划支持内联图像(使用装饰器)并最终支持中型块级图像(使用自定义块渲染器)。
其他限制包括缺少的功能,例如:文本对齐和文本颜色。 这些即将推出。
React 之前的 v15 将记录以下多余的警告:
一个组件是 contentEditable 并且包含由 反应。 现在你有责任保证没有 这些节点被意外修改或复制。 这是 可能不是故意的。
由于所有节点均由 Draft 内部管理,因此这不是问题,并且可以安全地忽略此警告。 您可以在实例化您的组件之前通过鸭子打孔 console.error
完全抑制此警告的显示:
console.error = (function(_error) {
return function(message) {
if (typeof message !== 'string' || message.indexOf('component is `contentEditable`') === -1) {
_error.apply(console, arguments);
}
};
})(console.error);
Contribute
我很乐意接受错误修复和改进(和测试)的拉取请求。 如果你有一个你想要实现的特性,那么首先打开一个问题看看它是否已经在处理中可能是个好主意。 请匹配项目其余部分的代码风格(ESLint 应强制执行此操作)并请包含测试。 谢谢!
Run the Demo
克隆这个项目。 运行 <代码>npm 安装。 运行 npm run build-dist
然后将您选择的服务器(如 serv)指向 <代码>/demo.html。
License
此软件已获得 ISC 许可。
React Rich Text Editor
This is a UI component built completely in React that is meant to be a full-featured textarea replacement similar to CKEditor, TinyMCE and other rich text "WYSIWYG" editors. It's based on the excellent, open source Draft.js from Facebook which is performant and production-tested.
Demo
Try the editor here: react-rte.org/demo
Getting Started
$ npm install --save 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')
.
Browser Compatibility
The scripts are transpiled by Babel to ES6. Additionally, at least one of this package's dependencies does not support IE. So, for IE and back-plat support you will need to include some polyfill in your HTML (#74, #196, #203): <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=String.prototype.startsWith,Array.from,Array.prototype.fill,Array.prototype.keys,Array.prototype.findIndex,Number.isInteger&flags=gated"></script>
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'}
]
};
return (
<RichTextEditor toolbarConfig={toolbarConfig} />
);
}
Motivation
In short, this is a 2016 approach to rich text editing built on modern, battle-hardened components and, importantly, we do not store document state in the DOM, eliminating entire classes of common "WYSIWYG" problems.
This editor is built on Draft.js from Facebook. Draft.js is more of a low-level framework (contentEditable
abstraction), however this component is intended to be a fully polished UI component that you can reach for when you need to replace a <textarea/>
in your application to support bold, italic, links, lists, etc.
The data model in Draft.js allows us to represent the document in a way that is mostly agnostic to the view/render layer or the textual representation (html/markdown) you choose. This data model encapsulates the content/state of the editor and is based on Immutable.js to be both performant and easy to reason about.
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 asRichTextEditor.createEmptyValue()
orRichTextEditor.createValueFromString(markup, 'html')
.onChange
: A function that will be called with the "value" of the editor whenever it is changed. The value has atoString
method which accepts a singleformat
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 mountedplaceholder
: A string to use as placeholder text for theRichTextEditor
.readOnly
: A boolean that determines if theRichTextEditor
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')
);
}
}
});
TODO
- Support images
- Better test coverage
- Documentation for using this editor in your projects
- Fix some issues with Markdown parsing (migrate to
remark
parser) - Internationalization
- Better icons and overall design
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);
Contribute
I'm happy to take pull requests for bug-fixes and improvements (and tests). If you have a feature you want to implement it's probably a good idea to open an issue first to see if it's already being worked on. Please match the code style of the rest of the project (ESLint should enforce this) and please include tests. Thanks!
Run the Demo
Clone this project. Run npm install
. Run npm run build-dist
then point the server of your choice (like serv) to /demo.html
.
License
This software is ISC Licensed.