Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3.9.1 #127

Merged
merged 11 commits into from
Mar 23, 2018
2 changes: 1 addition & 1 deletion Demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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 :

Expand Down Expand Up @@ -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';
Expand All @@ -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

Expand All @@ -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",
Expand All @@ -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';
Expand Down Expand Up @@ -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
}
Expand All @@ -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 },
Expand Down Expand Up @@ -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;
Expand All @@ -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 }
) :
{};

Expand Down
22 changes: 16 additions & 6 deletions src/HTMLImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

Expand Down
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) {
Expand Down Expand Up @@ -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 (
Expand Down
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';

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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];
Expand All @@ -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];
}
Expand All @@ -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
Expand Down
15 changes: 15 additions & 0 deletions src/HTMLUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down