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

[TS migration] Migrate HTMLRenderers components #35462

Merged
merged 13 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✋ Coming from #36349

We seem to have messed out the style because the props position changed.

// 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
Loading