Skip to content

Commit

Permalink
Merge pull request Expensify#35462 from ruben-rebelo/ts-migration/HTM…
Browse files Browse the repository at this point in the history
…LRenderers

[TS migration] Migrate HTMLRenderers components
  • Loading branch information
techievivek authored Feb 9, 2024
2 parents 49886be + 54821c2 commit 9463e22
Show file tree
Hide file tree
Showing 17 changed files with 208 additions and 246 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
<RenderHTMLConfigProvider
defaultTextProps={defaultTextProps}
defaultViewProps={defaultViewProps}
// @ts-expect-error TODO: Remove this once HTMLRenderers (https://github.com/Expensify/App/issues/25154) is migrated to TypeScript.
renderers={htmlRenderers}
computeEmbeddedMaxWidth={HTMLEngineUtils.computeEmbeddedMaxWidth}
enableExperimentalBRCollapsing={enableExperimentalBRCollapsing}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import lodashGet from 'lodash/get';
import React from 'react';
import {TNodeChildrenRenderer} from 'react-native-render-html';
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
import AnchorForAttachmentsOnly from '@components/AnchorForAttachmentsOnly';
import AnchorForCommentsOnly from '@components/AnchorForCommentsOnly';
import * as HTMLEngineUtils from '@components/HTMLEngineProvider/htmlEngineUtils';
Expand All @@ -10,21 +10,26 @@ import useThemeStyles from '@hooks/useThemeStyles';
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
import * as Link from '@userActions/Link';
import CONST from '@src/CONST';
import htmlRendererPropTypes from './htmlRendererPropTypes';

function AnchorRenderer(props) {
type AnchorRendererProps = CustomRendererProps<TBlock> & {
/** Key of the element */
key?: string;
};

function AnchorRenderer({tnode, style, key}: AnchorRendererProps) {
const styles = useThemeStyles();
const htmlAttribs = props.tnode.attributes;
const htmlAttribs = tnode.attributes;
const {environmentURL} = useEnvironment();
// An auth token is needed to download Expensify chat attachments
const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]);
const displayName = lodashGet(props.tnode, 'domNode.children[0].data', '');
const parentStyle = lodashGet(props.tnode, 'parent.styles.nativeTextRet', {});
const tNodeChild = tnode?.domNode?.children?.[0];
const displayName = tNodeChild && 'data' in tNodeChild && typeof tNodeChild.data === 'string' ? tNodeChild.data : '';
const parentStyle = tnode.parent?.styles?.nativeTextRet ?? {};
const attrHref = htmlAttribs.href || htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] || '';
const internalNewExpensifyPath = Link.getInternalNewExpensifyPath(attrHref);
const internalExpensifyPath = Link.getInternalExpensifyPath(attrHref);

if (!HTMLEngineUtils.isChildOfComment(props.tnode)) {
if (!HTMLEngineUtils.isChildOfComment(tnode)) {
// This is not a comment from a chat, the AnchorForCommentsOnly uses a Pressable to create a context menu on right click.
// We don't have this behaviour in other links in NewDot
// TODO: We should use TextLink, but I'm leaving it as Text for now because TextLink breaks the alignment in Android.
Expand All @@ -34,7 +39,7 @@ function AnchorRenderer(props) {
onPress={() => Link.openLink(attrHref, environmentURL, isAttachment)}
suppressHighlighting
>
<TNodeChildrenRenderer tnode={props.tnode} />
<TNodeChildrenRenderer tnode={tnode} />
</Text>
);
}
Expand All @@ -58,18 +63,16 @@ function AnchorRenderer(props) {
// eslint-disable-next-line react/jsx-props-no-multi-spaces
target={htmlAttribs.target || '_blank'}
rel={htmlAttribs.rel || 'noopener noreferrer'}
style={{...props.style, ...parentStyle, ...styles.textUnderlinePositionUnder, ...styles.textDecorationSkipInkNone}}
key={props.key}
displayName={displayName}
style={[parentStyle, styles.textUnderlinePositionUnder, styles.textDecorationSkipInkNone, style]}
key={key}
// Only pass the press handler for internal links. For public links or whitelisted internal links fallback to default link handling
onPress={internalNewExpensifyPath || internalExpensifyPath ? () => Link.openLink(attrHref, environmentURL, isAttachment) : undefined}
>
<TNodeChildrenRenderer tnode={props.tnode} />
<TNodeChildrenRenderer tnode={tnode} />
</AnchorForCommentsOnly>
);
}

