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
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.