From 170ae464e224d7d3f299fbcf9ea46ad7508d9126 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 11 Jan 2024 14:09:18 +0000 Subject: [PATCH 01/10] [TS migration] HTMLRenderers components migration # Conflicts: # src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx --- .../{AnchorRenderer.js => AnchorRenderer.tsx} | 28 ++++--- .../HTMLRenderers/CodeRenderer.js | 53 ------------ .../HTMLRenderers/CodeRenderer.tsx | 66 +++++++++++++++ .../{EditedRenderer.js => EditedRenderer.tsx} | 20 ++--- .../{ImageRenderer.js => ImageRenderer.tsx} | 37 +++++--- ...ereRenderer.js => MentionHereRenderer.tsx} | 20 +++-- ...serRenderer.js => MentionUserRenderer.tsx} | 84 ++++++++----------- .../HTMLRenderers/PreRenderer.js | 66 --------------- .../HTMLRenderers/PreRenderer.tsx | 65 ++++++++++++++ .../HTMLRenderers/htmlRendererPropTypes.js | 8 -- .../HTMLRenderers/{index.js => index.ts} | 2 + .../HTMLEngineProvider/HTMLRenderers/types.ts | 25 ++++++ 12 files changed, 252 insertions(+), 222 deletions(-) rename src/components/HTMLEngineProvider/HTMLRenderers/{AnchorRenderer.js => AnchorRenderer.tsx} (78%) delete mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.js create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx rename src/components/HTMLEngineProvider/HTMLRenderers/{EditedRenderer.js => EditedRenderer.tsx} (64%) rename src/components/HTMLEngineProvider/HTMLRenderers/{ImageRenderer.js => ImageRenderer.tsx} (73%) rename src/components/HTMLEngineProvider/HTMLRenderers/{MentionHereRenderer.js => MentionHereRenderer.tsx} (52%) rename src/components/HTMLEngineProvider/HTMLRenderers/{MentionUserRenderer.js => MentionUserRenderer.tsx} (51%) delete mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.js create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx delete mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js rename src/components/HTMLEngineProvider/HTMLRenderers/{index.js => index.ts} (88%) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/types.ts diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx similarity index 78% rename from src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js rename to src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx index c1cd5d6839a2..7f0d550ae2f1 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx @@ -1,4 +1,4 @@ -import lodashGet from 'lodash/get'; +import type {Node} from 'domhandler'; import React from 'react'; import {TNodeChildrenRenderer} from 'react-native-render-html'; import AnchorForAttachmentsOnly from '@components/AnchorForAttachmentsOnly'; @@ -10,21 +10,25 @@ 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'; +import type HtmlRendererProps from './types'; -function AnchorRenderer(props) { +type NodeWithData = Node & { + data: string; +}; + +function AnchorRenderer({tnode, style, key}: HtmlRendererProps) { 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 displayName = (tnode?.domNode?.children?.[0] as NodeWithData).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. @@ -34,7 +38,7 @@ function AnchorRenderer(props) { onPress={() => Link.openLink(attrHref, environmentURL, isAttachment)} suppressHighlighting > - + ); } @@ -58,18 +62,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} > - + ); } -AnchorRenderer.propTypes = htmlRendererPropTypes; AnchorRenderer.displayName = 'AnchorRenderer'; export default AnchorRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.js deleted file mode 100644 index 1932eaaf8a4f..000000000000 --- a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import {splitBoxModelStyle} from 'react-native-render-html'; -import _ from 'underscore'; -import * as HTMLEngineUtils from '@components/HTMLEngineProvider/htmlEngineUtils'; -import InlineCodeBlock from '@components/InlineCodeBlock'; -import useStyleUtils from '@hooks/useStyleUtils'; -import htmlRendererPropTypes from './htmlRendererPropTypes'; - -function CodeRenderer(props) { - const StyleUtils = useStyleUtils(); - // We split wrapper and inner styles - // "boxModelStyle" corresponds to border, margin, padding and backgroundColor - const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(props.style); - - // Get the correct fontFamily variant based in the fontStyle and fontWeight - const font = StyleUtils.getFontFamilyMonospace({ - fontStyle: textStyle.fontStyle, - fontWeight: textStyle.fontWeight, - }); - - // Determine the font size for the code based on whether it's inside an H1 element. - const isInsideH1 = HTMLEngineUtils.isChildOfH1(props.tnode); - - const fontSize = StyleUtils.getCodeFontSize(isInsideH1); - - const textStyleOverride = { - fontSize, - fontFamily: font, - - // We need to override this properties bellow that was defined in `textStyle` - // Because by default the `react-native-render-html` add a style in the elements, - // for example the tag has a fontWeight: "bold" and in the android it break the font - fontWeight: undefined, - fontStyle: undefined, - }; - - const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style']); - - return ( - - ); -} - -CodeRenderer.propTypes = htmlRendererPropTypes; -CodeRenderer.displayName = 'CodeRenderer'; - -export default CodeRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx new file mode 100644 index 000000000000..4ef461a5e2f7 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import {splitBoxModelStyle, TNodeChildrenRenderer} from 'react-native-render-html'; +import type {NativeTextStyles, TText} from 'react-native-render-html'; +import * as HTMLEngineUtils from '@components/HTMLEngineProvider/htmlEngineUtils'; +import InlineCodeBlock from '@components/InlineCodeBlock'; +import useStyleUtils from '@hooks/useStyleUtils'; +import type HtmlRendererProps from './types'; + +type CodeRendererProps = HtmlRendererProps & { + /** The position of this React element relative to the parent React element, starting at 0 */ + renderIndex: number; + + /** The total number of elements children of this React element parent */ + renderLength: number; +}; + +function CodeRenderer({style, TDefaultRenderer, key, tnode, renderIndex, renderLength}: CodeRendererProps) { + const StyleUtils = useStyleUtils(); + // We split wrapper and inner styles + // "boxModelStyle" corresponds to border, margin, padding and backgroundColor + const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle((style as NativeTextStyles) ?? {}); + + /** + * Get the defalult fontFamily variant + * */ + const font = StyleUtils.getFontFamilyMonospace({ + fontStyle: undefined, + fontWeight: undefined, + }); + + // Determine the font size for the code based on whether it's inside an H1 element. + const isInsideH1 = HTMLEngineUtils.isChildOfH1(tnode); + + const fontSize = StyleUtils.getCodeFontSize(isInsideH1); + + const textStyleOverride = { + fontSize, + fontFamily: font, + + // We need to override this properties bellow that was defined in `textStyle` + // Because by default the `react-native-render- html` add a style in the elements, + // for example the tag has a fontWeig ht: "bold" and in the android it break the font + fontWeight: undefined, + fontStyle: undefined, + }; + + const defaultRendererProps = {TNodeChildrenRenderer, style, textProps: {}, type: 'text' as const, viewProps: {}, tnode: tnode as TText, renderIndex, renderLength}; + + if (TDefaultRenderer === undefined) { + return null; + } + + return ( + + ); +} + +CodeRenderer.displayName = 'CodeRenderer'; + +export default CodeRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx similarity index 64% rename from src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js rename to src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx index 9ff5fdecae13..53519a4439e1 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx @@ -1,23 +1,20 @@ import React from 'react'; -import _ from 'underscore'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import withLocalize from '@components/withLocalize'; +import type {WithLocalizeProps} from '@components/withLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import htmlRendererPropTypes from './htmlRendererPropTypes'; +import type HtmlRendererProps from './types'; -const propTypes = { - ...htmlRendererPropTypes, - ...withLocalizePropTypes, -}; +type EditedRendererProps = WithLocalizeProps & HtmlRendererProps; -function EditedRenderer(props) { +function EditedRenderer({key, tnode, translate}: EditedRendererProps) { const theme = useTheme(); const styles = useThemeStyles(); - const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); - const isPendingDelete = Boolean(props.tnode.attributes.deleted !== undefined); + const defaultRendererProps = {key}; + const isPendingDelete = Boolean(tnode.attributes.deleted !== undefined); return ( - {props.translate('reportActionCompose.edited')} + {translate('reportActionCompose.edited')} ); } -EditedRenderer.propTypes = propTypes; EditedRenderer.displayName = 'EditedRenderer'; export default withLocalize(EditedRenderer); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx similarity index 73% rename from src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js rename to src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index f60377c842ea..c2b6ba9afe01 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -1,6 +1,7 @@ -import lodashGet from 'lodash/get'; import React, {memo} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; import ThumbnailImage from '@components/ThumbnailImage'; @@ -12,15 +13,25 @@ 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'; +import type HtmlRendererProps from './types'; -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; +}; -function ImageRenderer(props) { +type ImageRendererProps = ImageRendererWithOnyxProps & HtmlRendererProps; + +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: // @@ -61,17 +72,20 @@ function ImageRenderer(props) { {({anchor, report, action, checkIfContextMenuActive}) => ( ]} onPress={() => { - const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report.reportID, source); + // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. + 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'}}}, + // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. anchor, - report.reportID, + // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. + report?.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report), @@ -93,18 +107,15 @@ function ImageRenderer(props) { ); } -ImageRenderer.propTypes = propTypes; ImageRenderer.displayName = 'ImageRenderer'; -export default withOnyx({ +export default withOnyx({ 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, ), ); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx similarity index 52% rename from src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.js rename to src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx index 93ede229876d..b429c58e9981 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx @@ -1,26 +1,34 @@ import React from 'react'; +import type {StyleProp, TextStyle} from 'react-native'; import {TNodeChildrenRenderer} from 'react-native-render-html'; -import _ from 'underscore'; import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; -import htmlRendererPropTypes from './htmlRendererPropTypes'; +import type HtmlRendererProps from './types'; -function MentionHereRenderer(props) { +function MentionHereRenderer({style, tnode}: HtmlRendererProps) { const StyleUtils = useStyleUtils(); + + const styleWithoutColor: StyleProp = + typeof style === 'object' + ? { + ...style, + color: undefined, + } + : {}; + return ( ]} > - + ); } -MentionHereRenderer.propTypes = htmlRendererPropTypes; MentionHereRenderer.displayName = 'HereMentionRenderer'; export default MentionHereRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx similarity index 51% rename from src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js rename to src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index 3646d9148b3a..b2f4dad83251 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -1,13 +1,13 @@ -import {cloneDeep} from 'lodash'; -import lodashGet from 'lodash/get'; +import isEmpty from 'lodash/isEmpty'; import React from 'react'; +import type {StyleProp, TextStyle} from 'react-native'; import {TNodeChildrenRenderer} from 'react-native-render-html'; -import _ from 'underscore'; import {usePersonalDetails} from '@components/OnyxProvider'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -15,68 +15,51 @@ import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import personalDetailsPropType from '@pages/personalDetailsPropType'; import CONST from '@src/CONST'; -import * as LoginUtils from '@src/libs/LoginUtils'; import ROUTES from '@src/ROUTES'; -import htmlRendererPropTypes from './htmlRendererPropTypes'; +import type {Route} from '@src/ROUTES'; +import type HtmlRendererProps from './types'; -const propTypes = { - ...htmlRendererPropTypes, +type MentionUserRendererProps = WithCurrentUserPersonalDetailsProps & HtmlRendererProps; - /** Current user personal details */ - currentUserPersonalDetails: personalDetailsPropType.isRequired, -}; - -function MentionUserRenderer(props) { +function MentionUserRenderer(props: MentionUserRendererProps) { + const {TDefaultRenderer, style, tnode, currentUserPersonalDetails} = props; const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style']); - const htmlAttributeAccountID = lodashGet(props.tnode.attributes, 'accountid'); + const defaultRendererProps = {TDefaultRenderer, style, ...props}; + const htmlAttribAccountID = tnode.attributes.accountid; const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; - let accountID; - let displayNameOrLogin; - let navigationRoute; - const tnode = cloneDeep(props.tnode); - - const getMentionDisplayText = (displayText, userAccountID, userLogin = '') => { - // If the userAccountID does not exist, this is an email-based mention so the displayText must be an email. - // If the userAccountID exists but userLogin is different from displayText, this means the displayText is either user display name, Hidden, or phone number, in which case we should return it as is. - if (userAccountID && userLogin !== displayText) { - return displayText; - } - - // If the emails are not in the same private domain, we also return the displayText - if (!LoginUtils.areEmailsFromSamePrivateDomain(displayText, props.currentUserPersonalDetails.login)) { - return displayText; - } + let accountID: number; + let displayNameOrLogin: string; + let navigationRoute: Route; - // Otherwise, the emails must be of the same private domain, so we should remove the domain part - return displayText.split('@')[0]; - }; - - if (!_.isEmpty(htmlAttributeAccountID)) { - const user = lodashGet(personalDetails, htmlAttributeAccountID); - accountID = parseInt(htmlAttributeAccountID, 10); - displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(lodashGet(user, 'login', '')) || lodashGet(user, 'displayName', '') || translate('common.hidden'); - displayNameOrLogin = getMentionDisplayText(displayNameOrLogin, htmlAttributeAccountID, lodashGet(user, 'login', '')); - navigationRoute = ROUTES.PROFILE.getRoute(htmlAttributeAccountID); - } else if (!_.isEmpty(tnode.data)) { + if (!isEmpty(htmlAttribAccountID)) { + const user = personalDetails.htmlAttribAccountID; + accountID = parseInt(htmlAttribAccountID, 10); + displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') ?? user?.displayName ?? '' ?? translate('common.hidden'); + navigationRoute = ROUTES.PROFILE.getRoute(htmlAttribAccountID); + } else if (!isEmpty(tnode.data)) { // We need to remove the LTR unicode and leading @ from data as it is not part of the login displayNameOrLogin = tnode.data.replace(CONST.UNICODE.LTR, '').slice(1); - // We need to replace tnode.data here because we will pass it to TNodeChildrenRenderer below - tnode.data = tnode.data.replace(displayNameOrLogin, getMentionDisplayText(displayNameOrLogin, htmlAttributeAccountID)); - accountID = _.first(PersonalDetailsUtils.getAccountIDsByLogins([displayNameOrLogin])); + accountID = PersonalDetailsUtils.getAccountIDsByLogins([displayNameOrLogin]).slice(0)?.[0]; navigationRoute = ROUTES.DETAILS.getRoute(displayNameOrLogin); } else { // If neither an account ID or email is provided, don't render anything return null; } - const isOurMention = accountID === props.currentUserPersonalDetails.accountID; + const isOurMention = accountID === currentUserPersonalDetails.accountID; + + const styleWithoutColor: StyleProp = + typeof style === 'object' + ? { + ...style, + color: undefined, + } + : {}; return ( @@ -98,14 +81,14 @@ function MentionUserRenderer(props) { }} > , {color: StyleUtils.getMentionTextColor(isOurMention)}]} role={CONST.ROLE.LINK} testID="span" href={`/${navigationRoute}`} - // eslint-disable-next-line react/jsx-props-no-spreading - {...defaultRendererProps} > - {!_.isEmpty(htmlAttributeAccountID) ? `@${displayNameOrLogin}` : } + {htmlAttribAccountID !== null ? `@${displayNameOrLogin}` : } @@ -114,7 +97,6 @@ function MentionUserRenderer(props) { ); } -MentionUserRenderer.propTypes = propTypes; MentionUserRenderer.displayName = 'MentionUserRenderer'; export default withCurrentUserPersonalDetails(MentionUserRenderer); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.js deleted file mode 100644 index 27eff02d63ea..000000000000 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.js +++ /dev/null @@ -1,66 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import _ from 'underscore'; -import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; -import withLocalize from '@components/withLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportUtils from '@libs/ReportUtils'; -import CONST from '@src/CONST'; -import htmlRendererPropTypes from './htmlRendererPropTypes'; - -const propTypes = { - /** Press in handler for the code block */ - onPressIn: PropTypes.func, - - /** Press out handler for the code block */ - onPressOut: PropTypes.func, - - /** The position of this React element relative to the parent React element, starting at 0 */ - renderIndex: PropTypes.number.isRequired, - - /** The total number of elements children of this React element parent */ - renderLength: PropTypes.number.isRequired, - - ...htmlRendererPropTypes, -}; - -const defaultProps = { - onPressIn: undefined, - onPressOut: undefined, -}; - -function PreRenderer(props) { - const styles = useThemeStyles(); - const TDefaultRenderer = props.TDefaultRenderer; - const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'onPressIn', 'onPressOut', 'onLongPress']); - const isLast = props.renderIndex === props.renderLength - 1; - - return ( - - - {({anchor, report, action, checkIfContextMenuActive}) => ( - showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} - role={CONST.ROLE.PRESENTATION} - accessibilityLabel={props.translate('accessibilityHints.prestyledText')} - > - - {/* eslint-disable-next-line react/jsx-props-no-spreading */} - - - - )} - - - ); -} - -PreRenderer.displayName = 'PreRenderer'; -PreRenderer.propTypes = propTypes; -PreRenderer.defaultProps = defaultProps; - -export default withLocalize(PreRenderer); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx new file mode 100644 index 000000000000..7a3aa12583e5 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import {View} from 'react-native'; +import type {GestureResponderEvent} from 'react-native'; +import {TNodeChildrenRenderer} from 'react-native-render-html'; +import type {TText} from 'react-native-render-html'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; +import withLocalize from '@components/withLocalize'; +import type {WithLocalizeProps} from '@components/withLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; +import type HtmlRendererProps from './types'; + +type PreRendererProps = HtmlRendererProps & + WithLocalizeProps & { + /** Press in handler for the code block */ + onPressIn?: (event?: GestureResponderEvent | KeyboardEvent) => void; + + /** Press out handler for the code block */ + onPressOut?: (event?: GestureResponderEvent | KeyboardEvent) => void; + + /** Long press handler for the code block */ + onLongPress?: (event?: GestureResponderEvent | KeyboardEvent) => void; + + /** The position of this React element relative to the parent React element, starting at 0 */ + renderIndex: number; + + /** The total number of elements children of this React element parent */ + renderLength: number; + }; + +function PreRenderer({tnode, style, TDefaultRenderer, onPressIn = undefined, onPressOut = undefined, translate, renderIndex, renderLength}: PreRendererProps) { + const styles = useThemeStyles(); + const isLast = renderIndex === renderLength - 1; + + const defaultRendererProps = {TNodeChildrenRenderer, style, textProps: {}, type: 'text' as const, viewProps: {}, tnode: tnode as TText, renderIndex, renderLength}; + + return ( + + + {({anchor, report, action, checkIfContextMenuActive}) => ( + {})} + onPressIn={onPressIn} + onPressOut={onPressOut} + // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. + onLongPress={(event) => showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + role={CONST.ROLE.PRESENTATION} + accessibilityLabel={translate('accessibilityHints.prestyledText')} + > + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + {TDefaultRenderer !== undefined && } + + + )} + + + ); +} + +PreRenderer.displayName = 'PreRenderer'; + +export default withLocalize(PreRenderer); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js b/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js deleted file mode 100644 index f26806482e48..000000000000 --- a/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js +++ /dev/null @@ -1,8 +0,0 @@ -import PropTypes from 'prop-types'; - -export default { - tnode: PropTypes.object, - TDefaultRenderer: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - key: PropTypes.string, - style: PropTypes.object, -}; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.js b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts similarity index 88% rename from src/components/HTMLEngineProvider/HTMLRenderers/index.js rename to src/components/HTMLEngineProvider/HTMLRenderers/index.ts index 9d0dab731792..f86272cc856a 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -20,7 +20,9 @@ export default { // Custom tag renderers edited: EditedRenderer, pre: PreRenderer, + /* eslint-disable @typescript-eslint/naming-convention */ 'mention-user': MentionUserRenderer, 'mention-here': MentionHereRenderer, 'next-step-email': NextStepEmailRenderer, + /* eslint-enable @typescript-eslint/naming-convention */ }; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/types.ts b/src/components/HTMLEngineProvider/HTMLRenderers/types.ts new file mode 100644 index 000000000000..a98a2349cea4 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/types.ts @@ -0,0 +1,25 @@ +import type {StyleProp, TextStyle} from 'react-native'; +import type {TDefaultRenderer, TNode, TText} from 'react-native-render-html'; + +type HtmlRendererProps = { + /** + * Html node to render + */ + tnode: TNode & {data: string}; + + /** + * Renderer function for the node + */ + TDefaultRenderer?: TDefaultRenderer; + + /** + * Key of the element + */ + key?: string; + + /** + * Style for the node + */ + style?: StyleProp; +}; +export default HtmlRendererProps; From 90bd42a514ab9374842d51e266c134477f559752 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Mon, 15 Jan 2024 12:40:15 +0000 Subject: [PATCH 02/10] [TS migration] Code improvements --- .../BaseHTMLEngineProvider.tsx | 1 - .../HTMLRenderers/AnchorRenderer.tsx | 15 ++++--- .../HTMLRenderers/CodeRenderer.tsx | 29 +++++------- .../HTMLRenderers/EditedRenderer.tsx | 13 +++--- .../HTMLRenderers/ImageRenderer.tsx | 7 ++- .../HTMLRenderers/MentionHereRenderer.tsx | 18 +++----- .../HTMLRenderers/MentionUserRenderer.tsx | 25 +++++------ .../HTMLRenderers/NextStepEmailRenderer.tsx | 9 +--- .../HTMLRenderers/PreRenderer.tsx | 45 +++++++++---------- .../HTMLEngineProvider/HTMLRenderers/index.ts | 13 +++--- .../HTMLEngineProvider/HTMLRenderers/types.ts | 25 ----------- src/styles/utils/addOutlineWidth/types.ts | 4 +- src/styles/utils/index.ts | 2 +- 13 files changed, 76 insertions(+), 130 deletions(-) delete mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/types.ts diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index 690f2fc6883a..bd4f72c63ec3 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -93,7 +93,6 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim & { + /** Key of the element */ + key?: string; }; -function AnchorRenderer({tnode, style, key}: HtmlRendererProps) { +function AnchorRenderer({tnode, style, key}: AnchorRendererProps) { const styles = useThemeStyles(); 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 = (tnode?.domNode?.children?.[0] as NodeWithData).data ?? ''; + 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); @@ -62,7 +63,7 @@ function AnchorRenderer({tnode, style, key}: HtmlRendererProps) { // eslint-disable-next-line react/jsx-props-no-multi-spaces target={htmlAttribs.target || '_blank'} rel={htmlAttribs.rel || 'noopener noreferrer'} - style={[{...parentStyle, ...styles.textUnderlinePositionUnder, ...styles.textDecorationSkipInkNone}, style]} + 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} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx index 4ef461a5e2f7..1e3cd031b0a6 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx @@ -1,24 +1,21 @@ import React from 'react'; -import {splitBoxModelStyle, TNodeChildrenRenderer} from 'react-native-render-html'; -import type {NativeTextStyles, TText} from 'react-native-render-html'; +import type {TextStyle} from 'react-native'; +import {splitBoxModelStyle} from 'react-native-render-html'; +import type {CustomRendererProps, TText} from 'react-native-render-html'; import * as HTMLEngineUtils from '@components/HTMLEngineProvider/htmlEngineUtils'; import InlineCodeBlock from '@components/InlineCodeBlock'; import useStyleUtils from '@hooks/useStyleUtils'; -import type HtmlRendererProps from './types'; -type CodeRendererProps = HtmlRendererProps & { - /** The position of this React element relative to the parent React element, starting at 0 */ - renderIndex: number; - - /** The total number of elements children of this React element parent */ - renderLength: number; +type CodeRendererProps = CustomRendererProps & { + /** Key of the element */ + key?: string; }; -function CodeRenderer({style, TDefaultRenderer, key, tnode, renderIndex, renderLength}: CodeRendererProps) { +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((style as NativeTextStyles) ?? {}); + const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(style ?? {}); /** * Get the defalult fontFamily variant @@ -29,7 +26,7 @@ function CodeRenderer({style, TDefaultRenderer, key, tnode, renderIndex, renderL }); // Determine the font size for the code based on whether it's inside an H1 element. - const isInsideH1 = HTMLEngineUtils.isChildOfH1(tnode); + const isInsideH1 = HTMLEngineUtils.isChildOfH1(defaultRendererProps.tnode); const fontSize = StyleUtils.getCodeFontSize(isInsideH1); @@ -44,15 +41,9 @@ function CodeRenderer({style, TDefaultRenderer, key, tnode, renderIndex, renderL fontStyle: undefined, }; - const defaultRendererProps = {TNodeChildrenRenderer, style, textProps: {}, type: 'text' as const, viewProps: {}, tnode: tnode as TText, renderIndex, renderLength}; - - if (TDefaultRenderer === undefined) { - return null; - } - return ( ) { const theme = useTheme(); const styles = useThemeStyles(); - const defaultRendererProps = {key}; + const {translate} = useLocalize(); const isPendingDelete = Boolean(tnode.attributes.deleted !== undefined); return ( @@ -38,4 +35,4 @@ function EditedRenderer({key, tnode, translate}: EditedRendererProps) { EditedRenderer.displayName = 'EditedRenderer'; -export default withLocalize(EditedRenderer); +export default EditedRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index c2b6ba9afe01..9b78b4d950da 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -1,7 +1,7 @@ import React, {memo} from 'react'; -import type {StyleProp, ViewStyle} from 'react-native'; 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'; @@ -14,7 +14,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {User} from '@src/types/onyx'; -import type HtmlRendererProps from './types'; type ImageRendererWithOnyxProps = { /** @@ -25,7 +24,7 @@ type ImageRendererWithOnyxProps = { user: OnyxEntry; }; -type ImageRendererProps = ImageRendererWithOnyxProps & HtmlRendererProps; +type ImageRendererProps = ImageRendererWithOnyxProps & CustomRendererProps; function ImageRenderer({tnode}: ImageRendererProps) { const styles = useThemeStyles(); @@ -72,7 +71,7 @@ function ImageRenderer({tnode}: ImageRendererProps) { {({anchor, report, action, checkIfContextMenuActive}) => ( ]} + style={[styles.noOutline]} onPress={() => { // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report?.reportID, source); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx index b429c58e9981..c24d2f1163cc 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx @@ -1,27 +1,23 @@ import React from 'react'; -import type {StyleProp, TextStyle} from 'react-native'; +import type {TextStyle} from 'react-native'; +import {StyleSheet} from 'react-native'; +import type {CustomRendererProps, TText} from 'react-native-render-html'; import {TNodeChildrenRenderer} from 'react-native-render-html'; import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; -import type HtmlRendererProps from './types'; -function MentionHereRenderer({style, tnode}: HtmlRendererProps) { +function MentionHereRenderer({style, tnode}: CustomRendererProps) { const StyleUtils = useStyleUtils(); - const styleWithoutColor: StyleProp = - typeof style === 'object' - ? { - ...style, - color: undefined, - } - : {}; + const flattenStyle = StyleSheet.flatten(style as TextStyle); + const {color, ...styleWithoutColor} = flattenStyle; return ( ]} + style={[styleWithoutColor, StyleUtils.getMentionStyle(true) as TextStyle]} > diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index b2f4dad83251..c62bf78d397b 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -1,6 +1,8 @@ import isEmpty from 'lodash/isEmpty'; import React from 'react'; +import {StyleSheet} from 'react-native'; import type {StyleProp, TextStyle} from 'react-native'; +import type {CustomRendererProps, TText} from 'react-native-render-html'; import {TNodeChildrenRenderer} from 'react-native-render-html'; import {usePersonalDetails} from '@components/OnyxProvider'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; @@ -18,16 +20,14 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; -import type HtmlRendererProps from './types'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; -type MentionUserRendererProps = WithCurrentUserPersonalDetailsProps & HtmlRendererProps; +type MentionUserRendererProps = WithCurrentUserPersonalDetailsProps & CustomRendererProps; -function MentionUserRenderer(props: MentionUserRendererProps) { - const {TDefaultRenderer, style, tnode, currentUserPersonalDetails} = props; +function MentionUserRenderer({style, tnode, currentUserPersonalDetails, ...defaultRendererProps}: MentionUserRendererProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const defaultRendererProps = {TDefaultRenderer, style, ...props}; const htmlAttribAccountID = tnode.attributes.accountid; const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; @@ -40,11 +40,11 @@ function MentionUserRenderer(props: MentionUserRendererProps) { accountID = parseInt(htmlAttribAccountID, 10); displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') ?? user?.displayName ?? '' ?? translate('common.hidden'); navigationRoute = ROUTES.PROFILE.getRoute(htmlAttribAccountID); - } else if (!isEmpty(tnode.data)) { + } else if (!isEmptyObject(tnode.data)) { // We need to remove the LTR unicode and leading @ from data as it is not part of the login displayNameOrLogin = tnode.data.replace(CONST.UNICODE.LTR, '').slice(1); - accountID = PersonalDetailsUtils.getAccountIDsByLogins([displayNameOrLogin]).slice(0)?.[0]; + accountID = PersonalDetailsUtils.getAccountIDsByLogins([displayNameOrLogin])?.[0]; navigationRoute = ROUTES.DETAILS.getRoute(displayNameOrLogin); } else { // If neither an account ID or email is provided, don't render anything @@ -53,13 +53,8 @@ function MentionUserRenderer(props: MentionUserRendererProps) { const isOurMention = accountID === currentUserPersonalDetails.accountID; - const styleWithoutColor: StyleProp = - typeof style === 'object' - ? { - ...style, - color: undefined, - } - : {}; + const flattenStyle = StyleSheet.flatten(style as TextStyle); + const {color, ...styleWithoutColor} = flattenStyle; return ( @@ -88,7 +83,7 @@ function MentionUserRenderer(props: MentionUserRendererProps) { testID="span" href={`/${navigationRoute}`} > - {htmlAttribAccountID !== null ? `@${displayNameOrLogin}` : } + {htmlAttribAccountID ? `@${displayNameOrLogin}` : } diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx index b8292cad60a2..df2136e1b4b9 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx @@ -1,14 +1,9 @@ import React from 'react'; +import type {CustomRendererProps, TText} from 'react-native-render-html'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; -type NextStepEmailRendererProps = { - tnode: { - data: string; - }; -}; - -function NextStepEmailRenderer({tnode}: NextStepEmailRendererProps) { +function NextStepEmailRenderer({tnode}: CustomRendererProps) { const styles = useThemeStyles(); return {tnode.data}; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx index 7a3aa12583e5..0f1820463984 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -1,43 +1,38 @@ import React from 'react'; import {View} from 'react-native'; import type {GestureResponderEvent} from 'react-native'; -import {TNodeChildrenRenderer} from 'react-native-render-html'; -import type {TText} from 'react-native-render-html'; +import type {CustomRendererProps, TBlock} from 'react-native-render-html'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; -import withLocalize from '@components/withLocalize'; -import type {WithLocalizeProps} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; -import type HtmlRendererProps from './types'; -type PreRendererProps = HtmlRendererProps & - WithLocalizeProps & { - /** Press in handler for the code block */ - onPressIn?: (event?: GestureResponderEvent | KeyboardEvent) => void; +type PreRendererProps = CustomRendererProps & { + /** Press in handler for the code block */ + onPressIn?: (event?: GestureResponderEvent | KeyboardEvent) => void; - /** Press out handler for the code block */ - onPressOut?: (event?: GestureResponderEvent | KeyboardEvent) => void; + /** Press out handler for the code block */ + onPressOut?: (event?: GestureResponderEvent | KeyboardEvent) => void; - /** Long press handler for the code block */ - onLongPress?: (event?: GestureResponderEvent | KeyboardEvent) => void; + /** Long press handler for the code block */ + onLongPress?: (event?: GestureResponderEvent | KeyboardEvent) => void; - /** The position of this React element relative to the parent React element, starting at 0 */ - renderIndex: number; + /** The position of this React element relative to the parent React element, starting at 0 */ + renderIndex: number; - /** The total number of elements children of this React element parent */ - renderLength: number; - }; + /** The total number of elements children of this React element parent */ + renderLength: number; +}; -function PreRenderer({tnode, style, TDefaultRenderer, onPressIn = undefined, onPressOut = undefined, translate, renderIndex, renderLength}: PreRendererProps) { +function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...defaultRendererProps}: PreRendererProps) { const styles = useThemeStyles(); - const isLast = renderIndex === renderLength - 1; - - const defaultRendererProps = {TNodeChildrenRenderer, style, textProps: {}, type: 'text' as const, viewProps: {}, tnode: tnode as TText, renderIndex, renderLength}; + const {translate} = useLocalize(); + const isLast = defaultRendererProps.renderIndex === defaultRendererProps.renderLength - 1; return ( - + {({anchor, report, action, checkIfContextMenuActive}) => ( {/* eslint-disable-next-line react/jsx-props-no-spreading */} - {TDefaultRenderer !== undefined && } + )} @@ -62,4 +57,4 @@ function PreRenderer({tnode, style, TDefaultRenderer, onPressIn = undefined, onP PreRenderer.displayName = 'PreRenderer'; -export default withLocalize(PreRenderer); +export default PreRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts index f86272cc856a..9a4104d4efc5 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -1,3 +1,4 @@ +import type {CustomTagRendererRecord, CustomTextualRenderer} from 'react-native-render-html'; import AnchorRenderer from './AnchorRenderer'; import CodeRenderer from './CodeRenderer'; import EditedRenderer from './EditedRenderer'; @@ -10,10 +11,10 @@ import PreRenderer from './PreRenderer'; /** * This collection defines our custom renderers. It is a mapping from HTML tag type to the corresponding component. */ -export default { +const HTMLEngineProviderComponentList: CustomTagRendererRecord = { // Standard HTML tag renderers a: AnchorRenderer, - code: CodeRenderer, + code: CodeRenderer as CustomTextualRenderer, img: ImageRenderer, video: AnchorRenderer, // temporary until we have a video player component @@ -21,8 +22,10 @@ export default { edited: EditedRenderer, pre: PreRenderer, /* eslint-disable @typescript-eslint/naming-convention */ - 'mention-user': MentionUserRenderer, - 'mention-here': MentionHereRenderer, - 'next-step-email': NextStepEmailRenderer, + 'mention-user': MentionUserRenderer as CustomTextualRenderer, + 'mention-here': MentionHereRenderer as CustomTextualRenderer, + 'next-step-email': NextStepEmailRenderer as CustomTextualRenderer, /* eslint-enable @typescript-eslint/naming-convention */ }; + +export default HTMLEngineProviderComponentList; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/types.ts b/src/components/HTMLEngineProvider/HTMLRenderers/types.ts deleted file mode 100644 index a98a2349cea4..000000000000 --- a/src/components/HTMLEngineProvider/HTMLRenderers/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type {StyleProp, TextStyle} from 'react-native'; -import type {TDefaultRenderer, TNode, TText} from 'react-native-render-html'; - -type HtmlRendererProps = { - /** - * Html node to render - */ - tnode: TNode & {data: string}; - - /** - * Renderer function for the node - */ - TDefaultRenderer?: TDefaultRenderer; - - /** - * Key of the element - */ - key?: string; - - /** - * Style for the node - */ - style?: StyleProp; -}; -export default HtmlRendererProps; diff --git a/src/styles/utils/addOutlineWidth/types.ts b/src/styles/utils/addOutlineWidth/types.ts index 45975b72dc8a..d3a2538cd1b2 100644 --- a/src/styles/utils/addOutlineWidth/types.ts +++ b/src/styles/utils/addOutlineWidth/types.ts @@ -1,6 +1,6 @@ -import type {TextStyle} from 'react-native'; +import type {TextStyle, ViewStyle} from 'react-native'; import type {ThemeColors} from '@styles/theme/types'; -type AddOutlineWidth = (theme: ThemeColors, obj: TextStyle, val?: number, hasError?: boolean) => TextStyle; +type AddOutlineWidth = (theme: ThemeColors, obj: TStyle, val?: number, hasError?: boolean) => TStyle; export default AddOutlineWidth; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index a0f8f52927b9..1f6a784e2a5f 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1341,7 +1341,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ /** * Returns style object for the user mention component based on whether the mention is ours or not. */ - getMentionStyle: (isOurMention: boolean): ViewStyle => { + getMentionStyle: (isOurMention: boolean): ViewStyle | StyleProp => { const backgroundColor = isOurMention ? theme.ourMentionBG : theme.mentionBG; return { backgroundColor, From 66461db03759681d1ff53ab049672ed66bafe724 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Tue, 16 Jan 2024 09:40:10 +0000 Subject: [PATCH 03/10] [TS migration] Addressed suggestions HTMLRenderers --- .../HTMLRenderers/CodeRenderer.tsx | 8 ++++---- .../HTMLRenderers/EditedRenderer.tsx | 2 +- .../HTMLRenderers/MentionHereRenderer.tsx | 6 +++--- .../HTMLRenderers/MentionUserRenderer.tsx | 14 +++++++------- .../HTMLRenderers/NextStepEmailRenderer.tsx | 6 +++--- .../HTMLEngineProvider/HTMLRenderers/index.ts | 10 +++++----- src/components/InlineCodeBlock/index.native.tsx | 6 +++--- src/components/InlineCodeBlock/index.tsx | 6 +++--- src/components/InlineCodeBlock/types.ts | 7 +++++-- src/styles/utils/index.ts | 2 +- 10 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx index 1e3cd031b0a6..6b06d7de20ec 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx @@ -1,12 +1,12 @@ import React from 'react'; import type {TextStyle} from 'react-native'; import {splitBoxModelStyle} from 'react-native-render-html'; -import type {CustomRendererProps, TText} from 'react-native-render-html'; +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'; -type CodeRendererProps = CustomRendererProps & { +type CodeRendererProps = CustomRendererProps & { /** Key of the element */ key?: string; }; @@ -35,8 +35,8 @@ function CodeRenderer({TDefaultRenderer, key, style, ...defaultRendererProps}: C fontFamily: font, // We need to override this properties bellow that was defined in `textStyle` - // Because by default the `react-native-render- html` add a style in the elements, - // for example the tag has a fontWeig ht: "bold" and in the android it break the font + // Because by default the `react-native-render-html` add a style in the elements, + // for example the tag has a fontWeight: "bold" and in the android it break the font fontWeight: undefined, fontStyle: undefined, }; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx index 6b23fdc86df6..03f7a5dbedf7 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx @@ -7,7 +7,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -function EditedRenderer({tnode, ...defaultRendererProps}: CustomRendererProps) { +function EditedRenderer({tnode, TDefaultRenderer, style, ...defaultRendererProps}: CustomRendererProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx index c24d2f1163cc..09dc8cf9f641 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx @@ -1,12 +1,12 @@ import React from 'react'; import type {TextStyle} from 'react-native'; import {StyleSheet} from 'react-native'; -import type {CustomRendererProps, TText} from 'react-native-render-html'; +import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import {TNodeChildrenRenderer} from 'react-native-render-html'; import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; -function MentionHereRenderer({style, tnode}: CustomRendererProps) { +function MentionHereRenderer({style, tnode}: CustomRendererProps) { const StyleUtils = useStyleUtils(); const flattenStyle = StyleSheet.flatten(style as TextStyle); @@ -17,7 +17,7 @@ function MentionHereRenderer({style, tnode}: CustomRendererProps) { diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index c62bf78d397b..bc84caa7a738 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -1,8 +1,8 @@ import isEmpty from 'lodash/isEmpty'; import React from 'react'; import {StyleSheet} from 'react-native'; -import type {StyleProp, TextStyle} from 'react-native'; -import type {CustomRendererProps, TText} from 'react-native-render-html'; +import type {TextStyle} from 'react-native'; +import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import {TNodeChildrenRenderer} from 'react-native-render-html'; import {usePersonalDetails} from '@components/OnyxProvider'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; @@ -22,9 +22,9 @@ import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -type MentionUserRendererProps = WithCurrentUserPersonalDetailsProps & CustomRendererProps; +type MentionUserRendererProps = WithCurrentUserPersonalDetailsProps & CustomRendererProps; -function MentionUserRenderer({style, tnode, currentUserPersonalDetails, ...defaultRendererProps}: MentionUserRendererProps) { +function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersonalDetails, ...defaultRendererProps}: MentionUserRendererProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); @@ -38,9 +38,9 @@ function MentionUserRenderer({style, tnode, currentUserPersonalDetails, ...defau if (!isEmpty(htmlAttribAccountID)) { const user = personalDetails.htmlAttribAccountID; accountID = parseInt(htmlAttribAccountID, 10); - displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') ?? user?.displayName ?? '' ?? translate('common.hidden'); + displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') ?? user?.displayName ?? translate('common.hidden'); navigationRoute = ROUTES.PROFILE.getRoute(htmlAttribAccountID); - } else if (!isEmptyObject(tnode.data)) { + } else if ('data' in tnode && !isEmptyObject(tnode.data)) { // We need to remove the LTR unicode and leading @ from data as it is not part of the login displayNameOrLogin = tnode.data.replace(CONST.UNICODE.LTR, '').slice(1); @@ -78,7 +78,7 @@ function MentionUserRenderer({style, tnode, currentUserPersonalDetails, ...defau , {color: StyleUtils.getMentionTextColor(isOurMention)}]} + style={[styles.link, styleWithoutColor, StyleUtils.getMentionStyle(isOurMention), {color: StyleUtils.getMentionTextColor(isOurMention)}]} role={CONST.ROLE.LINK} testID="span" href={`/${navigationRoute}`} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx index df2136e1b4b9..e5a9dc92f03a 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import type {CustomRendererProps, TText} from 'react-native-render-html'; +import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; -function NextStepEmailRenderer({tnode}: CustomRendererProps) { +function NextStepEmailRenderer({tnode}: CustomRendererProps) { const styles = useThemeStyles(); - return {tnode.data}; + return {'data' in tnode && tnode.data}; } NextStepEmailRenderer.displayName = 'NextStepEmailRenderer'; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts index 9a4104d4efc5..f2c8cbe89a98 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -1,4 +1,4 @@ -import type {CustomTagRendererRecord, CustomTextualRenderer} from 'react-native-render-html'; +import type {CustomTagRendererRecord} from 'react-native-render-html'; import AnchorRenderer from './AnchorRenderer'; import CodeRenderer from './CodeRenderer'; import EditedRenderer from './EditedRenderer'; @@ -14,7 +14,7 @@ import PreRenderer from './PreRenderer'; const HTMLEngineProviderComponentList: CustomTagRendererRecord = { // Standard HTML tag renderers a: AnchorRenderer, - code: CodeRenderer as CustomTextualRenderer, + code: CodeRenderer, img: ImageRenderer, video: AnchorRenderer, // temporary until we have a video player component @@ -22,9 +22,9 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = { edited: EditedRenderer, pre: PreRenderer, /* eslint-disable @typescript-eslint/naming-convention */ - 'mention-user': MentionUserRenderer as CustomTextualRenderer, - 'mention-here': MentionHereRenderer as CustomTextualRenderer, - 'next-step-email': NextStepEmailRenderer as CustomTextualRenderer, + 'mention-user': MentionUserRenderer, + 'mention-here': MentionHereRenderer, + 'next-step-email': NextStepEmailRenderer, /* eslint-enable @typescript-eslint/naming-convention */ }; diff --git a/src/components/InlineCodeBlock/index.native.tsx b/src/components/InlineCodeBlock/index.native.tsx index 3a70308fa0cc..85d02b7239ca 100644 --- a/src/components/InlineCodeBlock/index.native.tsx +++ b/src/components/InlineCodeBlock/index.native.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import type {TText} from 'react-native-render-html'; import useThemeStyles from '@hooks/useThemeStyles'; import type InlineCodeBlockProps from './types'; +import type {TTextOrTPhrasing} from './types'; import WrappedText from './WrappedText'; -function InlineCodeBlock({TDefaultRenderer, defaultRendererProps, textStyle, boxModelStyle}: InlineCodeBlockProps) { +function InlineCodeBlock({TDefaultRenderer, defaultRendererProps, textStyle, boxModelStyle}: InlineCodeBlockProps) { const styles = useThemeStyles(); return ( @@ -16,7 +16,7 @@ function InlineCodeBlock({TDefaultRenderer, defaultRen textStyles={textStyle} wordStyles={[boxModelStyle, styles.codeWordStyle]} > - {defaultRendererProps.tnode.data} + {'data' in defaultRendererProps.tnode && defaultRendererProps.tnode.data} ); diff --git a/src/components/InlineCodeBlock/index.tsx b/src/components/InlineCodeBlock/index.tsx index 0802d4752661..312b7aaf4943 100644 --- a/src/components/InlineCodeBlock/index.tsx +++ b/src/components/InlineCodeBlock/index.tsx @@ -1,10 +1,10 @@ import React from 'react'; import {StyleSheet} from 'react-native'; -import type {TText} from 'react-native-render-html'; import Text from '@components/Text'; import type InlineCodeBlockProps from './types'; +import type { TTextOrTPhrasing } from './types'; -function InlineCodeBlock({TDefaultRenderer, textStyle, defaultRendererProps, boxModelStyle}: InlineCodeBlockProps) { +function InlineCodeBlock({TDefaultRenderer, textStyle, defaultRendererProps, boxModelStyle}: InlineCodeBlockProps) { const flattenTextStyle = StyleSheet.flatten(textStyle); const {textDecorationLine, ...textStyles} = flattenTextStyle; @@ -13,7 +13,7 @@ function InlineCodeBlock({TDefaultRenderer, textStyle, // eslint-disable-next-line react/jsx-props-no-spreading {...defaultRendererProps} > - {defaultRendererProps.tnode.data} + {'data' in defaultRendererProps.tnode && defaultRendererProps.tnode.data} ); } diff --git a/src/components/InlineCodeBlock/types.ts b/src/components/InlineCodeBlock/types.ts index ae847b293a60..b1d870000d41 100644 --- a/src/components/InlineCodeBlock/types.ts +++ b/src/components/InlineCodeBlock/types.ts @@ -1,7 +1,9 @@ import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; -import type {TDefaultRenderer, TDefaultRendererProps, TText} from 'react-native-render-html'; +import type {TDefaultRenderer, TDefaultRendererProps, TPhrasing, TText} from 'react-native-render-html'; -type InlineCodeBlockProps = { +type TTextOrTPhrasing = TText | TPhrasing; + +type InlineCodeBlockProps = { TDefaultRenderer: TDefaultRenderer; textStyle: StyleProp; defaultRendererProps: TDefaultRendererProps; @@ -9,3 +11,4 @@ type InlineCodeBlockProps = { }; export default InlineCodeBlockProps; +export type {TTextOrTPhrasing} diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 1f6a784e2a5f..e3a82d64deaf 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1341,7 +1341,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ /** * Returns style object for the user mention component based on whether the mention is ours or not. */ - getMentionStyle: (isOurMention: boolean): ViewStyle | StyleProp => { + getMentionStyle: (isOurMention: boolean): TextStyle => { const backgroundColor = isOurMention ? theme.ourMentionBG : theme.mentionBG; return { backgroundColor, From 5b7cd2bce7d6f46b0d4f4dede7693727ab16b4cb Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Tue, 16 Jan 2024 09:47:03 +0000 Subject: [PATCH 04/10] [TS migration] HTMLRenderers prettified --- src/components/InlineCodeBlock/index.tsx | 2 +- src/components/InlineCodeBlock/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/InlineCodeBlock/index.tsx b/src/components/InlineCodeBlock/index.tsx index 312b7aaf4943..593a08aaad5e 100644 --- a/src/components/InlineCodeBlock/index.tsx +++ b/src/components/InlineCodeBlock/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {StyleSheet} from 'react-native'; import Text from '@components/Text'; import type InlineCodeBlockProps from './types'; -import type { TTextOrTPhrasing } from './types'; +import type {TTextOrTPhrasing} from './types'; function InlineCodeBlock({TDefaultRenderer, textStyle, defaultRendererProps, boxModelStyle}: InlineCodeBlockProps) { const flattenTextStyle = StyleSheet.flatten(textStyle); diff --git a/src/components/InlineCodeBlock/types.ts b/src/components/InlineCodeBlock/types.ts index b1d870000d41..cc05f36a20cf 100644 --- a/src/components/InlineCodeBlock/types.ts +++ b/src/components/InlineCodeBlock/types.ts @@ -11,4 +11,4 @@ type InlineCodeBlockProps = { }; export default InlineCodeBlockProps; -export type {TTextOrTPhrasing} +export type {TTextOrTPhrasing}; From 42cbbd50ad59956abadbc31d0dd94ea3ce3fb166 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Tue, 16 Jan 2024 09:59:04 +0000 Subject: [PATCH 05/10] [TS migration] HTMLRenderers applied code suggestion --- .../HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index bc84caa7a738..b6020cf120e7 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -38,7 +38,8 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona if (!isEmpty(htmlAttribAccountID)) { const user = personalDetails.htmlAttribAccountID; accountID = parseInt(htmlAttribAccountID, 10); - displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') ?? user?.displayName ?? translate('common.hidden'); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') || user?.displayName || translate('common.hidden'); navigationRoute = ROUTES.PROFILE.getRoute(htmlAttribAccountID); } else if ('data' in tnode && !isEmptyObject(tnode.data)) { // We need to remove the LTR unicode and leading @ from data as it is not part of the login From 71640567161ffb4f661fac243b0a5727e10d5d22 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Tue, 16 Jan 2024 10:02:11 +0000 Subject: [PATCH 06/10] [TS migration] Comment style --- .../HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx index 6b06d7de20ec..d1c11dc12ed5 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.tsx @@ -17,9 +17,7 @@ function CodeRenderer({TDefaultRenderer, key, style, ...defaultRendererProps}: C // "boxModelStyle" corresponds to border, margin, padding and backgroundColor const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(style ?? {}); - /** - * Get the defalult fontFamily variant - * */ + /** Get the default fontFamily variant */ const font = StyleUtils.getFontFamilyMonospace({ fontStyle: undefined, fontWeight: undefined, From 3b3626733f3b9b2a32b6e5ad8bd0c78fe6a04cb4 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Tue, 16 Jan 2024 10:54:22 +0000 Subject: [PATCH 07/10] [TS migration] HTMLRenderers better check for typesafety --- .../HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx index e5a9dc92f03a..e91b6745e8b4 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx @@ -6,7 +6,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; function NextStepEmailRenderer({tnode}: CustomRendererProps) { const styles = useThemeStyles(); - return {'data' in tnode && tnode.data}; + return {'data' in tnode ? tnode.data : ''}; } NextStepEmailRenderer.displayName = 'NextStepEmailRenderer'; From a068c0c96ea27fb5886586b0b48fcb4bf0badf53 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 18 Jan 2024 09:46:48 +0000 Subject: [PATCH 08/10] [TS migration] Fixed issues after merge with master --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 9b78b4d950da..8c567685d0f4 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -73,18 +73,14 @@ function ImageRenderer({tnode}: ImageRendererProps) { { - // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. - 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'}}}, - // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. + event, anchor, - // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. - report?.reportID, + report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report), From 2e0f786821d9557d3645dd0ae8fb6451c798bbc7 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 18 Jan 2024 09:53:23 +0000 Subject: [PATCH 09/10] [TS migration] Lint fixes --- .../HTMLRenderers/ImageRenderer.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 8c567685d0f4..74d5c39d162b 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -76,16 +76,7 @@ function ImageRenderer({tnode}: ImageRendererProps) { const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report?.reportID ?? '', source); Navigation.navigate(route); }} - onLongPress={(event) => - showContextMenuForReport( - event, - 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')} > From 61fc9c6d24356265db2497f5a8fe74bd7e93018c Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Mon, 29 Jan 2024 15:18:54 +0000 Subject: [PATCH 10/10] [TS migration][HTMlRenderers] After merge code changes # Conflicts: # src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx | 4 +--- .../HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx | 2 +- .../HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx | 3 +-- src/components/ReportActionItem/ReportActionItemImage.tsx | 1 - 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 74d5c39d162b..3e6119ff279f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -16,9 +16,7 @@ import ROUTES from '@src/ROUTES'; import type {User} from '@src/types/onyx'; type ImageRendererWithOnyxProps = { - /** - * Current user - */ + /** 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; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index b6020cf120e7..ad9cfb4e6384 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -62,7 +62,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona {({anchor, report, action, checkIfContextMenuActive}) => ( showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} onPress={(event) => { event.preventDefault(); Navigation.navigate(navigationRoute); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx index 0f1820463984..798ec8f64194 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -39,8 +39,7 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d onPress={onPressIn ?? (() => {})} onPressIn={onPressIn} onPressOut={onPressOut} - // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. - onLongPress={(event) => showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} role={CONST.ROLE.PRESENTATION} accessibilityLabel={translate('accessibilityHints.prestyledText')} > diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index 710adeb1589e..10e66adbef6c 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -93,7 +93,6 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, tr // @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript. ({show}) => (