AnchorRenderer.propTypes = htmlRendererPropTypes;
AnchorRenderer.displayName = 'AnchorRenderer';

export default AnchorRenderer;
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import React from 'react';
import type {TextStyle} from 'react-native';
import {splitBoxModelStyle} from 'react-native-render-html';
import _ from 'underscore';
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
import * as HTMLEngineUtils from '@components/HTMLEngineProvider/htmlEngineUtils';
import InlineCodeBlock from '@components/InlineCodeBlock';
import useStyleUtils from '@hooks/useStyleUtils';
import htmlRendererPropTypes from './htmlRendererPropTypes';

function CodeRenderer(props) {
type CodeRendererProps = CustomRendererProps<TText | TPhrasing> & {
/** Key of the element */
key?: string;
};

function CodeRenderer({TDefaultRenderer, key, style, ...defaultRendererProps}: CodeRendererProps) {
const StyleUtils = useStyleUtils();
// We split wrapper and inner styles
// "boxModelStyle" corresponds to border, margin, padding and backgroundColor
const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(props.style);
const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(style ?? {});

// Get the correct fontFamily variant based in the fontStyle and fontWeight
/** Get the default fontFamily variant */
const font = StyleUtils.getFontFamilyMonospace({
fontStyle: textStyle.fontStyle,
fontWeight: textStyle.fontWeight,
fontStyle: undefined,
fontWeight: undefined,
});

// Determine the font size for the code based on whether it's inside an H1 element.
const isInsideH1 = HTMLEngineUtils.isChildOfH1(props.tnode);
const isInsideH1 = HTMLEngineUtils.isChildOfH1(defaultRendererProps.tnode);

const fontSize = StyleUtils.getCodeFontSize(isInsideH1);

Expand All @@ -34,20 +39,17 @@ function CodeRenderer(props) {
fontStyle: undefined,
};

const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style']);

return (
<InlineCodeBlock
defaultRendererProps={defaultRendererProps}
TDefaultRenderer={props.TDefaultRenderer}
defaultRendererProps={{...defaultRendererProps, style: style as TextStyle}}
TDefaultRenderer={TDefaultRenderer}
boxModelStyle={boxModelStyle}
textStyle={{...textStyle, ...textStyleOverride}}
key={props.key}
key={key}
/>
);
}

CodeRenderer.propTypes = htmlRendererPropTypes;
CodeRenderer.displayName = 'CodeRenderer';

export default CodeRenderer;
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import React from 'react';
import _ from 'underscore';
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import htmlRendererPropTypes from './htmlRendererPropTypes';

const propTypes = {
...htmlRendererPropTypes,
...withLocalizePropTypes,
};

function EditedRenderer(props) {
function EditedRenderer({tnode, TDefaultRenderer, style, ...defaultRendererProps}: CustomRendererProps<TBlock>) {
const theme = useTheme();
const styles = useThemeStyles();
const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']);
const isPendingDelete = Boolean(props.tnode.attributes.deleted !== undefined);
const {translate} = useLocalize();
const isPendingDelete = Boolean(tnode.attributes.deleted !== undefined);
return (
<Text>
<Text
Expand All @@ -33,13 +27,12 @@ function EditedRenderer(props) {
color={theme.textSupporting}
style={[styles.editedLabelStyles, isPendingDelete && styles.offlineFeedback.deleted]}
>
{props.translate('reportActionCompose.edited')}
{translate('reportActionCompose.edited')}
</Text>
</Text>
);
}

EditedRenderer.propTypes = propTypes;
EditedRenderer.displayName = 'EditedRenderer';

export default withLocalize(EditedRenderer);
export default EditedRenderer;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import lodashGet from 'lodash/get';
import React, {memo} from 'react';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus';
import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
import ThumbnailImage from '@components/ThumbnailImage';
Expand All @@ -12,15 +13,22 @@ import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import htmlRendererPropTypes from './htmlRendererPropTypes';
import type {User} from '@src/types/onyx';

const propTypes = {...htmlRendererPropTypes};
type ImageRendererWithOnyxProps = {
/** Current user */
// Following line is disabled because the onyx prop is only being used on the memo HOC
// eslint-disable-next-line react/no-unused-prop-types
user: OnyxEntry<User>;
};

function ImageRenderer(props) {
type ImageRendererProps = ImageRendererWithOnyxProps & CustomRendererProps<TBlock>;

function ImageRenderer({tnode}: ImageRendererProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const htmlAttribs = props.tnode.attributes;
const htmlAttribs = tnode.attributes;

// There are two kinds of images that need to be displayed:
//
Expand Down Expand Up @@ -63,20 +71,10 @@ function ImageRenderer(props) {
<PressableWithoutFocus
style={[styles.noOutline]}
onPress={() => {
const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report.reportID, source);
const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report?.reportID ?? '', source);
Navigation.navigate(route);
}}
onLongPress={(event) =>
showContextMenuForReport(
// Imitate the web event for native renderers
{nativeEvent: {...(event.nativeEvent || {}), target: {tagName: 'IMG'}}},
anchor,
report.reportID,
action,
checkIfContextMenuActive,
ReportUtils.isArchivedRoom(report),
)
}
onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON}
accessibilityLabel={translate('accessibilityHints.viewAttachment')}
>
Expand All @@ -93,18 +91,15 @@ function ImageRenderer(props) {
);
}

ImageRenderer.propTypes = propTypes;
ImageRenderer.displayName = 'ImageRenderer';

export default withOnyx({
export default withOnyx<ImageRendererProps, ImageRendererWithOnyxProps>({
user: {
key: ONYXKEYS.USER,
},
})(
memo(
ImageRenderer,
(prevProps, nextProps) =>
lodashGet(prevProps, 'tnode.attributes') === lodashGet(nextProps, 'tnode.attributes') &&
lodashGet(prevProps, 'user.shouldUseStagingServer') === lodashGet(nextProps, 'user.shouldUseStagingServer'),
(prevProps, nextProps) => prevProps.tnode.attributes === nextProps.tnode.attributes && prevProps.user?.shouldUseStagingServer === nextProps.user?.shouldUseStagingServer,
),
);
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import React from 'react';
import type {TextStyle} from 'react-native';
import {StyleSheet} from 'react-native';
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
import {TNodeChildrenRenderer} from 'react-native-render-html';
import _ from 'underscore';
import Text from '@components/Text';
import useStyleUtils from '@hooks/useStyleUtils';
import htmlRendererPropTypes from './htmlRendererPropTypes';

function MentionHereRenderer(props) {
function MentionHereRenderer({style, tnode}: CustomRendererProps<TText | TPhrasing>) {
const StyleUtils = useStyleUtils();

const flattenStyle = StyleSheet.flatten(style as TextStyle);
const {color, ...styleWithoutColor} = flattenStyle;

return (
<Text>
<Text
// Passing the true value to the function as here mention is always for the current user
color={StyleUtils.getMentionTextColor(true)}
style={[_.omit(props.style, 'color'), StyleUtils.getMentionStyle(true)]}
style={[styleWithoutColor, StyleUtils.getMentionStyle(true)]}
>
<TNodeChildrenRenderer tnode={props.tnode} />
<TNodeChildrenRenderer tnode={tnode} />
</Text>
</Text>
);
}

MentionHereRenderer.propTypes = htmlRendererPropTypes;
MentionHereRenderer.displayName = 'HereMentionRenderer';

export default MentionHereRenderer;
Loading

0 comments on commit 9463e22

Please sign in to comment.