-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Aleksandr Strikha
committed
Aug 22, 2016
0 parents
commit 7d02e36
Showing
10 changed files
with
657 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
presets: ["es2015"] | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.DS_Store | ||
.idea | ||
node_modules | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Active HTML for ReactJS | ||
Convert HTML string to React Components | ||
|
||
## The problem | ||
The most of CMS provide content as pure html from WYSIWYG editors: | ||
```json | ||
{ | ||
"content": "<a href='/hello'>Hello World</a><img src='image.png' class='main-image' alt='' /><p>Lorem ipsum...</p>" | ||
} | ||
``` | ||
In this case you lose advantage of using React components in content. | ||
|
||
## Solution | ||
```jsx | ||
import activeHtml from 'active-html'; | ||
|
||
class Html extends Component { | ||
|
||
render() { | ||
|
||
const components = { | ||
// replace <img> tags by custom react component | ||
img: (attributes) => { | ||
return (<Image {...attributes} />); | ||
}, | ||
// replace <a> tags by React Router Link component | ||
a: (attributes) => { | ||
return (<Link to={attributes.href} {...attributes} />); | ||
}, | ||
// add lazy load to all iframes | ||
iframe: (attributes) => { | ||
return ( | ||
<LazyLoad> | ||
<iframe {...attributes} /> | ||
</LazyLoad> | ||
); | ||
} | ||
}; | ||
|
||
// convert string property "content" to React components | ||
let nodes = activeHtml(this.props.content, components); | ||
|
||
return (<div className="html">{nodes}</div>); | ||
} | ||
|
||
``` | ||
## Installation | ||
#### Frontend | ||
npm install active-html --save-dev | ||
#### Backend (NodeJS) | ||
npm install active-html xmldom --save |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
'use strict'; | ||
|
||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
|
||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
|
||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
|
||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
|
||
var HtmlSerializer = function () { | ||
function HtmlSerializer() { | ||
_classCallCheck(this, HtmlSerializer); | ||
|
||
// if NodeJS | ||
if (typeof DOMParser === 'undefined') { | ||
var DOMParser = require('xmldom').DOMParser; | ||
if (DOMParser) { | ||
var options = { | ||
errorHandler: { | ||
warning: function warning(w) {} | ||
} | ||
}; | ||
this.parser = new DOMParser(options); | ||
} | ||
} | ||
// if browser | ||
else { | ||
this.parser = new DOMParser(); | ||
} | ||
|
||
this._removeEmptyStrings = false; | ||
this._attributesAdapter = null; | ||
} | ||
|
||
_createClass(HtmlSerializer, [{ | ||
key: 'removeEmptyStrings', | ||
value: function removeEmptyStrings() { | ||
var remove = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; | ||
|
||
this._removeEmptyStrings = remove; | ||
} | ||
}, { | ||
key: 'setAttributesAdapter', | ||
value: function setAttributesAdapter(adapter) { | ||
this._attributesAdapter = adapter; | ||
} | ||
}, { | ||
key: 'parseInlineCss', | ||
value: function parseInlineCss(css) { | ||
var urls = []; | ||
var tmpCss = css.replace(/url(\s+)?\(.*\)/i, function (match) { | ||
var key = urls.length; | ||
urls.push(match); | ||
return '%%' + key + '%%'; | ||
}); | ||
|
||
var arr = tmpCss.split(';').filter(function (item) { | ||
return item.indexOf(':') !== -1; | ||
}); | ||
|
||
var obj = {}; | ||
for (var i = 0; i < arr.length; i++) { | ||
var item = arr[i].split(':'); | ||
var key = item[0].trim(); | ||
var value = item[1].trim(); | ||
|
||
if (/%%\d+%%/.test(value)) { | ||
value = value.replace(/%%\d+%%/, function (m) { | ||
return urls[parseInt(m.replace(/\%/g, ''))]; | ||
}); | ||
} | ||
obj[key] = value; | ||
} | ||
|
||
return obj; | ||
} | ||
}, { | ||
key: 'parseHtml', | ||
value: function parseHtml(html) { | ||
if (typeof html !== 'string' || html == '') { | ||
return {}; | ||
} | ||
|
||
var doc = this.parser.parseFromString(html, "text/html"); | ||
|
||
if (!doc.childNodes) { | ||
return {}; | ||
} | ||
|
||
return this._parseNodes(doc.childNodes); | ||
} | ||
}, { | ||
key: '_parseNodes', | ||
value: function _parseNodes(nodes) { | ||
var i = void 0, | ||
k = void 0, | ||
node = void 0; | ||
var objects = []; | ||
|
||
for (i = 0; i < nodes.length; i++) { | ||
node = nodes[i]; | ||
|
||
var obj = {}; | ||
|
||
if (typeof node.tagName === 'undefined' && typeof node.nodeValue === 'string') { | ||
obj.node = 'text'; | ||
obj.text = node.nodeValue; | ||
|
||
if (this._removeEmptyStrings && obj.text.trim().length == 0) { | ||
continue; | ||
} | ||
} else { | ||
obj.node = node.tagName; | ||
} | ||
|
||
if (_typeof(node.attributes) === 'object' && node.attributes && node.attributes.length > 0) { | ||
var attributes = {}; | ||
for (k = 0; k < node.attributes.length; k++) { | ||
var attribute = node.attributes[k]; | ||
attributes[attribute.name.toLowerCase()] = attribute.value; | ||
} | ||
|
||
if (typeof attributes.style !== 'undefined') { | ||
attributes.style = this.parseInlineCss(attributes.style); | ||
} | ||
|
||
if (typeof this._attributesAdapter === 'function') { | ||
attributes = this._attributesAdapter(attributes); | ||
} | ||
|
||
obj.attributes = attributes; | ||
} | ||
|
||
if (_typeof(node.childNodes) === 'object' && node.childNodes && node.childNodes.length > 0) { | ||
obj.children = this._parseNodes(node.childNodes); | ||
} | ||
objects.push(obj); | ||
} | ||
|
||
return objects; | ||
} | ||
}]); | ||
|
||
return HtmlSerializer; | ||
}(); | ||
|
||
exports.default = HtmlSerializer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
'use strict'; | ||
|
||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
|
||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
|
||
exports.default = function (attributes) { | ||
var newAttributes = {}; | ||
|
||
for (var key in attributes) { | ||
if (attributes.hasOwnProperty(key)) { | ||
var value = attributes[key]; | ||
|
||
if (key == 'style' && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object') { | ||
var camelCaseCss = {}; | ||
for (var cssAttr in value) { | ||
if (value.hasOwnProperty(cssAttr)) { | ||
camelCaseCss[(0, _toCamelCase2.default)(cssAttr)] = value[cssAttr]; | ||
} | ||
} | ||
newAttributes[key] = camelCaseCss; | ||
} else if (typeof keyMap[key] !== 'undefined') { | ||
newAttributes[keyMap[key]] = value; | ||
} else { | ||
newAttributes[key] = value; | ||
} | ||
} | ||
} | ||
|
||
return newAttributes; | ||
}; | ||
|
||
var _HTMLDOMPropertyConfig = require('react/lib/HTMLDOMPropertyConfig'); | ||
|
||
var _HTMLDOMPropertyConfig2 = _interopRequireDefault(_HTMLDOMPropertyConfig); | ||
|
||
var _SVGDOMPropertyConfig = require('react/lib/SVGDOMPropertyConfig'); | ||
|
||
var _SVGDOMPropertyConfig2 = _interopRequireDefault(_SVGDOMPropertyConfig); | ||
|
||
var _toCamelCase = require('to-camel-case'); | ||
|
||
var _toCamelCase2 = _interopRequireDefault(_toCamelCase); | ||
|
||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
|
||
var keyMap = {}; | ||
|
||
for (var validAttr in _HTMLDOMPropertyConfig2.default.Properties) { | ||
if (_HTMLDOMPropertyConfig2.default.Properties.hasOwnProperty(validAttr)) { | ||
|
||
var lowerAttr = validAttr.toLowerCase(); | ||
if (lowerAttr !== validAttr) { | ||
keyMap[lowerAttr] = validAttr; | ||
} | ||
} | ||
} | ||
|
||
for (var reactAttr in _HTMLDOMPropertyConfig2.default.DOMAttributeNames) { | ||
if (_HTMLDOMPropertyConfig2.default.DOMAttributeNames.hasOwnProperty(reactAttr)) { | ||
var attr = _HTMLDOMPropertyConfig2.default.DOMAttributeNames[reactAttr]; | ||
keyMap[attr.toLowerCase()] = reactAttr; | ||
} | ||
} | ||
|
||
for (var _reactAttr in _SVGDOMPropertyConfig2.default.DOMAttributeNames) { | ||
if (_SVGDOMPropertyConfig2.default.DOMAttributeNames.hasOwnProperty(_reactAttr)) { | ||
var _attr = _SVGDOMPropertyConfig2.default.DOMAttributeNames[_reactAttr]; | ||
keyMap[_attr.toLowerCase()] = _reactAttr; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
'use strict'; | ||
|
||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
|
||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
|
||
exports.default = function (htmlSting) { | ||
var componentsMap = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; | ||
|
||
if (typeof htmlSting !== 'string') { | ||
if (debug) { | ||
console && console.error("Html should be string, " + (typeof htmlSting === 'undefined' ? 'undefined' : _typeof(htmlSting)) + " was given."); | ||
return null; | ||
} | ||
} | ||
|
||
var removeEmptyStrings = options && options.removeEmptyStrings === false ? false : true; | ||
var serializer = getSerializer(); | ||
serializer.removeEmptyStrings(removeEmptyStrings); | ||
|
||
var tree = serializer.parseHtml(htmlSting); | ||
|
||
return htmlTreeToComponents(tree, componentsMap); | ||
}; | ||
|
||
var _react = require('react'); | ||
|
||
var _react2 = _interopRequireDefault(_react); | ||
|
||
var _HtmlSerializer = require('./HtmlSerializer'); | ||
|
||
var _HtmlSerializer2 = _interopRequireDefault(_HtmlSerializer); | ||
|
||
var _adapter = require('./adapter'); | ||
|
||
var _adapter2 = _interopRequireDefault(_adapter); | ||
|
||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
|
||
var debug = process.env.NODE_ENV !== 'production'; | ||
|
||
var _serializer = null; | ||
|
||
function getSerializer() { | ||
if (_serializer === null) { | ||
_serializer = new _HtmlSerializer2.default(); | ||
_serializer.setAttributesAdapter(_adapter2.default); | ||
} | ||
return _serializer; | ||
} | ||
|
||
function htmlTreeToComponents(tree, componentsMap) { | ||
var index = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2]; | ||
|
||
|
||
return tree.map(function (item) { | ||
|
||
if (item.node === 'text') { | ||
return item.text; | ||
} | ||
|
||
var componentProps = { | ||
key: index | ||
}; | ||
|
||
if (typeof item.attributes !== 'undefined') { | ||
componentProps = Object.assign(componentProps, item.attributes); | ||
} | ||
|
||
if (typeof item.children !== 'undefined') { | ||
componentProps = Object.assign(componentProps, { | ||
children: htmlTreeToComponents(item.children, componentsMap, ++index) | ||
}); | ||
} | ||
|
||
if (typeof componentsMap[item.node] === 'undefined') { | ||
return _react2.default.createElement(item.node, componentProps); | ||
} | ||
|
||
if (typeof componentsMap[item.node] === 'function') { | ||
return componentsMap[item.node](componentProps); | ||
} | ||
|
||
if (debug) { | ||
console && console.warn("Component mapping should return a function, " + _typeof(componentsMap[item.node]) + " was given."); | ||
} | ||
|
||
return componentsMap[item.node]; | ||
}); | ||
} |
Oops, something went wrong.