Skip to content

Commit

Permalink
Merge pull request meliorence#146 from archriss/dev
Browse files Browse the repository at this point in the history
v3.10.0
  • Loading branch information
Exilz authored May 7, 2018
2 parents f5863da + 4f28049 commit 40293ba
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 44 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default class Demo extends Component {
Prop | Description | Type | Required/Default
------ | ------ | ------ | ------
`renderers` | Your [custom renderers](#creating-custom-renderers) | `object` | Optional, some default ones are supplied (`<a>`, `<img>`...)
`renderersProps` | Set of props accessible into your [custom renderers](#creating-custom-renderers) in `passProps` (4th argument) | `object` | Optional
`html` | HTML string to parse and render | `string` | Required
`uri` | *(experimental)* remote website to parse and render | `string` | Optional
`decodeEntities` | Decode HTML entities of your content | `bool` | Optional, defaults to `true`
Expand Down Expand Up @@ -137,7 +138,7 @@ Your renderers functions receive several arguments that will be very useful to m
* `htmlAttribs`: attributes attached to the node, parsed in a react-native way
* `children` : array with the children of the node
* `convertedCSSStyles` : conversion of the `style` attribute from CSS to react-native's stylesheet
* `passProps` : various useful information : `groupInfo`, `parentTagName`, `parentIsText`...
* `passProps` : various useful information : your `renderersProps`, `groupInfo`, `parentTagName`, `parentIsText`...

### Making your custom component block or inline

Expand Down
2 changes: 1 addition & 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.1",
"version": "3.10.0",
"author": "Archriss",
"license": "BSD-2-Clause",
"repository": "https://github.com/archriss/react-native-render-html",
Expand Down
81 changes: 40 additions & 41 deletions src/HTML.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
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 { cssStringToRNStyle, _getElementClassStyles, cssStringToObject, cssObjectToString, computeTextStyles } from './HTMLStyles';
import {
BLOCK_TAGS,
TEXT_TAGS,
MIXED_TAGS,
IGNORED_TAGS,
TEXT_TAGS_IGNORING_ASSOCIATION,
STYLESETS,
TextOnlyPropTypes
} from './HTMLUtils';
import { generateDefaultBlockStyles, generateDefaultTextStyles } from './HTMLDefaultStyles';
import htmlparser2 from 'htmlparser2';
import * as HTMLRenderers from './HTMLRenderers';
Expand Down Expand Up @@ -37,7 +45,8 @@ export default class HTML extends PureComponent {
emSize: PropTypes.number.isRequired,
ptSize: PropTypes.number.isRequired,
baseFontStyle: PropTypes.object.isRequired,
textSelectable: PropTypes.bool
textSelectable: PropTypes.bool,
renderersProps: PropTypes.object
}

static defaultProps = {
Expand Down Expand Up @@ -149,36 +158,6 @@ export default class HTML extends PureComponent {
this.defaultTextStyles = generateDefaultTextStyles(baseFontStyle.fontSize || 14);
}

filterBaseFontStyles (element, classStyles, props = this.props) {
const { tagsStyles, baseFontStyle } = props;
const { tagName, parentTag, parent, attribs } = element;
const styles = Object.keys(baseFontStyle);
let appliedStyles = {};

for (let i = 0; i < styles.length; i++) {
const styleAttribute = styles[i];
const tagToCheck = tagName === 'rawtext' ? parentTag : tagName;
const styleAttributeWithCSSDashes = styleAttribute.replace(/[A-Z]/, (match) => { return `-${match.toLowerCase()}`; });
const overridenFromStyle = attribs && attribs.style && attribs.style.search(styleAttributeWithCSSDashes) !== -1;
const overridenFromParentStyle = parent && parent.attribs && parent.attribs.style && parent.attribs.style.search(styleAttributeWithCSSDashes) !== -1;

const overridenFromTagStyle = tagToCheck && tagsStyles[tagToCheck] && tagsStyles[tagToCheck][styleAttribute];
const overridenFromParentTagStyle = parentTag && tagsStyles[parentTag] && tagsStyles[parentTag][styleAttribute];

const overridenFromClassStyles = classStyles && classStyles[styleAttribute];
const overridenFromDefaultStyles = this.defaultTextStyles[tagToCheck] && this.defaultTextStyles[tagToCheck][styleAttribute];

const notOverriden = !overridenFromStyle && !overridenFromParentStyle &&
!overridenFromTagStyle && !overridenFromParentTagStyle &&
!overridenFromClassStyles && !overridenFromDefaultStyles;

if (notOverriden) {
appliedStyles[styleAttribute] = baseFontStyle[styleAttribute];
}
}
return appliedStyles;
}

/**
* Loop on children and return whether if their parent needs to be a <View>
* @param {any} children
Expand Down Expand Up @@ -216,7 +195,11 @@ export default class HTML extends PureComponent {
associateRawTexts (children) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
if ((child.wrapper === 'Text' && TEXT_TAGS_IGNORING_ASSOCIATION.indexOf(child.tagName) === -1) && children.length > 1 && (!child.parent || child.parent.name !== 'p')) {
if (
(child.wrapper === 'Text' && TEXT_TAGS_IGNORING_ASSOCIATION.indexOf(child.tagName) === -1) &&
children.length > 1 &&
(!child.parent || TEXT_TAGS_IGNORING_ASSOCIATION.indexOf(child.parent.name) === -1)
) {
// Texts outside <p> or not <p> themselves (with siblings)
let wrappedTexts = [];
for (let j = i; j < children.length; j++) {
Expand All @@ -238,7 +221,7 @@ export default class HTML extends PureComponent {
nodeIndex: i,
parent: child.parent,
parentTag: child.parentTag,
tagName: child.parent && child.parent.name === 'li' ? 'textwrapper' : 'p',
tagName: 'textwrapper',
wrapper: 'Text'
};
}
Expand Down Expand Up @@ -399,7 +382,7 @@ export default class HTML extends PureComponent {
* @memberof HTML
*/
renderRNElements (RNElements, parentWrapper = 'root', parentIndex = 0, props = this.props) {
const { tagsStyles, classesStyles, emSize, ptSize, ignoredStyles, allowedStyles } = props;
const { tagsStyles, classesStyles, emSize, ptSize, ignoredStyles, allowedStyles, baseFontStyle } = 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 Down Expand Up @@ -445,9 +428,23 @@ export default class HTML extends PureComponent {
}

const classStyles = _getElementClassStyles(attribs, classesStyles);
const textElementStyles = this.filterBaseFontStyles(element, classStyles, props);
const textElement = data ?
<Text style={textElementStyles}>{ data }</Text> :
<Text
style={computeTextStyles(
element,
{
defaultTextStyles: this.defaultTextStyles,
tagsStyles,
classesStyles,
baseFontStyle,
emSize,
ptSize,
ignoredStyles,
allowedStyles
})}
>
{ data }
</Text> :
false;

const style = [
Expand All @@ -458,10 +455,12 @@ export default class HTML extends PureComponent {
]
.filter((s) => s !== undefined);

const extraProps = {};
if (Wrapper === Text) extraProps.selectable = this.props.textSelectable;
const renderersProps = {};
if (Wrapper === Text) {
renderersProps.selectable = this.props.textSelectable;
}
return (
<Wrapper key={key} style={style} {...extraProps}>
<Wrapper key={key} style={style} {...renderersProps}>
{ textElement }
{ childElements }
</Wrapper>
Expand Down
62 changes: 61 additions & 1 deletion src/HTMLStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,66 @@ export function _constructStyles ({ tagName, htmlAttribs, passProps, additionalS
return style.filter((style) => style !== undefined);
}

/**
* Computes the styles of a text node
* @export
* @param {any} element parsed DOM node of text
* @param {any} passProps set of props from the HTML component
* @returns {object} react-native styles
*/
export function computeTextStyles (element, passProps) {
let finalStyle = {};

// Construct an array with the styles of each level of the text node, ie :
// [element, parent1, parent2, parent3...]
const parentStyles = _recursivelyComputeParentTextStyles(element, passProps);

// Only merge the keys that aren't yet applied to the final object. ie:
// if fontSize is already set in the first iteration, ignore the fontSize that
// we got from the 3rd iteration because of a class for instance, hence
// respecting the proper style inheritance
parentStyles.forEach((styles) => {
Object.keys(styles).forEach((styleKey) => {
const styleValue = styles[styleKey];
if (!finalStyle[styleKey]) {
finalStyle[styleKey] = styleValue;
}
});
});

// Finally, try to add the baseFontStyle values to add pontentially missing
// styles to each text node
return { ...passProps.baseFontStyle, ...finalStyle };
}

function _recursivelyComputeParentTextStyles (element, passProps, styles = []) {
const { attribs, name } = element;
const { classesStyles, tagsStyles, defaultTextStyles } = passProps;

// Construct every style for this node
const HTMLAttribsStyles = attribs && attribs.style ? cssStringToRNStyle(attribs.style, STYLESETS.TEXT, passProps) : {};
const classStyles = _getElementClassStyles(attribs, classesStyles);
const userTagStyles = tagsStyles[name];
const defaultTagStyles = defaultTextStyles[name];

// Merge those according to their priority level
const mergedStyles = {
...defaultTagStyles,
...userTagStyles,
...classStyles,
...HTMLAttribsStyles
};

styles.push(mergedStyles);

if (element.parent) {
// Keep looping recursively if this node has parents
return _recursivelyComputeParentTextStyles(element.parent, passProps, styles);
} else {
return styles;
}
}

/**
* Creates a set of style from an array of classes asosciated to a node.
* @export
Expand Down Expand Up @@ -101,7 +161,7 @@ export function _getElementCSSClasses (htmlAttribs) {
* @param {object} { parentTag, emSize, ignoredStyles }
* @returns {object}
*/
function cssToRNStyle (css, styleset, { parentTag, emSize, ptSize, ignoredStyles, allowedStyles }) {
function cssToRNStyle (css, styleset, { emSize, ptSize, ignoredStyles, allowedStyles }) {
const styleProps = stylePropTypes[styleset];
return Object.keys(css)
.filter((key) => allowedStyles ? allowedStyles.indexOf(key) !== -1 : true)
Expand Down

0 comments on commit 40293ba

Please sign in to comment.