Skip to content

fix: Task - Mark down is not copied when task preview is copied to clipboard. #58572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {useMemo} from 'react';
import type {TextProps} from 'react-native';
import {HTMLContentModel, HTMLElementModel, RenderHTMLConfigProvider, TRenderEngineProvider} from 'react-native-render-html';
import type {TNode} from 'react-native-render-html';
import useThemeStyles from '@hooks/useThemeStyles';
import convertToLTR from '@libs/convertToLTR';
import FontUtils from '@styles/utils/FontUtils';
Expand Down Expand Up @@ -108,9 +109,9 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
contentModel: HTMLContentModel.block,
getMixedUAStyles: (tnode) => {
if (tnode.attributes.isemojisonly === undefined) {
return;
return HTMLEngineUtils.isChildOfTaskTitle(tnode as TNode) ? {} : styles.blockquote;
}
return styles.onlyEmojisTextLineHeight;
return HTMLEngineUtils.isChildOfTaskTitle(tnode as TNode) ? {} : {...styles.blockquote, ...styles.onlyEmojisTextLineHeight};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mananjadhav, I removed the custom blockquote renderer file after learning a better way to override styles for the task title from @bernhardoj's PR. This approach works really well for our case.

},
}),
}),
Expand All @@ -126,6 +127,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
styles.onlyEmojisText,
styles.onlyEmojisTextLineHeight,
styles.taskTitleMenuItem,
styles.blockquote,
],
);
/* eslint-enable @typescript-eslint/naming-convention */
Expand Down
11 changes: 11 additions & 0 deletions src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,20 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d
const {translate} = useLocalize();
const isLast = defaultRendererProps.renderIndex === defaultRendererProps.renderLength - 1;

const isChildOfTaskTitle = HTMLEngineUtils.isChildOfTaskTitle(defaultRendererProps.tnode);
const isInsideTaskTitle = HTMLEngineUtils.isChildOfTaskTitle(defaultRendererProps.tnode);
const fontSize = StyleUtils.getCodeFontSize(false, isInsideTaskTitle);

if (isChildOfTaskTitle) {
return (
<TDefaultRenderer
// eslint-disable-next-line react/jsx-props-no-spreading
{...defaultRendererProps}
style={styles.taskTitleMenuItem}
/>
);
}

