diff --git a/src/CONST.ts b/src/CONST.ts index 0ef02fcbd328..6983eb0bfcb3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -373,7 +373,7 @@ const CONST = { BILLABLE: 'billable', NON_BILLABLE: 'nonBillable', }, - + TASK_TITLE_DISABLED_RULES: ['image'], // Note: Group and Self-DM excluded as these are not tied to a Workspace WORKSPACE_ROOM_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL, chatTypes.POLICY_ROOM, chatTypes.POLICY_EXPENSE_CHAT, chatTypes.INVOICE], ANDROID_PACKAGE_NAME, diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx index b4d163306cb3..4b53eb0b9913 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx @@ -72,7 +72,15 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) { // eslint-disable-next-line react/jsx-props-no-multi-spaces target={htmlAttribs.target || '_blank'} rel={htmlAttribs.rel || 'noopener noreferrer'} - style={[style, parentStyle, textDecorationLineStyle, styles.textUnderlinePositionUnder, styles.textDecorationSkipInkNone, isChildOfTaskTitle && styles.taskTitleMenuItem]} + style={[ + style, + parentStyle, + textDecorationLineStyle, + styles.textUnderlinePositionUnder, + styles.textDecorationSkipInkNone, + isChildOfTaskTitle && styles.taskTitleMenuItem, + styles.dInlineFlex, + ]} 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 ? () => openLink(attrHref, environmentURL, isAttachment) : undefined} @@ -88,7 +96,14 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) { return ( {props.childTnode.data} diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index 4f22d65cbea0..9c7e2827a128 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -24,6 +24,7 @@ import ControlSelection from '@libs/ControlSelection'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; import Navigation from '@libs/Navigation/Navigation'; +import Parser from '@libs/Parser'; import {isCanceledTaskReport, isOpenTaskReport, isReportManager} from '@libs/ReportUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; @@ -64,6 +65,9 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che const {translate} = useLocalize(); const theme = useTheme(); const [taskReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`); + const taskTitle = action?.childReportName ?? taskReport?.reportName ?? ''; + + const taskTitleWithoutImage = Parser.replace(Parser.htmlToMarkdown(taskTitle), {disabledRules: [...CONST.TASK_TITLE_DISABLED_RULES]}); // The reportAction might not contain details regarding the taskReport // Only the direct parent reportAction will contain details about the taskReport @@ -127,7 +131,7 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che )} - ${taskReport?.reportName ?? action?.childReportName ?? ''}`} /> + ${taskTitleWithoutImage}`} /> {shouldShowGreenDotIndicator && ( diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index bc12663e5170..1ec73130e4af 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -22,6 +22,7 @@ import convertToLTR from '@libs/convertToLTR'; import getButtonState from '@libs/getButtonState'; import Navigation from '@libs/Navigation/Navigation'; import {getAvatarsForAccountIDs, getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; +import Parser from '@libs/Parser'; import {getDisplayNameForParticipant, getDisplayNamesWithTooltips, isCompletedTaskReport, isOpenTaskReport} from '@libs/ReportUtils'; import {isActiveTaskEditRoute} from '@libs/TaskUtils'; import {callFunctionIfActionIsAllowed} from '@userActions/Session'; @@ -44,7 +45,8 @@ function TaskView({report}: TaskViewProps) { useEffect(() => { setTaskReport(report); }, [report]); - const taskTitle = `${convertToLTR(report?.reportName ?? '')}`; + const titleWithoutImage = Parser.replace(Parser.htmlToMarkdown(report?.reportName ?? ''), {disabledRules: [...CONST.TASK_TITLE_DISABLED_RULES]}); + const taskTitle = `${convertToLTR(titleWithoutImage)}`; const assigneeTooltipDetails = getDisplayNamesWithTooltips(getPersonalDetailsForAccountIDs(report?.managerID ? [report?.managerID] : [], personalDetails), false); const isOpen = isOpenTaskReport(report); diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 9407c59718bd..5b730d70d8af 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -34,6 +34,7 @@ import { parseForAutocomplete, } from '@libs/SearchAutocompleteUtils'; import {buildSearchQueryJSON, buildUserReadableQueryString, sanitizeSearchValue} from '@libs/SearchQueryUtils'; +import StringUtils from '@libs/StringUtils'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -478,7 +479,12 @@ function SearchAutocompleteList( sections.push({title: translate('search.recentSearches'), data: recentSearchesData}); } - const styledRecentReports = recentReportsOptions.map((item) => ({...item, pressableStyle: styles.br2, wrapperStyle: [styles.pr3, styles.pl3]})); + const styledRecentReports = recentReportsOptions.map((item) => ({ + ...item, + pressableStyle: styles.br2, + text: StringUtils.lineBreaksToSpaces(item.text), + wrapperStyle: [styles.pr3, styles.pl3], + })); sections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports}); if (autocompleteSuggestions.length > 0) { diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 5281032967cf..f3c8353c36c9 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -145,7 +145,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps, { data: [ { - text: `${translate('search.searchIn')} ${reportForContextualSearch.text ?? reportForContextualSearch.alternateText}`, + text: StringUtils.lineBreaksToSpaces(`${translate('search.searchIn')} ${reportForContextualSearch.text ?? reportForContextualSearch.alternateText}`), singleIcon: Expensicons.MagnifyingGlass, searchQuery: reportQueryValue, autocompleteID, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c847a92554a5..c5879d5d6fc4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4862,7 +4862,7 @@ function completeShortMention(text: string): string { * For comments shorter than or equal to 10k chars, convert the comment from MD into HTML because that's how it is stored in the database * For longer comments, skip parsing, but still escape the text, and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!! */ -function getParsedComment(text: string, parsingDetails?: ParsingDetails, mediaAttributes?: Record): string { +function getParsedComment(text: string, parsingDetails?: ParsingDetails, mediaAttributes?: Record, disabledRules?: string[]): string { let isGroupPolicyReport = false; if (parsingDetails?.reportID) { const currentReport = getReportOrDraftReport(parsingDetails?.reportID); @@ -4877,11 +4877,12 @@ function getParsedComment(text: string, parsingDetails?: ParsingDetails, mediaAt } const textWithMention = completeShortMention(text); + const rules = disabledRules ?? []; return text.length <= CONST.MAX_MARKUP_LENGTH ? Parser.replace(textWithMention, { shouldEscapeText: parsingDetails?.shouldEscapeText, - disabledRules: isGroupPolicyReport ? [] : ['reportMentions'], + disabledRules: isGroupPolicyReport ? [...rules] : ['reportMentions', ...rules], extras: {mediaAttributeCache: mediaAttributes}, }) : lodashEscape(text); @@ -6506,7 +6507,7 @@ function buildOptimisticEditedTaskFieldReportAction({title, description}: Task): { type: CONST.REPORT.MESSAGE.TYPE.COMMENT, text: changelog, - html: getParsedComment(changelog), + html: getParsedComment(changelog, undefined, undefined, title !== undefined ? [...CONST.TASK_TITLE_DISABLED_RULES] : undefined), }, ], person: [ @@ -6851,7 +6852,7 @@ function buildOptimisticTaskReport( return { reportID: generateReportID(), - reportName: getParsedComment(title ?? ''), + reportName: getParsedComment(title ?? '', undefined, undefined, [...CONST.TASK_TITLE_DISABLED_RULES]), description: getParsedComment(description ?? '', {}, mediaAttributes), ownerAccountID, participants, diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 9045b1123451..52ad31b64dea 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -550,7 +550,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task // Ensure title is defined before parsing it with getParsedComment. If title is undefined, fall back to reportName from report. // Trim the final parsed title for consistency. - const reportName = title ? ReportUtils.getParsedComment(title) : report?.reportName ?? ''; + const reportName = title ? ReportUtils.getParsedComment(title, undefined, undefined, [...CONST.TASK_TITLE_DISABLED_RULES]) : report?.reportName ?? ''; const parsedTitle = (reportName ?? '').trim(); // Description can be unset, so we default to an empty string if so diff --git a/src/pages/tasks/NewTaskPage.tsx b/src/pages/tasks/NewTaskPage.tsx index afe396891894..c3d0fd55e54c 100644 --- a/src/pages/tasks/NewTaskPage.tsx +++ b/src/pages/tasks/NewTaskPage.tsx @@ -139,6 +139,7 @@ function NewTaskPage({route}: NewTaskPageProps) { shouldShowRightIcon rightLabel={translate('common.required')} shouldParseTitle + excludedMarkdownRules={[...CONST.TASK_TITLE_DISABLED_RULES]} /> ): FormInputErrors => { const errors: FormInputErrors = {}; - const parsedTitle = getParsedComment(title); + const parsedTitle = getParsedComment(title, undefined, undefined, [...CONST.TASK_TITLE_DISABLED_RULES]); const parsedTitleLength = getCommentLength(parsedTitle); if (!parsedTitle) { @@ -110,7 +110,7 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) name={INPUT_IDS.TITLE} label={translate('task.title')} accessibilityLabel={translate('task.title')} - defaultValue={Parser.htmlToMarkdown(report?.reportName ?? '')} + defaultValue={Parser.htmlToMarkdown(report?.reportName ?? '', {})} ref={(element: AnimatedTextInputRef) => { if (!element) { return; diff --git a/src/styles/index.ts b/src/styles/index.ts index ad7f3a5bb808..68646107aae8 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4278,18 +4278,20 @@ const styles = (theme: ThemeColors) => ...writingDirection.ltr, ...headlineFont, fontSize: variables.fontSizeXLarge, - lineHeight: variables.lineHeightSizeh2, + lineHeight: variables.lineHeighTaskTitle, maxWidth: '100%', ...wordBreak.breakWord, + textUnderlineOffset: -1, }, taskTitleMenuItemItalic: { ...writingDirection.ltr, ...headlineItalicFont, fontSize: variables.fontSizeXLarge, - lineHeight: variables.lineHeightSizeh2, + lineHeight: variables.lineHeighTaskTitle, maxWidth: '100%', ...wordBreak.breakWord, + textUnderlineOffset: -1, }, taskDescriptionMenuItem: { diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index b6ee3085d981..c6df26fa2304 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -635,7 +635,7 @@ function getCodeFontSize(isInsideH1: boolean, isInsideTaskTitle?: boolean) { return 15; } if (isInsideTaskTitle) { - return 19; + return 18; } return 13; } diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 2b39f2320b91..15b3edb3736f 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -113,6 +113,7 @@ export default { lineHeightXXXLarge: getValueUsingPixelRatio(32, 37), lineHeightSizeh1: getValueUsingPixelRatio(28, 32), lineHeightSizeh2: getValueUsingPixelRatio(24, 28), + lineHeighTaskTitle: getValueUsingPixelRatio(26, 30), lineHeightSignInHeroXSmall: getValueUsingPixelRatio(32, 37), inputHeight: getValueUsingPixelRatio(52, 72), inputHeightSmall: 28,