Skip to content

Commit

Permalink
Merge pull request meliorence#127 from archriss/dev
Browse files Browse the repository at this point in the history
v3.9.1
Exilz authored Mar 23, 2018

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents bbebfde + 0abe97b commit f5863da
Showing 8 changed files with 106 additions and 40 deletions.
2 changes: 1 addition & 1 deletion Demo/package.json
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
"dependencies": {
"react": "16.2.0",
"react-native": "0.52.0",
"react-native-render-html": "3.9.0"
"react-native-render-html": "3.9.1"
},
"devDependencies": {
"babel-jest": "22.0.6",
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -85,7 +85,8 @@ Prop | Description | Type | Required/Default
`remoteLoadingView` | Replace the default loader while fetching a remote website's content | `function` | Optional
`remoteErrorView` | Replace the default error if a remote website's content could not be fetched | `function` | Optional
`emSize` | The default value in pixels for `1em` | `number` | `14`
`baseFontStyle` | The default style applied to `<Text>` components | `number` | `14`
`ptSize` | The default value in pixels for `1pt` | `number` | `1.3`
`baseFontStyle` | The default style applied to `<Text>` components | `object` | `{ fontSize: 14 }`
`textSelectable` | Allow all texts to be selected | `boolean` | `false`
`alterData` | Target some specific texts and change their content, see [altering content](#altering-content) | `function` | Optional
`alterChildren` | Target some specific nested children and change them, see [altering content](#altering-content) | `function` | Optional
@@ -157,7 +158,7 @@ renderers: {

The default renderer of the `<ul>` and `<ol>` tags will either render a bullet or the count of your elements. If you wish to change this without having to re-write the whole list rendering implementation, you can use the `listsPrefixesRenderers` prop.

Just like with the `renderers` prop, supply an object with `ul` and/or `ul` as functions that recevie the [same arguments as your custom HTML tags](#custom-html-tags). For instance, you can swap the default black bullet of `<ul>` with a blue cross :
Just like with the `renderers` prop, supply an object with `ul` and/or `ul` as functions that receive the [same arguments as your custom HTML tags](#custom-html-tags). For instance, you can swap the default black bullet of `<ul>` with a blue cross :

```javascript
// ... your props
@@ -172,13 +173,13 @@ ul: (htmlAttribs, children, convertedCSSStyles, passProps) => {

In addition to your custom renderers, you can apply specific styles to HTML tags (`tagsStyles`) or HTML classes (`classesStyles`). You can also combine these styles with your custom renderers.

Styling options override thesmelves, so you might render a custom HTML tag with a [custom renderer](#creating-custom-renderers) like `<bluecircle>`, make it green with a class `<bluecircle class="make-me-green">` or make it red by styling the tag itself.
Styling options override themselves, so you might render a custom HTML tag with a [custom renderer](#creating-custom-renderers) like `<bluecircle>`, make it green with a class `<bluecircle class="make-me-green">` or make it red by styling the tag itself.

The default style of your custom renderer will be merged to the one from your `classesStyles` which will also be merged by the `style` attribute.

> **IMPORTANT NOTE : Do NOT use the `StyleSheet` API to create the styles you're going to feed to `tagsStyle` and `classesStyles`. Although it might look like it's working at first, the caching logic of `react-native` makes it impossible for this module to deep check each of your style to properly apply the precedence and priorities of your nested tags' styles.**
Here's an usage example
Here's a usage example

```javascript
// props
@@ -194,25 +195,25 @@ const html = `

## Images

By default, unstyled images will be rendered with their respective height and width without resizing. You can force their dimensions by using the `style` attribute in your HTML content, or [style](#styling) them with a class or through the `<img>` tag.
By default, unstyled images will be rendered with their respective height and width without resizing. You can force their dimensions by using the `style` attribute in your HTML content or [style](#styling) them with a class or through the `<img>` tag.

If you can't set the dimension of each image in your content, you might find the `imagesMaxWidth` prop useful. It resizes (and keeps proportions) your images to a maximum width, ensuring that your images won't overflow out of your viewport.

A nice trick, demonstrated in the [basic usage of this module](#basic-usage) is to use the `Dimensions` API of react-native : `imagesMaxWidth={Dimensions.get('window').width}`. You could substract a value to it to make a margin.
A nice trick, demonstrated in the [basic usage of this module](#basic-usage) is to use the `Dimensions` API of react-native : `imagesMaxWidth={Dimensions.get('window').width}`. You could subtract a value to it to make a margin.

Please note that if you set width AND height through any mean of styling, `imagesMaxWidth` will be ignored.

Before their dimensions have been properly retrieved, images will temporarily be rendered in 100px wide squares. You can override this default value with prop `imagesInitialDimensions`.

Images with broken links will render an empty square with a thin border, similar to what safari renders in a webview.

Please note that all of these behaviours are implemented in the default `<img>` renderer. If you want to provide your own `<img>` renderer, you'll have to make this happen by yourself. You can use the `img` function in `HTMLRenderers.js` as a starting point.
Please note that all of these behaviors are implemented in the default `<img>` renderer. If you want to provide your own `<img>` renderer, you'll have to make this happen by yourself. You can use the `img` function in `HTMLRenderers.js` as a starting point.

## Altering content

`alterData` and `alterChildren` props are very useful to make some modifications on the structure of your HTML before it's actually rendered with react components.

They both are functions that receive the parsed `node` as their first and only parameter. You must return your changes : a `string` with `alterData` and an `array` with `alterChildren` or a falsy value if you don't need to change anything.
They both are functions that receive the parsed `node` as their first and only parameter. You must return your changes: a `string` with `alterData` and an `array` with `alterChildren` or a falsy value if you don't need to change anything.

### alterData

@@ -235,7 +236,7 @@ alterData: (node) => {

### alterChildren

`alterChildren` allows you to change the children wrapped in any node. For instance, you might want to change the content of a a list.
`alterChildren` allows you to change the children wrapped in any node. For instance, you might want to change the content of a list.

Here's an example :

@@ -302,7 +303,7 @@ You can't expect native components to be able to render *everything* you can fin
* `ignoredStyles` : array of ignored CSS rules. Nothing is ignored by default
* `ignoreNodesFunction` : this is a cumbersome, yet powerful, way of ignoring very specific stuff.

**Please note** that if you supply `ignoredTags`, you will override the default ignored ones. There are *a lot* of them, if you want to keep them and add you own, you can do something like :
**Please note** that if you supply `ignoredTags`, you will override the default ignored ones. There are *a lot* of them, if you want to keep them and add your own, you can do something like :

```javascript
import { IGNORED_TAGS } from 'react-native-render-html/src/HTMLUtils';
@@ -314,7 +315,7 @@ ignoredTags={[ ...IGNORED_TAGS, 'tag1', 'tag2']}

`ignoreNodesFunction` receives 3 parameters : `node`, `parentTagName` and `parentIsText`.

`node` is the result of the HTML parsing, which allows you to look for children, check the parent's markup and much more. `parentTagName` is a conveniant way to access the parent of your node, and `parentIsText` is a great way to make sure you won't be rendering a `<View>` inside a `<Text>` which, right now, makes react-native crash.
`node` is the result of the HTML parsing, which allows you to look for children, check the parent's markup and much more. `parentTagName` is a convenient way to access the parent of your node, and `parentIsText` is a great way to make sure you won't be rendering a `<View>` inside a `<Text>` which, right now, makes react-native crash.

## Useful functions

@@ -333,4 +334,4 @@ import { functionName } from 'react-native-render-html/src/HTMLUtils';
* `getClosestNodeParentByTag(node, tag)`
* Description: Returns the closest parent of a node with a specific tag.
* Parameters : - `node` : a parsed HTML node from `alterChildren` for example
* Returns : An HTML node if found.
* Returns : An HTML node if found.
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-render-html",
"version": "3.9.0",
"version": "3.9.1",
"author": "Archriss",
"license": "BSD-2-Clause",
"repository": "https://github.com/archriss/react-native-render-html",
@@ -23,5 +23,17 @@
"prop-types": ">=15.5.10",
"react": "*",
"react-native": "*"
},
"devDependencies": {
"babel-eslint": "8.2.2",
"eslint": "4.18.2",
"eslint-config-standard": "11.0.0",
"eslint-config-standard-jsx": "5.0.0",
"eslint-config-standard-react": "6.0.0",
"eslint-plugin-import": "2.9.0",
"eslint-plugin-node": "6.0.1",
"eslint-plugin-promise": "3.7.0",
"eslint-plugin-react": "7.7.0",
"eslint-plugin-standard": "3.0.1"
}
}
10 changes: 7 additions & 3 deletions src/HTML.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { View, Text, ViewPropTypes, ActivityIndicator } from 'react-native';
import { View, Text, ViewPropTypes, ActivityIndicator, Dimensions } from 'react-native';
import { BLOCK_TAGS, TEXT_TAGS, MIXED_TAGS, IGNORED_TAGS, TEXT_TAGS_IGNORING_ASSOCIATION, STYLESETS, TextOnlyPropTypes } from './HTMLUtils';
import { cssStringToRNStyle, _getElementClassStyles, cssStringToObject, cssObjectToString } from './HTMLStyles';
import { generateDefaultBlockStyles, generateDefaultTextStyles } from './HTMLDefaultStyles';
@@ -35,6 +35,7 @@ export default class HTML extends PureComponent {
height: PropTypes.number
}),
emSize: PropTypes.number.isRequired,
ptSize: PropTypes.number.isRequired,
baseFontStyle: PropTypes.object.isRequired,
textSelectable: PropTypes.bool
}
@@ -44,6 +45,9 @@ export default class HTML extends PureComponent {
debug: false,
decodeEntities: true,
emSize: 14,
ptSize: 1.3,
staticContentMaxWidth: Dimensions.get('window').width,
imagesMaxWidth: Dimensions.get('window').width,
ignoredTags: IGNORED_TAGS,
ignoredStyles: [],
baseFontStyle: { fontSize: 14 },
@@ -395,7 +399,7 @@ export default class HTML extends PureComponent {
* @memberof HTML
*/
renderRNElements (RNElements, parentWrapper = 'root', parentIndex = 0, props = this.props) {
const { tagsStyles, classesStyles, emSize, ignoredStyles, allowedStyles } = props;
const { tagsStyles, classesStyles, emSize, ptSize, ignoredStyles, allowedStyles } = props;
return RNElements && RNElements.length ? RNElements.map((element, index) => {
const { attribs, data, tagName, parentTag, children, nodeIndex, wrapper } = element;
const Wrapper = wrapper === 'Text' ? Text : View;
@@ -405,7 +409,7 @@ export default class HTML extends PureComponent {
cssStringToRNStyle(
attribs.style,
Wrapper === Text ? STYLESETS.TEXT : STYLESETS.VIEW, // proper prop-types validation
{ parentTag: tagName, emSize, ignoredStyles, allowedStyles }
{ parentTag: tagName, emSize, ptSize, ignoredStyles, allowedStyles }
) :
{};

22 changes: 16 additions & 6 deletions src/HTMLImage.js
Original file line number Diff line number Diff line change
@@ -49,14 +49,24 @@ export default class HTMLImage extends PureComponent {
if (width) {
styleWidth = width;
}
style.forEach((styles) => {
if (!width && styles['width']) {
styleWidth = styles['width'];
if (Array.isArray(style)) {
style.forEach((styles) => {
if (!width && styles['width']) {
styleWidth = styles['width'];
}
if (!height && styles['height']) {
styleHeight = styles['height'];
}
});
} else {
if (!width && style['width']) {
styleWidth = style['width'];
}
if (!height && styles['height']) {
styleHeight = styles['height'];
if (!height && style['height']) {
styleHeight = style['height'];
}
});
}

return { styleWidth, styleHeight };
}

31 changes: 17 additions & 14 deletions src/HTMLRenderers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { TouchableOpacity, Text, View, WebView, Dimensions } from 'react-native';
import { _constructStyles } from './HTMLStyles';
import { _constructStyles, _getElementClassStyles } from './HTMLStyles';
import HTMLImage from './HTMLImage';

export function a (htmlAttribs, children, convertedCSSStyles, passProps) {
@@ -116,24 +116,27 @@ export function iframe (htmlAttribs, children, convertedCSSStyles, passProps) {
if (!htmlAttribs.src) {
return false;
}
const { staticContentMaxWidth } = passProps;
const { staticContentMaxWidth, tagsStyles, classesStyles } = passProps;

const tagStyleHeight = tagsStyles.iframe && tagsStyles.iframe.height;
const tagStyleWidth = tagsStyles.iframe && tagsStyles.iframe.width;

const classStyles = _getElementClassStyles(htmlAttribs, classesStyles);
const classStyleWidth = classStyles.width;
const classStyleHeight = classStyles.height;

const attrHeight = htmlAttribs.height ? parseInt(htmlAttribs.height) : false;
const attrWidth = htmlAttribs.width ? parseInt(htmlAttribs.width) : false;

const height = attrHeight || classStyleHeight || tagStyleHeight || 200;
const width = attrWidth || classStyleWidth || tagStyleWidth || staticContentMaxWidth;

const style = _constructStyles({
tagName: 'iframe',
htmlAttribs,
passProps,
styleSet: 'VIEW',
additionalStyles: [
{
height: htmlAttribs.height ?
parseInt(htmlAttribs.height, 10) :
undefined
},
{
width: staticContentMaxWidth && htmlAttribs.width && htmlAttribs.width <= staticContentMaxWidth ?
parseInt(htmlAttribs.width, 10) :
staticContentMaxWidth
}
]
additionalStyles: [{ height, width }]
});

return (
27 changes: 24 additions & 3 deletions src/HTMLStyles.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PERC_SUPPORTED_STYLES, STYLESETS, stylePropTypes } from './HTMLUtils';
import { PERC_SUPPORTED_STYLES, STYLESETS, ABSOLUTE_FONT_SIZE, stylePropTypes } from './HTMLUtils';
import { generateDefaultBlockStyles, generateDefaultTextStyles } from './HTMLDefaultStyles';
import checkPropTypes from './checkPropTypes';

@@ -101,7 +101,7 @@ export function _getElementCSSClasses (htmlAttribs) {
* @param {object} { parentTag, emSize, ignoredStyles }
* @returns {object}
*/
function cssToRNStyle (css, styleset, { parentTag, emSize, ignoredStyles, allowedStyles }) {
function cssToRNStyle (css, styleset, { parentTag, emSize, ptSize, ignoredStyles, allowedStyles }) {
const styleProps = stylePropTypes[styleset];
return Object.keys(css)
.filter((key) => allowedStyles ? allowedStyles.indexOf(key) !== -1 : true)
@@ -130,6 +130,7 @@ function cssToRNStyle (css, styleset, { parentTag, emSize, ignoredStyles, allowe
if (value.search('inherit') !== -1) {
return undefined;
}
value = value.replace('!important', '');
// See if we can use the percentage directly
if (value.search('%') !== -1 && PERC_SUPPORTED_STYLES.indexOf(key) !== -1) {
return [key, value];
@@ -138,14 +139,21 @@ function cssToRNStyle (css, styleset, { parentTag, emSize, ignoredStyles, allowe
const pxSize = parseFloat(value.replace('em', '')) * emSize;
return [key, pxSize];
}
if (value.search('pt') !== -1) {
const pxSize = parseFloat(value.replace('pt', '')) * ptSize;
return [key, pxSize];
}
// See if we can convert a 20px to a 20 automagically
const numericValue = parseFloat(value.replace('px', ''));
if (!isNaN(numericValue)) {
if (key !== 'fontWeight' && !isNaN(numericValue)) {
testStyle[key] = numericValue;
if (checkPropTypes(styleProp, testStyle, key, 'react-native-render-html') == null) {
return [key, numericValue];
}
}
if (key === 'fontSize') {
return mapAbsoluteFontSize(key, value);
}
}
return [key, value];
}
@@ -158,6 +166,19 @@ function cssToRNStyle (css, styleset, { parentTag, emSize, ignoredStyles, allowe
}, {});
}

/**
* @param {string} key: the key of style
* @param {string} value: the value of style
* @return {array}
*/
function mapAbsoluteFontSize (key, value) {
let fontSize = value;
if (ABSOLUTE_FONT_SIZE.hasOwnProperty(value)) {
fontSize = ABSOLUTE_FONT_SIZE[value];
}
return [key, fontSize];
}

/**
* @param str: the css style string
* @param styleset=STYLESETS.TEXT: the styleset to convert the styles against
15 changes: 15 additions & 0 deletions src/HTMLUtils.js
Original file line number Diff line number Diff line change
@@ -26,6 +26,21 @@ export const MIXED_TAGS = ['a'];
// These text tags shouldn't be associated with their siblings in the associateRawTags method
export const TEXT_TAGS_IGNORING_ASSOCIATION = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];

export const ABSOLUTE_FONT_SIZE = {
'medium': 14,
'xx-small': 8.5,
'x-small': 10,
'small': 12,
'large': 17,
'x-large': 20,
'xx-large': 24,
'smaller': 13.3,
'larger': 16,
'length': null,
'initial': null,
'inherit': null
};

export const IGNORED_TAGS = ['head', 'scripts', 'audio', 'video', 'track', 'embed', 'object', 'param', 'source', 'canvas', 'noscript',
'caption', 'col', 'colgroup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'button', 'datalist', 'fieldset', 'form',
'input', 'label', 'legend', 'meter', 'optgroup', 'option', 'output', 'progress', 'select', 'textarea', 'details', 'diaglog',

0 comments on commit f5863da

Please sign in to comment.