Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jsdf committed Apr 1, 2015
0 parents commit 869dfcd
Show file tree
Hide file tree
Showing 7 changed files with 8,060 additions and 0 deletions.
45 changes: 45 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Ignore docs files
_gh_pages
_site
.ruby-version

# Numerous always-ignore extensions
*.diff
*.err
*.orig
*.log
*.rej
*.swo
*.swp
*.zip
*.vi
*~

# OS or Editor folders
.DS_Store
._*
Thumbs.db
.cache
.project
.settings
.tmproj
*.esproj
nbproject
*.sublime-project
*.sublime-workspace
.idea

# Komodo
*.komodoproject
.komodotools

# Folders to ignore
node_modules
bower_components

lib/
example/output/

main.jsbundle

images/generated/
123 changes: 123 additions & 0 deletions HTMLView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
var htmlparser = require('./vendor/htmlparser2')
var entities = require('./vendor/entities')
var React = require('react-native')
var {
LinkingIOS,
StyleSheet,
Text,
} = React

var LINE_BREAK = '\n'
var PARAGRAPH_BREAK = '\n\n'

function htmlToElement(rawHtml, opts, done) {
function domToElement(dom, parent) {
if (!dom) return null

return dom.map((node, index, list) => {
if (opts.customRenderer) {
var rendered = opts.customRenderer(node, index, list)
if (rendered || rendered === null) return rendered
}

if (node.type == 'text') {
return (
<Text key={index} style={parent ? opts.styles[parent.name] : null}>
{entities.decodeHTML(node.data)}
</Text>
)
}

if (node.type == 'tag') {
var linkPressHandler = null
if (node.name == 'a' && node.attribs && node.attribs.href) {
linkPressHandler = () => opts.linkHandler(entities.decodeHTML(node.attribs.href))
}

return (
<Text key={index} onPress={linkPressHandler}>
{node.name == 'pre' ? LINE_BREAK : null}
{domToElement(node.children, node)}
{node.name == 'br' ? LINE_BREAK : null}
{node.name == 'p' && index < list.length-1 ? PARAGRAPH_BREAK : null}
</Text>
)
}
})
}

var handler = new htmlparser.DomHandler(function (err, dom) {
if (err) done(err)
done(null, domToElement(dom))
})
var parser = new htmlparser.Parser(handler)
parser.write(rawHtml)
parser.done()
}

var HTMLView = React.createClass({
mixins: [
React.addons.PureRenderMixin,
],
getDefaultProps() {
return {
onLinkPress: LinkingIOS.openURL,
}
},
getInitialState() {
return {
element: null,
}
},
componentWillReceiveProps() {
if (this.state.element) return
this.startHtmlRender()
},
componentDidMount() {
this.startHtmlRender()
},
startHtmlRender() {
if (!this.props.value) return
if (this.renderingHtml) return

var opts = {
linkHandler: this.props.onLinkPress,
styles: Object.assign({}, baseStyles, this.props.stylesheet),
customRenderer: this.props.renderNode,
}

this.renderingHtml = true
htmlToElement(this.props.value, opts, (err, element) => {
this.renderingHtml = false

if (err) return (this.props.onError || console.error)(err)

if (this.isMounted()) this.setState({element})
})
},
render() {
if (this.state.element) {
return <Text children={this.state.element} />
}
return <Text />
}
})

var boldStyle = {fontWeight: '500'}
var italicStyle = {fontStyle: 'italic'}
var codeStyle = {fontFamily: 'Menlo'}

var baseStyles = StyleSheet.create({
b: boldStyle,
strong: boldStyle,
i: italicStyle,
em: italicStyle,
pre: codeStyle,
code: codeStyle,
a: {
fontWeight: '500',
color: '#007AFF',
},
})

module.exports = HTMLView
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# React Native HTMLView
A component which takes HTML content and renders it as native views, with
customisable style and handling of links, etc.

### usage

props:

- `value`: a string of HTML content to render
- `onLinkPress`: a function which will be called with a url when a link is pressed.
Passing this prop will override how links are handled (defaults to calling LinkingIOS.openURL(url))
- `stylesheet`: a stylesheet object keyed by tag name, which will override the
styles applied to those respective tags.
- `renderNode`: a custom function to render HTML nodes however you see fit. If
the function returns `undefined` (not `null`), the default renderer will be
used for that node.

### example

```js
var React = require('react-native')
var {Text, View, ListView} = React

var HTMLView = require('react-native-htmlview')

var ContentView = React.createClass({
render() {
return (
var htmlContent = '<p><a href="">&hearts; nice job!</a></p>'
<HTMLView
value={htmlContent}
onLinkPress={(url) => console.log('navigating to: ', url)}
stylesheet={styles}
/>
)
}
})
```

var styles = StyleSheet.create({
a: {
fontWeight: '300',
color: '#FF3366', // pink links
},
})

### screenshot

In action (from [ReactNativeHackerNews](https://github.com/jsdf/ReactNativeHackerNews)):

![React Native Hacker News Comments](http://i.imgur.com/FYOgBYc.png)


![Under Construction](https://jamesfriend.com.au/files/under-construction.gif)

I just wrote this... use at your own risk. Not API stable.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./HTMLView')
31 changes: 31 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "react-native-htmlview",
"version": "0.1.1",
"description": "A component which renders HTML content as native views",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "James Friend <[email protected]> (http://jsdf.co/)",
"license": "ISC",
"keywords": [
"react",
"html",
"react-native",
"react-component",
"react-native-component",
"mobile",
"ui"
],
"files": [
"index.js",
"HTMLView.js",
"vendor/"
],
"homepage": "https://github.com/jsdf/react-native-htmlview",
"bugs": "https://github.com/jsdf/react-native-htmlview/issues",
"repository": {
"type": "git",
"url": "git://github.com/jsdf/react-native-htmlview.git"
}
}
Loading

0 comments on commit 869dfcd

Please sign in to comment.