return (
<View style={isLast ? styles.mt2 : styles.mv2}>
<ShowContextMenuContext.Consumer>
Expand Down
9 changes: 6 additions & 3 deletions src/components/ReportActionItem/TaskView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
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 StringUtils from '@libs/StringUtils';
import {isActiveTaskEditRoute} from '@libs/TaskUtils';
import {callFunctionIfActionIsAllowed} from '@userActions/Session';
import {canActionTask as canActionTaskUtil, canModifyTask as canModifyTaskUtil, clearTaskErrors, completeTask, reopenTask, setTaskReport} from '@userActions/Task';
Expand All @@ -45,8 +45,11 @@ function TaskView({report}: TaskViewProps) {
useEffect(() => {
setTaskReport(report);
}, [report]);
const titleWithoutImage = Parser.replace(Parser.htmlToMarkdown(report?.reportName ?? ''), {disabledRules: [...CONST.TASK_TITLE_DISABLED_RULES]});
const taskTitle = `<task-title>${convertToLTR(titleWithoutImage)}</task-title>`;

const taskTitleWithoutPre = StringUtils.removePreCodeBlock(report?.reportName);
const titleWithoutImage = Parser.replace(Parser.htmlToMarkdown(taskTitleWithoutPre), {disabledRules: [...CONST.TASK_TITLE_DISABLED_RULES]});
const taskTitle = `<task-title>${titleWithoutImage}</task-title>`;

const assigneeTooltipDetails = getDisplayNamesWithTooltips(getPersonalDetailsForAccountIDs(report?.managerID ? [report?.managerID] : [], personalDetails), false);

const isOpen = isOpenTaskReport(report);
Expand Down
2 changes: 1 addition & 1 deletion src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4574,7 +4574,7 @@ function getReportNameInternal({
}

if (isTaskReport(report)) {
return Parser.htmlToText(report?.reportName ?? '');
return Parser.htmlToText(report?.reportName ?? '').trim();
}

if (isChatThread(report)) {
Expand Down
9 changes: 8 additions & 1 deletion src/libs/StringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,11 @@ function removeDoubleQuotes(text = '') {
return text.replace(/"/g, '');
}

export default {sanitizeString, isEmptyString, removeInvisibleCharacters, normalizeAccents, normalizeCRLF, lineBreaksToSpaces, getFirstLine, removeDoubleQuotes};
/**
* Remove pre tag from the html
*/
function removePreCodeBlock(text = '') {
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we have tests for this method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The function is quite simple, and a test might not add much value.

return text.replace(/<pre[^>]*>|<\/pre>/g, '');
}

export default {sanitizeString, isEmptyString, removeInvisibleCharacters, normalizeAccents, normalizeCRLF, lineBreaksToSpaces, getFirstLine, removeDoubleQuotes, removePreCodeBlock};
13 changes: 7 additions & 6 deletions src/libs/TaskUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,23 @@ function getTaskReportActionMessage(action: OnyxEntry<ReportAction>): Pick<Messa
}
}

function getTaskTitleFromReport(taskReport: OnyxEntry<Report>, fallbackTitle = ''): string {
function getTaskTitleFromReport(taskReport: OnyxEntry<Report>, fallbackTitle = '', shouldReturnMarkdown = false): string {
// 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.
const title = taskReport?.reportID && taskReport.reportName ? taskReport.reportName : fallbackTitle;
return Parser.htmlToText(title);

return shouldReturnMarkdown ? Parser.htmlToMarkdown(title) : Parser.htmlToText(title).trim();
}

function getTaskTitle(taskReportID: string | undefined, fallbackTitle = ''): string {
function getTaskTitle(taskReportID: string | undefined, fallbackTitle = '', shouldReturnMarkdown = false): string {
const taskReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`];
return getTaskTitleFromReport(taskReport, fallbackTitle);
return getTaskTitleFromReport(taskReport, fallbackTitle, shouldReturnMarkdown);
}

function getTaskCreatedMessage(reportAction: OnyxEntry<ReportAction>) {
function getTaskCreatedMessage(reportAction: OnyxEntry<ReportAction>, shouldReturnMarkdown = false) {
const taskReportID = reportAction?.childReportID;
const taskTitle = getTaskTitle(taskReportID, reportAction?.childReportName);
const taskTitle = getTaskTitle(taskReportID, reportAction?.childReportName, shouldReturnMarkdown);
return taskTitle ? translateLocal('task.messages.created', {title: taskTitle}) : '';
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/home/report/ContextMenu/ContextMenuActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ const ContextMenuActions: ContextMenuAction[] = [
setClipboardMessage(displayMessage);
}
} else if (isCreatedTaskReportAction(reportAction)) {
const taskPreviewMessage = getTaskCreatedMessage(reportAction);
const taskPreviewMessage = getTaskCreatedMessage(reportAction, true);
Clipboard.setString(taskPreviewMessage);
} else if (isMemberChangeAction(reportAction)) {
const logMessage = getMemberChangeMessageFragment(reportAction, getReportName).html ?? '';
Expand Down
28 changes: 10 additions & 18 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,17 +181,6 @@ const webViewStyles = (theme: ThemeColors) =>
flexShrink: 1,
},

blockquote: {
borderLeftColor: theme.border,
borderLeftWidth: 4,
paddingLeft: 12,
marginTop: 4,
marginBottom: 4,

// Overwrite default HTML margin for blockquotes
marginLeft: 0,
},

pre: {
...baseCodeTagStyles(theme),
paddingVertical: 8,
Expand Down Expand Up @@ -276,6 +265,16 @@ const styles = (theme: ThemeColors) =>
boxShadow: variables.popoverMenuShadow,
paddingVertical: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING,
},
blockquote: {
borderLeftColor: theme.border,
borderLeftWidth: 4,
paddingLeft: 12,
marginTop: 4,
marginBottom: 4,

// Overwrite default HTML margin for blockquotes
marginLeft: 0,
},

h1: {
fontSize: variables.fontSizeLarge,
Expand Down Expand Up @@ -3487,13 +3486,6 @@ const styles = (theme: ThemeColors) =>
...spacing.ml4,
},

blockquote: {
borderLeftColor: theme.border,
borderLeftWidth: 4,
paddingLeft: 12,
marginVertical: 4,
},

noSelect: {
boxShadow: 'none',
// After https://github.com/facebook/react-native/pull/46284 RN accepts only 3 options and undefined
Expand Down