diff --git a/.eslintrc.js b/.eslintrc.js index 27014cf9dd7b..198620c70b0f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,7 +8,7 @@ const restrictedImportPaths = [ '', "For 'useWindowDimensions', please use '@src/hooks/useWindowDimensions' instead.", "For 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from '@components/Pressable' instead.", - "For 'StatusBar', please use '@src/libs/StatusBar' instead.", + "For 'StatusBar', please use '@libs/StatusBar' instead.", "For 'Text', please use '@components/Text' instead.", "For 'ScrollView', please use '@components/ScrollView' instead.", ].join('\n'), @@ -59,8 +59,12 @@ const restrictedImportPaths = [ }, { name: 'expensify-common', - importNames: ['Device'], - message: "Do not import Device directly, it's known to make VSCode's IntelliSense crash. Please import the desired module from `expensify-common/dist/Device` instead.", + importNames: ['Device', 'ExpensiMark'], + message: [ + '', + "For 'Device', do not import it directly, it's known to make VSCode's IntelliSense crash. Please import the desired module from `expensify-common/dist/Device` instead.", + "For 'ExpensiMark', please use '@libs/Parser' instead.", + ].join('\n'), }, ]; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 7703b804611a..431a12d00106 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -21,8 +21,8 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; -import {parseHtmlToText} from '@libs/OnyxAwareParser'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; @@ -251,7 +251,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti numberOfLines={1} accessibilityLabel={translate('accessibilityHints.lastChatMessagePreview')} > - {parseHtmlToText(optionItem.alternateText)} + {Parser.htmlToText(optionItem.alternateText)} ) : null} diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index bf12c95825dc..473806aac3af 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -1,4 +1,3 @@ -import {ExpensiMark} from 'expensify-common'; import type {ImageContentFit} from 'expo-image'; import type {ReactElement, ReactNode} from 'react'; import React, {forwardRef, useContext, useMemo} from 'react'; @@ -14,6 +13,7 @@ import ControlSelection from '@libs/ControlSelection'; import convertToLTR from '@libs/convertToLTR'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; +import Parser from '@libs/Parser'; import type {AvatarSource} from '@libs/UserUtils'; import variables from '@styles/variables'; import * as Session from '@userActions/Session'; @@ -433,16 +433,14 @@ function MenuItem( if (!title || !shouldParseTitle) { return ''; } - const parser = new ExpensiMark(); - return parser.replace(title, {shouldEscapeText}); + return Parser.replace(title, {shouldEscapeText}); }, [title, shouldParseTitle, shouldEscapeText]); const helperHtml = useMemo(() => { if (!helperText || !shouldParseHelperText) { return ''; } - const parser = new ExpensiMark(); - return parser.replace(helperText, {shouldEscapeText}); + return Parser.replace(helperText, {shouldEscapeText}); }, [helperText, shouldParseHelperText, shouldEscapeText]); const processedTitle = useMemo(() => { diff --git a/src/hooks/useCopySelectionHelper.ts b/src/hooks/useCopySelectionHelper.ts index ed379bfcf2e6..9bcb9b8b0139 100644 --- a/src/hooks/useCopySelectionHelper.ts +++ b/src/hooks/useCopySelectionHelper.ts @@ -1,7 +1,7 @@ import {useEffect} from 'react'; import Clipboard from '@libs/Clipboard'; import KeyboardShortcut from '@libs/KeyboardShortcut'; -import {parseHtmlToMarkdown, parseHtmlToText} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import SelectionScraper from '@libs/SelectionScraper'; import CONST from '@src/CONST'; @@ -11,10 +11,10 @@ function copySelectionToClipboard() { return; } if (!Clipboard.canSetHtml()) { - Clipboard.setString(parseHtmlToMarkdown(selection)); + Clipboard.setString(Parser.htmlToMarkdown(selection)); return; } - Clipboard.setHtml(selection, parseHtmlToText(selection)); + Clipboard.setHtml(selection, Parser.htmlToText(selection)); } export default function useCopySelectionHelper() { diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 4705a170c3bd..a17bc0134333 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -1,6 +1,6 @@ import {useNavigation} from '@react-navigation/native'; import {useCallback, useEffect} from 'react'; -import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import type UseHtmlPaste from './types'; const insertByCommand = (text: string) => { @@ -71,7 +71,7 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi */ const handlePastedHTML = useCallback( (html: string) => { - paste(parseHtmlToMarkdown(html)); + paste(Parser.htmlToMarkdown(html)); }, [paste], ); diff --git a/src/libs/Log.ts b/src/libs/Log.ts index 64271dee2265..83965807263a 100644 --- a/src/libs/Log.ts +++ b/src/libs/Log.ts @@ -2,7 +2,7 @@ // action would likely cause confusion about which one to use. But most other API methods should happen inside an action file. /* eslint-disable rulesdir/no-api-in-views */ -import {ExpensiMark, Logger} from 'expensify-common'; +import {Logger} from 'expensify-common'; import Onyx from 'react-native-onyx'; import type {Merge} from 'type-fest'; import CONST from '@src/CONST'; @@ -80,6 +80,5 @@ const Log = new Logger({ isDebug: true, }); timeout = setTimeout(() => Log.info('Flushing logs older than 10 minutes', true, {}, true), 10 * 60 * 1000); -ExpensiMark.setLogger(Log); export default Log; diff --git a/src/libs/OnyxAwareParser.ts b/src/libs/OnyxAwareParser.ts deleted file mode 100644 index 51ea39ef972a..000000000000 --- a/src/libs/OnyxAwareParser.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {ExpensiMark} from 'expensify-common'; -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import * as ReportConnection from './ReportConnection'; - -const parser = new ExpensiMark(); - -const accountIDToNameMap: Record = {}; - -Onyx.connect({ - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - callback: (personalDetailsList) => { - Object.values(personalDetailsList ?? {}).forEach((personalDetails) => { - if (!personalDetails) { - return; - } - - accountIDToNameMap[personalDetails.accountID] = personalDetails.login ?? String(personalDetails.accountID); - }); - }, -}); - -function parseHtmlToMarkdown( - html: string, - reportIDToName?: Record, - accountIDToName?: Record, - cacheVideoAttributes?: (videoSource: string, videoAttrs: string) => void, -): string { - return parser.htmlToMarkdown(html, { - reportIDToName: reportIDToName ?? ReportConnection.getAllReportsNameMap(), - accountIDToName: accountIDToName ?? accountIDToNameMap, - cacheVideoAttributes, - }); -} - -function parseHtmlToText( - html: string, - reportIDToName?: Record, - accountIDToName?: Record, - cacheVideoAttributes?: (videoSource: string, videoAttrs: string) => void, -): string { - return parser.htmlToText(html, {reportIDToName: reportIDToName ?? ReportConnection.getAllReportsNameMap(), accountIDToName: accountIDToName ?? accountIDToNameMap, cacheVideoAttributes}); -} - -export {parseHtmlToMarkdown, parseHtmlToText}; diff --git a/src/libs/Parser.ts b/src/libs/Parser.ts new file mode 100644 index 000000000000..cbfeb6e40922 --- /dev/null +++ b/src/libs/Parser.ts @@ -0,0 +1,51 @@ +// eslint-disable-next-line no-restricted-imports +import {ExpensiMark} from 'expensify-common'; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import Log from './Log'; +import * as ReportConnection from './ReportConnection'; + +const accountIDToNameMap: Record = {}; + +Onyx.connect({ + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + callback: (personalDetailsList) => { + Object.values(personalDetailsList ?? {}).forEach((personalDetails) => { + if (!personalDetails) { + return; + } + + accountIDToNameMap[personalDetails.accountID] = personalDetails.login ?? String(personalDetails.accountID); + }); + }, +}); + +type Extras = { + reportIDToName?: Record; + accountIDToName?: Record; + cacheVideoAttributes?: (vidSource: string, attrs: string) => void; + videoAttributeCache?: Record; +}; + +class ExpensiMarkWithContext extends ExpensiMark { + htmlToMarkdown(htmlString: string, extras?: Extras): string { + return super.htmlToMarkdown(htmlString, { + reportIDToName: extras?.reportIDToName ?? ReportConnection.getAllReportsNameMap(), + accountIDToName: extras?.accountIDToName ?? accountIDToNameMap, + cacheVideoAttributes: extras?.cacheVideoAttributes, + }); + } + + htmlToText(htmlString: string, extras?: Extras): string { + return super.htmlToText(htmlString, { + reportIDToName: extras?.reportIDToName ?? ReportConnection.getAllReportsNameMap(), + accountIDToName: extras?.accountIDToName ?? accountIDToNameMap, + cacheVideoAttributes: extras?.cacheVideoAttributes, + }); + } +} + +ExpensiMarkWithContext.setLogger(Log); +const Parser = new ExpensiMarkWithContext(); + +export default Parser; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index aba4f296086b..3f8acd0e06fe 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -20,7 +20,7 @@ import isReportMessageAttachment from './isReportMessageAttachment'; import * as Localize from './Localize'; import Log from './Log'; import type {MessageElementBase, MessageTextElement} from './MessageElement'; -import {parseHtmlToText} from './OnyxAwareParser'; +import Parser from './Parser'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as ReportConnection from './ReportConnection'; import type {OptimisticIOUReportAction, PartialReportAction} from './ReportUtils'; @@ -1080,11 +1080,11 @@ function getReportActionText(reportAction: PartialReportAction): string { // Sometime html can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const text = (message?.html || message?.text) ?? ''; - return text ? parseHtmlToText(text) : ''; + return text ? Parser.htmlToText(text) : ''; } function getTextFromHtml(html?: string): string { - return html ? parseHtmlToText(html) : ''; + return html ? Parser.htmlToText(html) : ''; } function getMemberChangeMessageFragment(reportAction: OnyxEntry): Message { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ee8e221e5aba..5a4e44f6b70c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1,5 +1,5 @@ import {format} from 'date-fns'; -import {ExpensiMark, Str} from 'expensify-common'; +import {Str} from 'expensify-common'; import {isEmpty} from 'lodash'; import lodashEscape from 'lodash/escape'; import lodashFindLastIndex from 'lodash/findLastIndex'; @@ -63,7 +63,7 @@ import ModifiedExpenseMessage from './ModifiedExpenseMessage'; import linkingConfig from './Navigation/linkingConfig'; import Navigation from './Navigation/Navigation'; import * as NumberUtils from './NumberUtils'; -import {parseHtmlToText} from './OnyxAwareParser'; +import Parser from './Parser'; import Permissions from './Permissions'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as PhoneNumber from './PhoneNumber'; @@ -3302,7 +3302,7 @@ function parseReportActionHtmlToText(reportAction: OnyxEntry, repo const logins = PersonalDetailsUtils.getLoginsByAccountIDs(accountIDs); accountIDs.forEach((id, index) => (accountIDToName[id] = logins[index])); - const textMessage = Str.removeSMSDomain(parseHtmlToText(html, reportIDToName, accountIDToName)); + const textMessage = Str.removeSMSDomain(Parser.htmlToText(html, {reportIDToName, accountIDToName})); parsedReportActionMessageCache[key] = textMessage; return textMessage; @@ -3657,7 +3657,6 @@ function getParsedComment(text: string, parsingDetails?: ParsingDetails): string isGroupPolicyReport = isReportInGroupPolicy(currentReport); } - const parser = new ExpensiMark(); const textWithMention = text.replace(CONST.REGEX.SHORT_MENTION, (match) => { if (!Str.isValidMention(match)) { return match; @@ -3668,7 +3667,7 @@ function getParsedComment(text: string, parsingDetails?: ParsingDetails): string }); return text.length <= CONST.MAX_MARKUP_LENGTH - ? parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) + ? Parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) : lodashEscape(text); } @@ -3677,7 +3676,7 @@ function getReportDescriptionText(report: Report): string { return ''; } - return parseHtmlToText(report.description); + return Parser.htmlToText(report.description); } function getPolicyDescriptionText(policy: OnyxEntry): string { @@ -3685,7 +3684,7 @@ function getPolicyDescriptionText(policy: OnyxEntry): string { return ''; } - return parseHtmlToText(policy.description); + return Parser.htmlToText(policy.description); } function buildOptimisticAddCommentReportAction( @@ -3707,10 +3706,10 @@ function buildOptimisticAddCommentReportAction( textForNewComment = CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML; } else if (isTextOnly) { htmlForNewComment = commentText; - textForNewComment = parseHtmlToText(htmlForNewComment); + textForNewComment = Parser.htmlToText(htmlForNewComment); } else { htmlForNewComment = `${commentText}${CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML}`; - textForNewComment = `${parseHtmlToText(commentText)}\n${CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML}`; + textForNewComment = `${Parser.htmlToText(commentText)}\n${CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML}`; } const isAttachment = !text && file !== undefined; diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts index f8472bd43098..218bf3a93c69 100644 --- a/src/libs/actions/Policy/Member.ts +++ b/src/libs/actions/Policy/Member.ts @@ -1,4 +1,3 @@ -import {ExpensiMark} from 'expensify-common'; import type {NullishDeep, OnyxCollection, OnyxCollectionInputValue, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; @@ -12,6 +11,7 @@ import type { import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import Log from '@libs/Log'; +import Parser from '@libs/Parser'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PhoneNumber from '@libs/PhoneNumber'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -627,7 +627,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const params: AddMembersToWorkspaceParams = { employees: JSON.stringify(logins.map((login) => ({email: login}))), - welcomeNote: new ExpensiMark().replace(welcomeNote), + welcomeNote: Parser.replace(welcomeNote), policyID, }; if (!isEmptyObject(membersChats.reportCreationData)) { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4d674726540a..9870b561ad6f 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1,5 +1,5 @@ import {format as timezoneFormat, utcToZonedTime} from 'date-fns-tz'; -import {ExpensiMark, Str} from 'expensify-common'; +import {Str} from 'expensify-common'; import isEmpty from 'lodash/isEmpty'; import {DeviceEventEmitter, InteractionManager, Linking} from 'react-native'; import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; @@ -62,7 +62,7 @@ import {registerPaginationConfig} from '@libs/Middleware/Pagination'; import Navigation from '@libs/Navigation/Navigation'; import type {NetworkStatus} from '@libs/NetworkConnection'; import LocalNotification from '@libs/Notification/LocalNotification'; -import {parseHtmlToMarkdown, parseHtmlToText} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PhoneNumber from '@libs/PhoneNumber'; import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils'; @@ -1498,21 +1498,19 @@ function removeLinksFromHtml(html: string, links: string[]): string { * @param videoAttributeCache cache of video attributes ([videoSource]: videoAttributes) */ function handleUserDeletedLinksInHtml(newCommentText: string, originalCommentMarkdown: string, videoAttributeCache?: Record): string { - const parser = new ExpensiMark(); if (newCommentText.length > CONST.MAX_MARKUP_LENGTH) { return newCommentText; } - const htmlForNewComment = parser.replace(newCommentText, { + const htmlForNewComment = Parser.replace(newCommentText, { extras: {videoAttributeCache}, }); - const removedLinks = parser.getRemovedMarkdownLinks(originalCommentMarkdown, newCommentText); + const removedLinks = Parser.getRemovedMarkdownLinks(originalCommentMarkdown, newCommentText); return removeLinksFromHtml(htmlForNewComment, removedLinks); } /** Saves a new message for a comment. Marks the comment as edited, which will be reflected in the UI. */ function editReportComment(reportID: string, originalReportAction: OnyxEntry, textForNewComment: string, videoAttributeCache?: Record) { - const parser = new ExpensiMark(); const originalReportID = ReportUtils.getOriginalReportID(reportID, originalReportAction); if (!originalReportID || !originalReportAction) { @@ -1523,7 +1521,7 @@ function editReportComment(reportID: string, originalReportAction: OnyxEntry rule.name).filter((name) => name !== 'autolink')}; - parsedOriginalCommentHTML = parser.replace(originalCommentMarkdown, autolinkFilter); + const autolinkFilter = {filterRules: Parser.rules.map((rule) => rule.name).filter((name) => name !== 'autolink')}; + parsedOriginalCommentHTML = Parser.replace(originalCommentMarkdown, autolinkFilter); } // Delete the comment if it's empty diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index 596e49c880e6..8ee260065b63 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -19,7 +19,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {PrivateNotesNavigatorParamList} from '@libs/Navigation/types'; -import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; @@ -52,7 +52,7 @@ function PrivateNotesEditPage({route, personalDetailsList, report, session}: Pri // We need to edit the note in markdown format, but display it in HTML format const [privateNote, setPrivateNote] = useState( - () => ReportActions.getDraftPrivateNote(report.reportID).trim() || parseHtmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim(), + () => ReportActions.getDraftPrivateNote(report.reportID).trim() || Parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim(), ); /** @@ -93,7 +93,7 @@ function PrivateNotesEditPage({route, personalDetailsList, report, session}: Pri const originalNote = report?.privateNotes?.[Number(route.params.accountID)]?.note ?? ''; let editedNote = ''; if (privateNote.trim() !== originalNote.trim()) { - editedNote = ReportActions.handleUserDeletedLinksInHtml(privateNote.trim(), parseHtmlToMarkdown(originalNote).trim()); + editedNote = ReportActions.handleUserDeletedLinksInHtml(privateNote.trim(), Parser.htmlToMarkdown(originalNote).trim()); ReportActions.updatePrivateNotes(report.reportID, Number(route.params.accountID), editedNote); } diff --git a/src/pages/RoomDescriptionPage.tsx b/src/pages/RoomDescriptionPage.tsx index 9f8586292895..1d64ca9e1129 100644 --- a/src/pages/RoomDescriptionPage.tsx +++ b/src/pages/RoomDescriptionPage.tsx @@ -12,7 +12,7 @@ import TextInput from '@components/TextInput'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import variables from '@styles/variables'; @@ -32,7 +32,7 @@ type RoomDescriptionPageProps = { function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) { const styles = useThemeStyles(); - const [description, setDescription] = useState(() => parseHtmlToMarkdown(report?.description ?? '')); + const [description, setDescription] = useState(() => Parser.htmlToMarkdown(report?.description ?? '')); const reportDescriptionInputRef = useRef(null); const focusTimeoutRef = useRef | null>(null); const {translate} = useLocalize(); diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 43ba70d0ffcf..079731d78097 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -18,7 +18,7 @@ import getAttachmentDetails from '@libs/fileDownload/getAttachmentDetails'; import * as Localize from '@libs/Localize'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import Navigation from '@libs/Navigation/Navigation'; -import {parseHtmlToMarkdown, parseHtmlToText} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -42,11 +42,11 @@ function getActionHtml(reportAction: OnyxInputOrEntry): string { /** Sets the HTML string to Clipboard */ function setClipboardMessage(content: string) { if (!Clipboard.canSetHtml()) { - Clipboard.setString(parseHtmlToMarkdown(content)); + Clipboard.setString(Parser.htmlToMarkdown(content)); } else { const anchorRegex = CONST.REGEX_LINK_IN_ANCHOR; const isAnchorTag = anchorRegex.test(content); - const plainText = isAnchorTag ? parseHtmlToMarkdown(content) : parseHtmlToText(content); + const plainText = isAnchorTag ? Parser.htmlToMarkdown(content) : Parser.htmlToText(content); Clipboard.setHtml(content, plainText); } } @@ -238,7 +238,7 @@ const ContextMenuActions: ContextMenuAction[] = [ } const editAction = () => { if (!draftMessage) { - Report.saveReportActionDraft(reportID, reportAction, parseHtmlToMarkdown(getActionHtml(reportAction))); + Report.saveReportActionDraft(reportID, reportAction, Parser.htmlToMarkdown(getActionHtml(reportAction))); } else { Report.deleteReportActionDraft(reportID, reportAction); } diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index c744ad589306..81d5fc58ae12 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -38,7 +38,7 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import getPlatform from '@libs/getPlatform'; import * as KeyDownListener from '@libs/KeyboardShortcut/KeyDownPressListener'; -import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -543,7 +543,7 @@ function ComposerWithSuggestions( event.preventDefault(); if (lastReportAction) { const message = Array.isArray(lastReportAction?.message) ? lastReportAction?.message?.at(-1) ?? null : lastReportAction?.message ?? null; - Report.saveReportActionDraft(reportID, lastReportAction, parseHtmlToMarkdown(message?.html ?? '')); + Report.saveReportActionDraft(reportID, lastReportAction, Parser.htmlToMarkdown(message?.html ?? '')); } } }, diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 1f16d4331e44..d580b9d591eb 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -26,8 +26,8 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import type {Selection} from '@libs/focusComposerWithDelay/types'; import focusEditAfterCancelDelete from '@libs/focusEditAfterCancelDelete'; -import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser'; import onyxSubscribe from '@libs/onyxSubscribe'; +import Parser from '@libs/Parser'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import reportActionItemEventHandler from '@libs/ReportActionItemEventHandler'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -114,8 +114,8 @@ function ReportActionItemMessageEdit( useEffect(() => { draftMessageVideoAttributeCache.clear(); - const originalMessage = parseHtmlToMarkdown(ReportActionsUtils.getReportActionHtml(action), undefined, undefined, (videoSource, attrs) => { - draftMessageVideoAttributeCache.set(videoSource, attrs); + const originalMessage = Parser.htmlToMarkdown(ReportActionsUtils.getReportActionHtml(action), { + cacheVideoAttributes: (videoSource, attrs) => draftMessageVideoAttributeCache.set(videoSource, attrs), }); if (ReportActionsUtils.isDeletedAction(action) || !!(action.message && draftMessage === originalMessage) || !!(prevDraftMessage === draftMessage || isCommentPendingSaved.current)) { return; diff --git a/src/pages/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx index e9b632a0cee9..f5aaf9ea8ffd 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.tsx +++ b/src/pages/tasks/NewTaskDescriptionPage.tsx @@ -1,5 +1,4 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import {ExpensiMark} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -16,7 +15,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {NewTaskNavigatorParamList} from '@libs/Navigation/types'; -import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import variables from '@styles/variables'; @@ -35,8 +34,6 @@ type NewTaskDescriptionPageOnyxProps = { type NewTaskDescriptionPageProps = NewTaskDescriptionPageOnyxProps & StackScreenProps; -const parser = new ExpensiMark(); - function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -80,7 +77,7 @@ function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) { ; -const parser = new ExpensiMark(); - function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -50,7 +47,7 @@ function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) { useEffect(() => { setTaskTitle(task?.title ?? ''); - setTaskDescription(parseHtmlToMarkdown(parser.replace(task?.description ?? ''))); + setTaskDescription(Parser.htmlToMarkdown(Parser.replace(task?.description ?? ''))); }, [task]); const validate = (values: FormOnyxValues): FormInputErrors => { @@ -136,7 +133,7 @@ function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) { autoGrowHeight maxAutoGrowHeight={variables.textInputAutoGrowMaxHeight} shouldSubmitForm - defaultValue={parseHtmlToMarkdown(parser.replace(taskDescription))} + defaultValue={Parser.htmlToMarkdown(Parser.replace(taskDescription))} value={taskDescription} onValueChange={setTaskDescription} isMarkdownEnabled diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index 66bd7d9e9a82..92f5b2394308 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -15,7 +15,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import withReportOrNotFound from '@pages/home/report/withReportOrNotFound'; @@ -48,7 +48,7 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti const submit = useCallback( (values: FormOnyxValues) => { - if (values.description !== parseHtmlToMarkdown(report?.description ?? '') && !isEmptyObject(report)) { + if (values.description !== Parser.htmlToMarkdown(report?.description ?? '') && !isEmptyObject(report)) { // Set the description of the report in the store and then call EditTask API // to update the description of the report on the server Task.editTask(report, {description: values.description}); @@ -111,7 +111,7 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti name={INPUT_IDS.DESCRIPTION} label={translate('newTaskPage.descriptionOptional')} accessibilityLabel={translate('newTaskPage.descriptionOptional')} - defaultValue={parseHtmlToMarkdown(report?.description ?? '')} + defaultValue={Parser.htmlToMarkdown(report?.description ?? '')} ref={(element: AnimatedTextInputRef) => { if (!element) { return; diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 9bab92bc38b0..f7742ecd1361 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -1,5 +1,4 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import {ExpensiMark} from 'expensify-common'; import lodashDebounce from 'lodash/debounce'; import React, {useEffect, useMemo, useState} from 'react'; import {Keyboard, View} from 'react-native'; @@ -22,8 +21,8 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import Parser from '@libs/Parser'; import * as PolicyUtils from '@libs/PolicyUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import type {SettingsNavigatorParamList} from '@navigation/types'; @@ -59,8 +58,6 @@ type WorkspaceInviteMessagePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInviteMessagePageOnyxProps & StackScreenProps; -const parser = new ExpensiMark(); - function WorkspaceInviteMessagePage({ workspaceInviteMessageDraft, invitedEmailsToAccountIDsDraft, @@ -88,7 +85,7 @@ function WorkspaceInviteMessagePage({ // policy?.description can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing policy?.description || - parser.replace( + Parser.replace( translate('workspace.common.welcomeNote', { workspaceName: policy?.name ?? '', }), @@ -96,7 +93,7 @@ function WorkspaceInviteMessagePage({ useEffect(() => { if (!isEmptyObject(invitedEmailsToAccountIDsDraft)) { - setWelcomeNote(parseHtmlToMarkdown(getDefaultWelcomeNote())); + setWelcomeNote(Parser.htmlToMarkdown(getDefaultWelcomeNote())); return; } Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(route.params.policyID), true); diff --git a/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx b/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx index 54dcfd62ac44..527fdf28f8d1 100644 --- a/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx +++ b/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx @@ -1,4 +1,3 @@ -import {ExpensiMark} from 'expensify-common'; import React, {useCallback, useState} from 'react'; import {Keyboard, View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; @@ -12,7 +11,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser'; +import Parser from '@libs/Parser'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import variables from '@styles/variables'; import * as Policy from '@userActions/Policy/Policy'; @@ -24,17 +23,15 @@ import type {WithPolicyProps} from './withPolicy'; type Props = WithPolicyProps; -const parser = new ExpensiMark(); - function WorkspaceProfileDescriptionPage({policy}: Props) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [description, setDescription] = useState(() => - parseHtmlToMarkdown( + Parser.htmlToMarkdown( // policy?.description can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing policy?.description || - parser.replace( + Parser.replace( translate('workspace.common.welcomeNote', { workspaceName: policy?.name ?? '', }), diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index 75532c256534..aa2683091f6f 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -1,6 +1,5 @@ import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import {ExpensiMark} from 'expensify-common'; import React, {useCallback, useState} from 'react'; import type {ImageStyle, StyleProp} from 'react-native'; import {Image, StyleSheet, View} from 'react-native'; @@ -25,6 +24,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; +import Parser from '@libs/Parser'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; @@ -46,7 +46,6 @@ type WorkspaceProfilePageOnyxProps = { }; type WorkspaceProfilePageProps = WithPolicyProps & WorkspaceProfilePageOnyxProps & StackScreenProps; -const parser = new ExpensiMark(); function WorkspaceProfilePage({policyDraft, policy: policyProp, currencyList = {}, route}: WorkspaceProfilePageProps) { const styles = useThemeStyles(); @@ -79,7 +78,7 @@ function WorkspaceProfilePage({policyDraft, policy: policyProp, currencyList = { // policy?.description can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing policy?.description || - parser.replace( + Parser.replace( translate('workspace.common.welcomeNote', { workspaceName: policy?.name ?? '', }), diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenInputForm.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenInputForm.tsx index 08e2ab9460ec..3747bf1b6e4e 100644 --- a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenInputForm.tsx +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenInputForm.tsx @@ -1,4 +1,3 @@ -import {ExpensiMark} from 'expensify-common'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; @@ -12,12 +11,11 @@ import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; import {connectPolicyToNetSuite} from '@libs/actions/connections/NetSuiteCommands'; import * as ErrorUtils from '@libs/ErrorUtils'; +import Parser from '@libs/Parser'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/NetSuiteTokenInputForm'; -const parser = new ExpensiMark(); - function NetSuiteTokenInputForm({onNext, policyID}: SubStepProps & {policyID: string}) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -76,7 +74,7 @@ function NetSuiteTokenInputForm({onNext, policyID}: SubStepProps & {policyID: st {formInput === INPUT_IDS.NETSUITE_ACCOUNT_ID && ( ${parser.replace( + html={`${Parser.replace( translate(`workspace.netsuite.tokenInput.formSteps.enterCredentials.${formInput}Description`), )}`} /> diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenSetupContent.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenSetupContent.tsx index f1b9ed258d7d..889e3101fb32 100644 --- a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenSetupContent.tsx +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenSetupContent.tsx @@ -1,4 +1,3 @@ -import {ExpensiMark} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; import Button from '@components/Button'; @@ -8,11 +7,10 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import Parser from '@libs/Parser'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; -const parser = new ExpensiMark(); - function NetSuiteTokenSetupContent({onNext, screenIndex}: SubStepProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -27,7 +25,7 @@ function NetSuiteTokenSetupContent({onNext, screenIndex}: SubStepProps) { {translate(titleKey)} - ${parser.replace(translate(description))}`} /> + ${Parser.replace(translate(description))}`} />