From 1e900c23c1b9d63c0188b0ab54a0f008d384e9bb Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sat, 7 Dec 2024 10:09:18 +0530 Subject: [PATCH 1/3] feat: Upgrade task titles with markdown rendering. Signed-off-by: krishna2323 --- .../HTMLEngineProvider/BaseHTMLEngineProvider.tsx | 6 ++++++ .../HTMLRenderers/AnchorRenderer.tsx | 4 +++- .../HTMLEngineProvider/HTMLRenderers/index.ts | 2 ++ src/components/HTMLEngineProvider/htmlEngineUtils.ts | 6 +++++- src/components/ReportActionItem/TaskView.tsx | 10 +++++++--- src/libs/ReportUtils.ts | 3 +++ src/libs/TaskUtils.ts | 4 +++- src/libs/actions/Task.ts | 7 +++++-- src/pages/tasks/TaskTitlePage.tsx | 11 +++++++++-- 9 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index d211aac7fd4c..f801eaa138de 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -32,6 +32,11 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim tagName: 'edited', contentModel: HTMLContentModel.textual, }), + 'task-title': HTMLElementModel.fromCustomModel({ + tagName: 'task-title', + contentModel: HTMLContentModel.block, + mixedUAStyles: {...styles.taskTitleMenuItem}, + }), 'alert-text': HTMLElementModel.fromCustomModel({ tagName: 'alert-text', mixedUAStyles: {...styles.formError, ...styles.mb0}, @@ -103,6 +108,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim styles.textLineThrough, styles.mutedNormalTextLabel, styles.onlyEmojisTextLineHeight, + styles.taskTitleMenuItem, ], ); /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx index 493ddec5a5d0..8329a9db98c5 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx @@ -33,6 +33,8 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) { const linkHasImage = tnode.tagName === 'a' && tnode.children.some((child) => child.tagName === 'img'); const isDeleted = HTMLEngineUtils.isDeletedNode(tnode); + const isChildOfTaskTitle = HTMLEngineUtils.isChildOfTaskTitle(tnode); + const textDecorationLineStyle = isDeleted ? styles.underlineLineThrough : {}; if (!HTMLEngineUtils.isChildOfComment(tnode)) { @@ -41,7 +43,7 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) { // TODO: We should use TextLink, but I'm leaving it as Text for now because TextLink breaks the alignment in Android. return ( Link.openLink(attrHref, environmentURL, isAttachment)} suppressHighlighting > diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts index ce24584048b0..dc717d8e3d5a 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -9,6 +9,7 @@ import MentionReportRenderer from './MentionReportRenderer'; import MentionUserRenderer from './MentionUserRenderer'; import NextStepEmailRenderer from './NextStepEmailRenderer'; import PreRenderer from './PreRenderer'; +import TaskTitleRenderer from './TaskTitleRendered'; import VideoRenderer from './VideoRenderer'; /** @@ -25,6 +26,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = { edited: EditedRenderer, pre: PreRenderer, /* eslint-disable @typescript-eslint/naming-convention */ + 'task-title': TaskTitleRenderer, 'mention-user': MentionUserRenderer, 'mention-report': MentionReportRenderer, 'mention-here': MentionHereRenderer, diff --git a/src/components/HTMLEngineProvider/htmlEngineUtils.ts b/src/components/HTMLEngineProvider/htmlEngineUtils.ts index fba467add14b..f94339820fbf 100644 --- a/src/components/HTMLEngineProvider/htmlEngineUtils.ts +++ b/src/components/HTMLEngineProvider/htmlEngineUtils.ts @@ -59,6 +59,10 @@ function isChildOfH1(tnode: TNode): boolean { return isChildOfNode(tnode, (node) => node.domNode?.name !== undefined && node.domNode.name.toLowerCase() === 'h1'); } +function isChildOfTaskTitle(tnode: TNode): boolean { + return isChildOfNode(tnode, (node) => node.domNode?.name !== undefined && node.domNode.name.toLowerCase() === 'task-title'); +} + /** * Check if the parent node has deleted style. */ @@ -67,4 +71,4 @@ function isDeletedNode(tnode: TNode): boolean { return 'textDecorationLine' in parentStyle && parentStyle.textDecorationLine === 'line-through'; } -export {computeEmbeddedMaxWidth, isChildOfComment, isCommentTag, isChildOfH1, isDeletedNode}; +export {computeEmbeddedMaxWidth, isChildOfComment, isCommentTag, isChildOfH1, isDeletedNode, isChildOfTaskTitle}; diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index 9a2906aa7d62..2fa00ec40ea3 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -11,6 +11,7 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; +import RenderHTML from '@components/RenderHTML'; import Text from '@components/Text'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; @@ -48,7 +49,7 @@ function TaskView({report, ...props}: TaskViewProps) { Task.setTaskReport(report); }, [report]); - const taskTitle = convertToLTR(report.reportName ?? ''); + const taskTitle = `${convertToLTR(report.reportName ?? '')}`; const assigneeTooltipDetails = ReportUtils.getDisplayNamesWithTooltips( OptionsListUtils.getPersonalDetailsForAccountIDs(report.managerID ? [report.managerID] : [], props.personalDetails), false, @@ -117,12 +118,15 @@ function TaskView({report, ...props}: TaskViewProps) { disabled={!canModifyTask || !canActionTask} /> - {taskTitle} - + */} + + + {isOpen && ( diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 136521e23f64..77c442d41eee 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3977,6 +3977,9 @@ function getReportName( return getIOUUnapprovedMessage(parentReportAction); } + if (isTaskReport(report)) { + return Parser.htmlToText(report?.reportName ?? ''); + } if (isChatThread(report)) { if (!isEmptyObject(parentReportAction) && ReportActionsUtils.isTransactionThread(parentReportAction)) { formattedName = getTransactionReportName(parentReportAction); diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index 11434373dafb..82bb6968a9fa 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -7,6 +7,7 @@ import type {Message} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import * as Localize from './Localize'; import Navigation from './Navigation/Navigation'; +import Parser from './Parser'; import {getReportActionHtml, getReportActionText} from './ReportActionsUtils'; import * as ReportConnection from './ReportConnection'; @@ -42,7 +43,8 @@ function getTaskTitleFromReport(taskReport: OnyxEntry, fallbackTitle = ' // We need to check for reportID, not just reportName, because when a receiver opens the task for the first time, // an optimistic report is created with the only property – reportName: 'Chat report', // and it will be displayed as the task title without checking for reportID to be present. - return taskReport?.reportID && taskReport.reportName ? taskReport.reportName : fallbackTitle; + const title = taskReport?.reportID && taskReport.reportName ? taskReport.reportName : fallbackTitle; + return Parser.htmlToText(title); } function getTaskTitle(taskReportID: string, fallbackTitle = ''): string { diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 5668d91b4999..94dc6b392c7d 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -520,6 +520,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task // Sometimes title or description is undefined, so we need to check for that, and we provide it to multiple functions const reportName = (title ?? report?.reportName)?.trim(); + const parsedTitle = ReportUtils.getParsedComment(reportName ?? ''); // Description can be unset, so we default to an empty string if so const newDescription = typeof description === 'string' ? ReportUtils.getParsedComment(description) : report.description; @@ -535,7 +536,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, value: { - reportName, + reportName: parsedTitle, description: reportDescription, pendingFields: { ...(title && {reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), @@ -556,6 +557,8 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, value: { + reportName: parsedTitle, + description: reportDescription, pendingFields: { ...(title && {reportName: null}), ...(description && {description: null}), @@ -582,7 +585,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task const parameters: EditTaskParams = { taskReportID: report.reportID, - title: reportName, + title: parsedTitle, description: reportDescription, editedTaskReportActionID: editTaskReportAction.reportActionID, }; diff --git a/src/pages/tasks/TaskTitlePage.tsx b/src/pages/tasks/TaskTitlePage.tsx index d767b5b9da3c..0ee6c1eee762 100644 --- a/src/pages/tasks/TaskTitlePage.tsx +++ b/src/pages/tasks/TaskTitlePage.tsx @@ -17,9 +17,11 @@ import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TaskDetailsNavigatorParamList} from '@libs/Navigation/types'; +import Parser from '@libs/Parser'; import * as ReportUtils from '@libs/ReportUtils'; import withReportOrNotFound from '@pages/home/report/withReportOrNotFound'; import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound'; +import variables from '@styles/variables'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -40,7 +42,7 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) if (!title) { ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, translate('newTaskPage.pleaseEnterTaskName')); - } else if (title.length > CONST.TITLE_CHARACTER_LIMIT) { + } else if (title.length > CONST.DESCRIPTION_LIMIT) { ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, translate('common.error.characterLimitExceedCounter', {length: title.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } @@ -104,7 +106,8 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) name={INPUT_IDS.TITLE} label={translate('task.title')} accessibilityLabel={translate('task.title')} - defaultValue={report?.reportName ?? ''} + // defaultValue={report?.reportName ?? ''} + defaultValue={Parser.htmlToMarkdown(report?.reportName ?? '')} ref={(element: AnimatedTextInputRef) => { if (!element) { return; @@ -114,6 +117,10 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) } inputRef.current = element; }} + autoGrowHeight + maxAutoGrowHeight={variables.textInputAutoGrowMaxHeight} + shouldSubmitForm + isMarkdownEnabled /> From 83c1f021ec850773b317abf4869e3e497c3ac7d8 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sat, 7 Dec 2024 10:10:36 +0530 Subject: [PATCH 2/3] add task title renderer. Signed-off-by: krishna2323 --- .../HTMLRenderers/TaskTitleRendered.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/TaskTitleRendered.tsx diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/TaskTitleRendered.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/TaskTitleRendered.tsx new file mode 100644 index 000000000000..526c14d6b7e7 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/TaskTitleRendered.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; +import {TNodeChildrenRenderer} from 'react-native-render-html'; +import Text from '@components/Text'; +import useThemeStyles from '@hooks/useThemeStyles'; + +function TaskTitleRenderer({tnode}: CustomRendererProps) { + const styles = useThemeStyles(); + + return ( + + + + ); +} + +TaskTitleRenderer.displayName = 'TaskTitleRenderer'; + +export default TaskTitleRenderer; From 1ef52383c193d62541f6f79597ba755a5e49a497 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 7 Feb 2025 11:12:53 +0530 Subject: [PATCH 3/3] minor fix. Signed-off-by: krishna2323 --- src/components/ReportActionItem/TaskView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index 8d1fc8684a15..54339bb36f60 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -43,7 +43,6 @@ function TaskView({report}: TaskViewProps) { setTaskReport(report); }, [report]); const taskTitle = `${convertToLTR(report.reportName ?? '')}`; - const personalDetails = usePersonalDetails(); const assigneeTooltipDetails = getDisplayNamesWithTooltips(getPersonalDetailsForAccountIDs(report?.managerID ? [report?.managerID] : [], personalDetails), false); const isOpen = isOpenTaskReport(report);