Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Exilz committed Nov 29, 2016
0 parents commit 36d0d68
Show file tree
Hide file tree
Showing 15 changed files with 1,114 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .eslintrc
@@ -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" }]
}
}
89 changes: 89 additions & 0 deletions .flowconfig
@@ -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
34 changes: 34 additions & 0 deletions .gitignore
@@ -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
202 changes: 202 additions & 0 deletions HTML.js
@@ -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>);
}
}

0 comments on commit 36d0d68

Please sign in to comment.