forked from meliorence/react-native-render-html
-
Notifications
You must be signed in to change notification settings - Fork 0
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
0 parents
commit 36d0d68
Showing
15 changed files
with
1,114 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,24 @@ | ||
{ | ||
"parser" : "babel-eslint", | ||
"extends" : [ | ||
"standard", | ||
"standard-react" | ||
], | ||
"env" : { | ||
"browser" : true | ||
}, | ||
"globals": { | ||
"__DEV__": false | ||
}, | ||
"rules": { | ||
"indent": [2, 4], | ||
"generator-star-spacing": 0, | ||
"react/jsx-indent": [0, 4], | ||
"jsx-indent-props": [0, 4], | ||
"react/jsx-curly-spacing": [0, "never"], | ||
"react/jsx-boolean-value": [0, "never"], | ||
"semi" : [2, "always"], | ||
"operator-linebreak": [2, "after"], | ||
"no-warning-comments": [1, { "terms": ["todo", "fixme", "xxx"], "location": "start" }] | ||
} | ||
} |
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,89 @@ | ||
[ignore] | ||
|
||
# We fork some components by platform. | ||
.*/*.web.js | ||
.*/*.android.js | ||
|
||
# Some modules have their own node_modules with overlap | ||
.*/node_modules/node-haste/.* | ||
|
||
# Ugh | ||
.*/node_modules/babel.* | ||
.*/node_modules/babylon.* | ||
.*/node_modules/invariant.* | ||
|
||
# Ignore react and fbjs where there are overlaps, but don't ignore | ||
# anything that react-native relies on | ||
.*/node_modules/fbjs/lib/Map.js | ||
.*/node_modules/fbjs/lib/fetch.js | ||
.*/node_modules/fbjs/lib/ExecutionEnvironment.js | ||
.*/node_modules/fbjs/lib/ErrorUtils.js | ||
|
||
# Flow has a built-in definition for the 'react' module which we prefer to use | ||
# over the currently-untyped source | ||
.*/node_modules/react/react.js | ||
.*/node_modules/react/lib/React.js | ||
.*/node_modules/react/lib/ReactDOM.js | ||
|
||
.*/__mocks__/.* | ||
.*/__tests__/.* | ||
|
||
.*/commoner/test/source/widget/share.js | ||
|
||
# Ignore commoner tests | ||
.*/node_modules/commoner/test/.* | ||
|
||
# See https://github.com/facebook/flow/issues/442 | ||
.*/react-tools/node_modules/commoner/lib/reader.js | ||
|
||
# Ignore jest | ||
.*/node_modules/jest-cli/.* | ||
|
||
# Ignore Website | ||
.*/website/.* | ||
|
||
.*/node_modules/is-my-json-valid/test/.*\.json | ||
.*/node_modules/iconv-lite/encodings/tables/.*\.json | ||
.*/node_modules/y18n/test/.*\.json | ||
.*/node_modules/spdx-license-ids/spdx-license-ids.json | ||
.*/node_modules/spdx-exceptions/index.json | ||
.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json | ||
.*/node_modules/resolve/lib/core.json | ||
.*/node_modules/jsonparse/samplejson/.*\.json | ||
.*/node_modules/json5/test/.*\.json | ||
.*/node_modules/ua-parser-js/test/.*\.json | ||
.*/node_modules/builtin-modules/builtin-modules.json | ||
.*/node_modules/binary-extensions/binary-extensions.json | ||
.*/node_modules/url-regex/tlds.json | ||
.*/node_modules/joi/.*\.json | ||
.*/node_modules/isemail/.*\.json | ||
.*/node_modules/tr46/.*\.json | ||
|
||
[include] | ||
|
||
[libs] | ||
node_modules/react-native/Libraries/react-native/react-native-interface.js | ||
node_modules/react-native/flow | ||
flow/ | ||
|
||
[options] | ||
module.system=haste | ||
|
||
esproposal.class_static_fields=enable | ||
esproposal.class_instance_fields=enable | ||
|
||
munge_underscores=true | ||
|
||
module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' | ||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\)$' -> 'RelativeImageStub' | ||
|
||
suppress_type=$FlowIssue | ||
suppress_type=$FlowFixMe | ||
suppress_type=$FixMe | ||
|
||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-2]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) | ||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-2]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ | ||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy | ||
|
||
[version] | ||
0.22.0 |
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,34 @@ | ||
# OSX | ||
# | ||
.DS_Store | ||
|
||
# Xcode | ||
# | ||
build/ | ||
*.pbxuser | ||
!default.pbxuser | ||
*.mode1v3 | ||
!default.mode1v3 | ||
*.mode2v3 | ||
!default.mode2v3 | ||
*.perspectivev3 | ||
!default.perspectivev3 | ||
xcuserdata | ||
*.xccheckout | ||
*.moved-aside | ||
DerivedData | ||
*.hmap | ||
*.ipa | ||
*.xcuserstate | ||
project.xcworkspace | ||
|
||
# Android/IJ | ||
# | ||
.idea | ||
.gradle | ||
local.properties | ||
|
||
# node.js | ||
# | ||
node_modules/ | ||
npm-debug.log |
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,202 @@ | ||
import React from 'react'; | ||
import { View } from 'react-native'; | ||
import shallowCompare from 'react-addons-shallow-compare'; | ||
import htmlparser2 from 'htmlparser2'; | ||
import HTMLElement from './HTMLElement'; | ||
import HTMLTextNode from './HTMLTextNode'; | ||
import HTMLRenderers from './HTMLRenderers'; | ||
import HTMLStyles from './HTMLStyles'; | ||
import { TEXT_TAG_NAMES } from './HTMLUtils'; | ||
|
||
export default class HTML extends React.Component { | ||
/* ****************************************************************************/ | ||
// Class | ||
/* ****************************************************************************/ | ||
|
||
static propTypes = { | ||
html: React.PropTypes.string.isRequired, | ||
htmlStyles: React.PropTypes.object, | ||
containerStyle: View.propTypes.style, | ||
onLinkPress: React.PropTypes.func, | ||
imagesMaxWidth: React.PropTypes.number, | ||
renderers: React.PropTypes.object.isRequired | ||
} | ||
|
||
static defaultProps = { | ||
renderers: HTMLRenderers | ||
} | ||
|
||
constructor (props) { | ||
super(props); | ||
this.renderers = { | ||
...HTMLRenderers, | ||
...(this.props.renderers || {}) | ||
}; | ||
this.imgsToRender = []; | ||
} | ||
|
||
/* ****************************************************************************/ | ||
// Data Lifecycle | ||
/* ****************************************************************************/ | ||
|
||
shouldComponentUpdate (nextProps, nextState) { | ||
return shallowCompare(this, nextProps, nextState); | ||
} | ||
|
||
/* ****************************************************************************/ | ||
// Rendering | ||
/* ****************************************************************************/ | ||
|
||
/** | ||
* Returns an RN element from the HTML node being parsed | ||
* @param node: object | ||
* @param index: number | ||
* @param groupInfo: object | ||
* @param parentTagName: string | ||
* @parentIsText: bool | ||
*/ | ||
createElement (node, index, groupInfo, parentTagName, parentIsText) { | ||
return ( | ||
<HTMLElement | ||
key={index} | ||
htmlStyles={this.props.htmlStyles} | ||
imagesMaxWidth={this.props.imagesMaxWidth} | ||
htmlAttribs={node.attribs} | ||
tagName={node.name} | ||
groupInfo={groupInfo} | ||
parentTagName={parentTagName} | ||
parentIsText={parentIsText} | ||
onLinkPress={this.props.onLinkPress} | ||
renderers={this.renderers}> | ||
{this.renderHtmlAsRN(node.children, node.name, !HTMLStyles.blockElements.has(node.name))} | ||
</HTMLElement> | ||
); | ||
} | ||
|
||
/** | ||
* Returns if a text node is worth being rendered. | ||
* Loop on it and its children and look for actual text to display, | ||
* if none is found, don't render it (a single img or an empty p for instance) | ||
*/ | ||
shouldRenderNode (node) { | ||
if (!node.children || !node.children.length) { | ||
return false; | ||
} | ||
for (let i = 0; i < node.children.length; i++) { | ||
if (node.children[i].type === 'text') { | ||
return true; | ||
} else if (TEXT_TAG_NAMES.has(node.children[i].name)) { | ||
if (this.shouldRenderNode(node.children[i])) { | ||
return true; | ||
} else { | ||
continue; | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Loop on a HTML node and look for imgs that need | ||
* to be rendered outside this node (ie : img outside | ||
* of text elements) | ||
*/ | ||
addImgsToRenderList (node, index, groupInfo, parentTagName, parentIsText) { | ||
if (!node.children || !node.children.length) { | ||
return; | ||
} | ||
for (let i = 0; i < node.children.length; i++) { | ||
if (node.children[i].name === 'img') { | ||
this.imgsToRender.push( | ||
this.createElement( | ||
node.children[i], | ||
index, | ||
groupInfo, | ||
parentTagName, | ||
parentIsText | ||
) | ||
); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Converts the html elements to RN elements | ||
* @param htmlElements: the array of html elements | ||
* @param parentTagName='body': the parent html element if any | ||
* @param parentIsText: true if the parent element was a text-y element | ||
* @return the equivalent RN elements | ||
*/ | ||
renderHtmlAsRN (htmlElements, parentTagName, parentIsText) { | ||
return htmlElements.map((node, index, list) => { | ||
if (node.type === 'text') { | ||
const str = HTMLTextNode.removeWhitespaceListHTML(node.data, index, parentTagName); | ||
if (str.length) { | ||
return (<HTMLTextNode key={index}>{str}</HTMLTextNode>); | ||
} else { | ||
return undefined; | ||
} | ||
} else if (node.type === 'tag') { | ||
// Generate grouping info if we are a group-type element | ||
let groupInfo; | ||
if (node.name === 'li') { | ||
groupInfo = { | ||
index: htmlElements.reduce((acc, e) => { | ||
if (e === node) { | ||
acc.found = true; | ||
} else if (!acc.found && e.type === 'tag' && e.name === 'li') { | ||
acc.index++; | ||
} | ||
return acc; | ||
}, {index: 0, found: false}).index, | ||
count: htmlElements.filter((e) => e.type === 'tag' && e.name === 'li').length | ||
}; | ||
} | ||
|
||
let ElementsToRender; | ||
const Element = this.createElement(node, index, groupInfo, parentTagName, parentIsText); | ||
|
||
if (this.imgsToRender.length && !parentIsText) { | ||
ElementsToRender = ( | ||
<View key={index}> | ||
{ this.imgsToRender.map((img, imgIndex) => <View key={`view-${index}-image-${imgIndex}`}>{ img }</View>) } | ||
{ Element } | ||
</View> | ||
); | ||
this.imgsToRender = []; | ||
} else { | ||
ElementsToRender = Element; | ||
} | ||
|
||
if (node.name === 'img') { | ||
this.imgsToRender.push(Element); | ||
return false; | ||
} | ||
|
||
if (TEXT_TAG_NAMES.has(node.name)) { | ||
this.addImgsToRenderList(node, index, groupInfo, parentTagName, parentIsText); | ||
|
||
if (!this.shouldRenderNode(node)) { | ||
return false; | ||
} | ||
} | ||
|
||
return ElementsToRender; | ||
} | ||
}) | ||
.filter((e) => e !== undefined); | ||
} | ||
|
||
render () { | ||
let rnNodes; | ||
const parser = new htmlparser2.Parser( | ||
new htmlparser2.DomHandler((_err, dom) => { | ||
rnNodes = this.renderHtmlAsRN(dom, 'body', false); | ||
}) | ||
); | ||
parser.write(this.props.html); | ||
parser.done(); | ||
|
||
return (<View style={this.props.containerStyle || {}}>{rnNodes}</View>); | ||
} | ||
} |
Oops, something went wrong.