Skip to content

Commit 9cd38fc

Browse files
authored
Merge pull request #9 from HubSpot/to-html-middleware
Middleware conversion configurations
2 parents 9eb63dc + 4a29280 commit 9cd38fc

File tree

11 files changed

+191
-89
lines changed

11 files changed

+191
-89
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
***
1414

1515
## Overview
16-
Draft Extend is a platform to build a full-featured Draft.js editor using modular plugins that can integrate with [draft-convert](#) to serialize with HTML. The higher-order function API makes it extremely easy to use any number of plugins for rendering and conversion.
16+
Draft Extend is a platform to build a full-featured Draft.js editor using modular plugins that can integrate with [draft-convert](http://github.com/HubSpot/draft-convert) to serialize with HTML. The higher-order function API makes it extremely easy to use any number of plugins for rendering and conversion.
1717

1818
#### Usage:
1919
```javascript

building-plugins.md

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ const htmlToEntity = (nodeName, node) => {
7272
// Convert entities to HTML for output
7373
const entityToHTML = (entity, originalText) => {
7474
if (entity.type === ENTITY_TYPE) {
75-
return `<a href="${entity.data.href}" target="${entity.data.target}">${originalText}</a>`;
75+
return (
76+
<a href={entity.data.href} target={entity.data.target}>
77+
{originalText}
78+
</a>
79+
);
7680
}
7781
return originalText;
7882
};
@@ -90,8 +94,8 @@ export default LinkPlugin;
9094

9195
## createPlugin
9296
Factory function to create plugins. `createPlugin` takes one `options` object argument. All properties of `options` are optional.
93-
#### Plugin options
94-
**Editor rendering options**
97+
### Plugin options
98+
#### Editor rendering options
9599
- `displayName: string` - `displayName` of the higher-order component when wrapping around `Editor`.
96100
- default: `'Plugin'`
97101
- `buttons: Array<Component> | Component` - Zero or more button components to add to the Editor. If only one button is needed it may be passed by itself without an array. See [Buttons & Overlays](#buttons--overlays) for more information on props and usage.
@@ -109,36 +113,52 @@ Factory function to create plugins. `createPlugin` takes one `options` object ar
109113
- `keyBindingFn: function(e: SyntheticKeyboardEvent): ?string` - Function to assign named key commands to key events on the editor. Works as described in [Draft.js' key bindings documentation](https://facebook.github.io/draft-js/docs/advanced-topics-key-bindings.html). If the plugin should not name the key command it may return `undefined`. Note that if no plugin names a key commmand the `Editor` component will fall back to `Draft.getDefaultKeyBinding`.
110114
- `keyCommandListener: function(editorState: EditorState, command: string, keyboardEvent: SyntheticKeyboardEvent): boolean | EditorState` - Function to handle key commands without using a button or overlay component.
111115

112-
**Conversion Options** - Plugins can include options to handle serialization and deserialization of their functionality to and from HTML.
113-
- `styleToHTML: {[inlineStyleType: string]: MarkupObject}` - Object mapping inline style types to HTML markup for output. A `MarkupObject` is an object of shape `{start, end}`, for example:
114-
```javascript
115-
const styleToHTML = {
116-
'BOLD': {
116+
#### Conversion Options
117+
Plugins can include options to handle serialization and deserialization of their functionality to and from HTML.
118+
119+
**Middleware usage**
120+
`draft-extend` conversion options are all [middleware functions](http://redux.js.org/docs/advanced/Middleware.html) that allow plugins to transform the result of those that were composed before it. An example plugin leveraging middleware is a block alignment plugin that adds an `align` property to the block's metadata. This plugin should add a `text-align: right` style to any block with the property regardless of block type. Transforming the result of `next(block)` instead of building markup from scratch allows the plugin to only apply the changes it needs to. If middleware functionality is not necessary, any conversion option may omit the higher-order function receiving `next` and merely return `null` or `undefined` to defer the entire result to subsequent plugins.
121+
```javascript
122+
const AlignmentPlugin = createPlugin({
123+
...
124+
blockToHTML: (next) => (block) => {
125+
const result = next(block);
126+
if (block.data.align && React.isValidElement(result)) {
127+
const style = result.props.style || {};
128+
style.textAlign = block.data.align;
129+
return React.cloneElement(result, {style});
130+
}
131+
return result;
132+
}
133+
});
134+
```
135+
136+
**Options**
137+
- `styleToHTML: (next: function) => (style: string) => (ReactElement | MarkupObject)` - Function that takes inline style types and returns an empty `ReactElement` (most likely created via JS) or HTML markup for output. A `MarkupObject` is an object of shape `{start, end}`, for example:
138+
```javascript
139+
const styleToHTML = (style) => {
140+
if (style === 'BOLD') {
141+
return {
117142
start: '<strong>',
118143
end: '</strong>'
119-
}
120-
};
121-
```
122-
- default: `{}`
123-
- `blockToHTML: {[blockType: string]: BlockMarkupObject}` - Object mapping block types to HTML markup for output. A `BlockMarkupObject` is identical to `MarkupObject` with the exception of nestable blocks: `ordered-list-item` and `unordered-list-item`. These block types include properties for handling nesting. The default values for `ordered-list-item` are:
124-
```
125-
{
126-
start: '<li>',
127-
end: '</li>',
128-
nestStart: '<ol>',
129-
nestEnd: '</ol>'
144+
};
130145
}
131-
```
132-
- default: `{}`
133-
- `entityToHTML: function(entity: RawEntity, originalText: string): string` - Function to transform instances into HTML output. A `RawEntity` is an object of shape `{type: string, mutability: string, data: object}`.
134-
- default: `(entity, originalText) => originalText`
135-
- `htmlToStyle: function(nodeName: string, node: Node, currentStyle: OrderedSet): OrderedSet` - Function that is passed an HTML Node and the current `Immutable.OrderedSet` of inline styles applied. It should return a transformed list of styles to be applied to all children of the node. The function will be invoked on all HTML nodes in the input.
136-
- default: `(nodeName, node, currentStyle) => currentStyle`
137-
- `htmlToBlock: function(nodeName: string, node: Node): string` - Function that inspects a block level HTML Node and can return a custom ContentBlock type to assign to the block. If no custom type should be used it may return nothing.
138-
- default: `() => {}`
139-
- `htmlToEntity: function(nodeName: string, node: Node): string` - Function that inspects an HTML Node and converts it to any Draft.js Entity that should be applied to the contents of the Node. If an Entity should be applied this function should call `Entity.create()` and return the string return value that is the Entity instance's key. If no entity is necessary it may return nothing.
140-
- default: `() => {}`
141-
- `textToEntity: function(text): Array<TextEntityObject>` - Function to convert plain input text to entities. Note that `textToEntity` is invoked with the value of each individual text DOM Node. For example, with input `<div>node one<span>node two</span></div>` `textToEntity` will be called with strings `'node one'` and `'node two'`. This function generally uses a regular expression to return an array of as many `TextEntityObject`s as necessary for the text string. A `TextEntityObject` is an object with properties:
146+
};
147+
```
148+
- `blockToHTML: (next: function) => (block: RawBlock) => (ReactElement | {element: ReactElement, nest?: ReactElement} | BlockMarkupObject)` - Function accepting a raw block object and returning `ReactElement`s or HTML markup for output. If using `ReactElement`s as return values for nestable blocks (`ordered-list-item` and `unordered-list-item`), a `ReactElement` for both the wrapping element and the block being nested may be included in an object of shape `{element, nest}`. A `BlockMarkupObject` is identical to `MarkupObject` with the exception of nestable blocks. These block types include properties for handling nesting. The default values for `ordered-list-item` are:
149+
```javascript
150+
{
151+
start: '<li>',
152+
end: '</li>',
153+
nestStart: '<ol>',
154+
nestEnd: '</ol>'
155+
}
156+
```
157+
- `entityToHTML: (next: function) => (entity: RawEntity, originalText: string): (ReactElement | MarkupObject | string)` - Function to transform instances into HTML output. A `RawEntity` is an object of shape `{type: string, mutability: string, data: object}`. If the returned `ReactElement` contains no children it will be wrapped around `originalText`. A `MarkupObject` will also be wrapped around `orignalText`.
158+
- `htmlToStyle: (next: function) => (nodeName: string, node: Node) => OrderedSet` - Function that is passed an HTML Node. It should return a list of styles to be applied to all children of the node. The function will be invoked on all HTML nodes in the input.
159+
- `htmlToBlock: (next: function) => (nodeName: string, node: Node) => RawBlock | string` - Function that inspects an HTML `Node` and can return data to assign to the block in the shape `{type, data}`. If no data is necessary a block type may be returned as a string. If no custom type should be used it may return `null` or `undefined`.
160+
- `htmlToEntity: (next: function) => (nodeName: string, node: Node) => ?string` - Function that inspects an HTML Node and converts it to any Draft.js Entity that should be applied to the contents of the Node. If an Entity should be applied this function should call `Entity.create()` and return the string return value that is the Entity instance's key. If no entity is necessary it may return nothing.
161+
- `textToEntity: (next: function) => (text: string) => Array<TextEntityObject>` - Function to convert plain input text to entities. Note that `textToEntity` is invoked with the value of each individual text DOM Node. For example, with input `<div>node one<span>node two</span></div>` `textToEntity` will be called with strings `'node one'` and `'node two'`. Implementations of this function generally uses a regular expression to return an array of as many `TextEntityObject`s as necessary for the text string. A `TextEntityObject` is an object with properties:
142162
- `offset: number`: Offset of the entity to add within `text`
143163
- `length: number`: Length of the entity starting at `offset` within `text`
144164
- `result: string`: If necessary, a new string to replace the substring in `text` defined by `offset` and `length`. If omitted from the object no replacement will be made.
@@ -166,15 +186,15 @@ A collection of useful functions for building plugins.
166186
- `camelCaseToHyphen: function(camelCase: string): string` - Converts a camelCased word to a hyphenated-string. Used in `styleObjectToString`.
167187
- `styleObjectToString: function(styles: object): string` - Converts a style object (i.e. object passed into the `style` prop of a React component) to a CSS string for use in a `style` HTML attribute. Useful for converting inline style types to HTML while keeping a single source of truth for the style for both `styleMap` and `styleToHTML`.
168188
- `entityStrategy: function(entityType: string): function(contentBlock, callback)` - factory function to generate decorator strategies to decorate all instances of a given entity type. For example:
169-
```
170-
const MyPlugin = createPlugin({
171-
...
172-
decorators: {
173-
strategy: entityStrategy('myEntity'),
174-
component: MyEntityComponent
175-
}
176-
});
177-
```
189+
```javascript
190+
const MyPlugin = createPlugin({
191+
...
192+
decorators: {
193+
strategy: entityStrategy('myEntity'),
194+
component: MyEntityComponent
195+
}
196+
});
197+
```
178198
- `getEntitySelection: function(editorState: EditorState, entityKey: string): SelectionState` - Returns the selection of an Entity instance in `editorState` with key `entityKey`.
179199
- `getSelectedInlineStyles: function(editorState): Set` - Returns a `Set` of all inline style types matched by any character in the current selection. Ths acts differently from `EditorState.getCurrentInlineStyle()` when the selection is not collapsed since `getSelectedInlineStyles` will include styles from every character in the selection instead of the single character at the focus index of the selection.
180200
- `getActiveEntity(editorState: EditorState): ?string` - Returns the key for the Entity that the current selection start is within, if it exists. Returns `undefined` if the selection start is not within an entity range.

example/blockStyles.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="utf-8" />
55
<title>draft-extend</title>
66
<link rel="stylesheet" href="../node_modules/draft-js/dist/Draft.css" />
7-
<link rel="stylesheet" href="../build/draft-extend.css" />
7+
<link rel="stylesheet" href="../dist/draft-extend.css" />
88
<style>
99
#target {
1010
font-family: sans-serif;
@@ -17,12 +17,13 @@
1717
<div id="target"></div>
1818
<script src="../node_modules/react/dist/react.min.js"></script>
1919
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
20+
<script src="../node_modules/react-dom/dist/react-dom-server.min.js"></script>
2021
<script src="../node_modules/immutable/dist/immutable.min.js"></script>
2122
<script src="../node_modules/es6-shim/es6-shim.js"></script>
2223
<script src="../node_modules/babel-standalone/babel.min.js"></script>
2324
<script src="../node_modules/draft-js/dist/Draft.js"></script>
2425
<script src="../dist/draft-extend.js"></script>
25-
<script src="../node_modules/draft-convert/build/draft-convert.js"></script>
26+
<script src="../node_modules/draft-convert/dist/draft-convert.js"></script>
2627
<script src="./ToolbarButton.js"></script>
2728
<script type="text/babel">
2829
const {

example/inlineStyles.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<div id="target"></div>
1818
<script src="../node_modules/react/dist/react.min.js"></script>
1919
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
20+
<script src="../node_modules/react-dom/dist/react-dom-server.min.js"></script>
2021
<script src="../node_modules/immutable/dist/immutable.min.js"></script>
2122
<script src="../node_modules/es6-shim/es6-shim.js"></script>
2223
<script src="../node_modules/babel-standalone/babel.min.js"></script>
@@ -48,6 +49,15 @@
4849
{label: 'Strikethrough', style: 'STRIKETHROUGH'}
4950
];
5051

52+
const styleToHTML = (style) => {
53+
if (style === 'STRIKETHROUGH') {
54+
return <s />;
55+
}
56+
if (style === 'CODE') {
57+
return <div style={{fontFamily: 'monospace'}} />;
58+
}
59+
};
60+
5161
const createInlineStyle = ({label, style}) => {
5262
return ({editorState, onChange}) => {
5363
const toggleStyle = () => {
@@ -74,7 +84,8 @@
7484
};
7585

7686
const InlinePlugin = createPlugin({
77-
buttons: INLINE_STYLES.map(createInlineStyle)
87+
buttons: INLINE_STYLES.map(createInlineStyle),
88+
styleToHTML
7889
});
7990

8091
const WithPlugin = InlinePlugin(Editor);

example/link.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<div id="target"></div>
1818
<script src="../node_modules/react/dist/react.min.js"></script>
1919
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
20+
<script src="../node_modules/react-dom/dist/react-dom-server.min.js"></script>
2021
<script src="../node_modules/immutable/dist/immutable.min.js"></script>
2122
<script src="../node_modules/es6-shim/es6-shim.js"></script>
2223
<script src="../node_modules/babel-standalone/babel.min.js"></script>

example/mention.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<div id="target"></div>
1818
<script src="../node_modules/react/dist/react.min.js"></script>
1919
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
20+
<script src="../node_modules/react-dom/dist/react-dom-server.min.js"></script>
2021
<script src="../node_modules/immutable/dist/immutable.min.js"></script>
2122
<script src="../node_modules/es6-shim/es6-shim.js"></script>
2223
<script src="../node_modules/babel-standalone/babel.min.js"></script>
@@ -219,6 +220,7 @@
219220
style={{
220221
margin: '0',
221222
padding: '0',
223+
fontFamily: 'sans-serif'
222224
}}
223225
>
224226
<li
@@ -262,7 +264,8 @@
262264
<ul
263265
style={{
264266
margin: '0',
265-
padding: '0'
267+
padding: '0',
268+
fontFamily: 'sans-serif'
266269
}}
267270
>
268271
{this.renderResults()}

example/token.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<div id="target"></div>
1818
<script src="../node_modules/react/dist/react.min.js"></script>
1919
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
20+
<script src="../node_modules/react-dom/dist/react-dom-server.min.js"></script>
2021
<script src="../node_modules/immutable/dist/immutable.min.js"></script>
2122
<script src="../node_modules/es6-shim/es6-shim.js"></script>
2223
<script src="../node_modules/babel-standalone/babel.min.js"></script>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"babel-preset-es2015": "^6.6.0",
4242
"babel-preset-react": "^6.5.0",
4343
"babel-standalone": "^6.7.7",
44-
"draft-convert": "^1.2.0",
44+
"draft-convert": "^1.3.1",
4545
"draft-js": "^0.8.1",
4646
"es6-shim": "^0.35.0",
4747
"react": "^15.0.2",

0 commit comments

Comments
 (0)