Skip to content

Commit d477c24

Browse files
committed
Initial commit of patched version
Contains changes from sstur/draft-js-utils#155 to allow multiple inlineStyles to be returned by `customInlineFn`.
0 parents  commit d477c24

15 files changed

+2406
-0
lines changed

LICENSE

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ISC License
2+
3+
Copyright (c) 2016, Simon Sturmer <[email protected]>
4+
5+
Permission to use, copy, modify, and/or distribute this software for any
6+
purpose with or without fee is hereby granted, provided that the above
7+
copyright notice and this permission notice appear in all copies.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# DraftJS: Import Element to ContentState
2+
3+
This is a module for [DraftJS](https://github.com/facebook/draft-js) that will convert an HTML DOM Element to editor content.
4+
5+
It was extracted from [React-RTE](https://react-rte.org) and placed into a separate module for more general use. Hopefully it can be helpful in your projects.
6+
7+
## Installation
8+
9+
npm install --save draft-js-import-element
10+
11+
This project is still under development. If you want to help out, please open an issue to discuss or join us on [Slack](https://draftjs.slack.com/).
12+
13+
## Usage
14+
15+
`stateFromElement` takes a DOM node `element` and returns a DraftJS [ContentState](https://facebook.github.io/draft-js/docs/api-reference-content-state.html).
16+
17+
```js
18+
import {stateFromElement} from 'draft-js-import-element';
19+
const contentState = stateFromElement(element);
20+
```
21+
22+
### Options
23+
24+
You can optionally pass a second `Object` argument to `stateFromElement` with the following supported properties:
25+
26+
- `customBlockFn`: Function to specify block type/data based on HTML element. Example:
27+
```js
28+
let options = {
29+
// Should return null/undefined or an object with optional: type (string); data (plain object)
30+
customBlockFn: (element) => {
31+
if (element.tagName === 'ARTICLE') {
32+
return {type: 'custom-block-type'};
33+
}
34+
if (element.tagName === 'CENTER') {
35+
return {data: {align: 'center'}};
36+
}
37+
},
38+
};
39+
let contentState = stateFromElement(element, options);
40+
```
41+
42+
- `customInlineFn`: Function to specify how inline elements are handled. Example:
43+
44+
```js
45+
let options = {
46+
// Should return a Style() or Entity() or null/undefined
47+
customInlineFn: (element, {Style, Entity}) => {
48+
if (element.tagName === 'SPAN' && element.className === 'emphasis') {
49+
return Style('ITALIC');
50+
} else if (element.tagName === 'IMG') {
51+
return Entity('IMAGE', {src: element.getAttribute('src')});
52+
}
53+
},
54+
};
55+
let contentState = stateFromElement(element, options);
56+
```
57+
58+
- `elementStyles`: HTML element name as key, DraftJS style string as value. Example:
59+
```js
60+
stateFromElement(element, {
61+
elementStyles: {
62+
// Support `<sup>` (superscript) inline element:
63+
'sup': 'SUPERSCRIPT',
64+
},
65+
});
66+
```
67+
68+
## License
69+
70+
This software is [BSD Licensed](/LICENSE).
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
2+
3+
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
4+
5+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { keys.push.apply(keys, Object.getOwnPropertySymbols(object)); } if (enumerableOnly) keys = keys.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); return keys; }
6+
7+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
8+
9+
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
10+
11+
var _global = global,
12+
describe = _global.describe,
13+
it = _global.it,
14+
expect = _global.expect;
15+
import stateFromElement from '../stateFromElement';
16+
import { TextNode, ElementNode } from 'synthetic-dom';
17+
import { convertToRaw } from 'draft-js';
18+
import fs from 'fs';
19+
import { join } from 'path'; // This separates the test cases in `data/test-cases.txt`.
20+
21+
var SEP = '\n\n#';
22+
var testCasesRaw = fs.readFileSync(join(__dirname, '..', '..', 'test', 'test-cases.txt'), 'utf8');
23+
var testCases = testCasesRaw.slice(1).trim().split(SEP).map(function (text) {
24+
var lines = text.split('\n');
25+
var description = lines.shift().trim();
26+
var state = removeBlockKeys(JSON.parse(lines[0]));
27+
var html = lines.slice(1).join('\n');
28+
return {
29+
description: description,
30+
state: state,
31+
html: html
32+
};
33+
});
34+
describe('stateFromElement', function () {
35+
it('should create content state', function () {
36+
var textNode = new TextNode('Hello World');
37+
var element = new ElementNode('div', [], [textNode]);
38+
var contentState = stateFromElement(element);
39+
var rawContentState = removeBlockKeys(convertToRaw(contentState));
40+
expect(rawContentState).toEqual({
41+
entityMap: {},
42+
blocks: [{
43+
text: 'Hello World',
44+
type: 'unstyled',
45+
data: {},
46+
depth: 0,
47+
inlineStyleRanges: [],
48+
entityRanges: []
49+
}]
50+
});
51+
});
52+
it('should support option customBlockFn (type)', function () {
53+
var textNode = new TextNode('Hello World');
54+
var element = new ElementNode('center', [], [textNode]);
55+
var options = {
56+
customBlockFn: function customBlockFn(element) {
57+
var tagName = element.tagName;
58+
59+
if (tagName === 'CENTER') {
60+
return {
61+
type: 'center-align'
62+
};
63+
}
64+
}
65+
};
66+
var contentState = stateFromElement(element, options);
67+
var rawContentState = removeBlockKeys(convertToRaw(contentState));
68+
expect(rawContentState).toEqual({
69+
entityMap: {},
70+
blocks: [{
71+
text: 'Hello World',
72+
type: 'center-align',
73+
data: {},
74+
depth: 0,
75+
inlineStyleRanges: [],
76+
entityRanges: []
77+
}]
78+
});
79+
});
80+
it('should support option customBlockFn (data)', function () {
81+
var textNode = new TextNode('Hello World');
82+
var element = new ElementNode('p', [{
83+
name: 'align',
84+
value: 'right'
85+
}], [textNode]);
86+
var options = {
87+
customBlockFn: function customBlockFn(element) {
88+
var tagName = element.tagName;
89+
90+
if (tagName === 'P' && element.getAttribute('align') === 'right') {
91+
return {
92+
data: {
93+
textAlign: 'right'
94+
}
95+
};
96+
}
97+
}
98+
};
99+
var contentState = stateFromElement(element, options);
100+
var rawContentState = removeBlockKeys(convertToRaw(contentState));
101+
expect(rawContentState).toEqual({
102+
entityMap: {},
103+
blocks: [{
104+
text: 'Hello World',
105+
type: 'unstyled',
106+
data: {
107+
textAlign: 'right'
108+
},
109+
depth: 0,
110+
inlineStyleRanges: [],
111+
entityRanges: []
112+
}]
113+
});
114+
});
115+
it('should support option customInlineFn', function () {
116+
var element = new ElementNode('div', [], [new ElementNode('span', [{
117+
name: 'class',
118+
value: 'bold'
119+
}], [new TextNode('Hello')]), new ElementNode('span', [{
120+
name: 'class',
121+
value: 'link'
122+
}], [new TextNode('World')])]);
123+
var options = {
124+
customInlineFn: function customInlineFn(el, _ref) {
125+
var Style = _ref.Style,
126+
Entity = _ref.Entity;
127+
128+
if (el.tagName === 'SPAN' && el.className === 'bold') {
129+
return Style('BOLD');
130+
}
131+
132+
if (el.tagName === 'SPAN' && el.className === 'link') {
133+
return Entity('LINK', {
134+
url: '/abc'
135+
});
136+
}
137+
}
138+
};
139+
var contentState = stateFromElement(element, options);
140+
var rawContentState = removeBlockKeys(convertToRaw(contentState));
141+
expect(rawContentState).toEqual({
142+
entityMap: _defineProperty({}, 0, {
143+
type: 'LINK',
144+
mutability: 'MUTABLE',
145+
data: {
146+
url: '/abc'
147+
}
148+
}),
149+
blocks: [{
150+
text: 'HelloWorld',
151+
type: 'unstyled',
152+
depth: 0,
153+
inlineStyleRanges: [{
154+
offset: 0,
155+
length: 5,
156+
style: 'BOLD'
157+
}],
158+
entityRanges: [{
159+
offset: 5,
160+
length: 5,
161+
key: 0
162+
}],
163+
data: {}
164+
}]
165+
});
166+
});
167+
it('should support option elementStyles', function () {
168+
var textNode = new TextNode('Superscript');
169+
var element = new ElementNode('sup', [], [textNode]);
170+
var wrapperElement = new ElementNode('div', [], [element]);
171+
var options = {
172+
elementStyles: {
173+
sup: 'SUPERSCRIPT'
174+
}
175+
};
176+
var contentState = stateFromElement(wrapperElement, options);
177+
var rawContentState = removeBlockKeys(convertToRaw(contentState));
178+
expect(rawContentState).toEqual({
179+
entityMap: {},
180+
blocks: [{
181+
text: 'Superscript',
182+
type: 'unstyled',
183+
data: {},
184+
depth: 0,
185+
inlineStyleRanges: [{
186+
offset: 0,
187+
length: 11,
188+
style: 'SUPERSCRIPT'
189+
}],
190+
entityRanges: []
191+
}]
192+
});
193+
});
194+
it('should support images', function () {
195+
var imageNode = new ElementNode('img', [{
196+
name: 'src',
197+
value: 'imgur.com/asdf.jpg'
198+
}]);
199+
var wrapperElement = new ElementNode('div', [], [imageNode]);
200+
var contentState = stateFromElement(wrapperElement);
201+
var rawContentState = removeBlockKeys(convertToRaw(contentState));
202+
expect(rawContentState).toEqual({
203+
blocks: [{
204+
data: {},
205+
depth: 0,
206+
entityRanges: [{
207+
key: 0,
208+
length: 1,
209+
offset: 0
210+
}],
211+
inlineStyleRanges: [],
212+
text: ' ',
213+
type: 'unstyled'
214+
}],
215+
entityMap: {
216+
// This is necessary due to flow not supporting non-string literal property keys
217+
// eslint-disable-next-line quote-props
218+
'0': {
219+
data: {
220+
src: 'imgur.com/asdf.jpg'
221+
},
222+
mutability: 'MUTABLE',
223+
type: 'IMAGE'
224+
}
225+
}
226+
});
227+
});
228+
});
229+
describe('stateFromHTML', function () {
230+
testCases.forEach(function (testCase) {
231+
var description = testCase.description,
232+
state = testCase.state,
233+
html = testCase.html;
234+
it("should render ".concat(description), function () {
235+
var element = parseHTML(html);
236+
var actualState = removeBlockKeys(convertToRaw(stateFromElement(element)));
237+
expect(actualState).toEqual(state);
238+
});
239+
});
240+
});
241+
242+
function parseHTML(html) {
243+
if (document.documentElement) {
244+
document.documentElement.innerHTML = html;
245+
} // This makes Flow happy
246+
247+
248+
return document.body || document.createElement('body');
249+
}
250+
251+
function removeBlockKeys(content) {
252+
var newContent = _objectSpread({}, content);
253+
254+
newContent.blocks = content.blocks.map(function (block) {
255+
var key = block.key,
256+
other = _objectWithoutProperties(block, ["key"]); // eslint-disable-line no-unused-vars
257+
258+
259+
return other;
260+
});
261+
return newContent;
262+
}

0 commit comments

Comments
 (0)