Skip to content
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

[Mentions v2] Support mentions in editing comments #40565

Merged
merged 21 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f7275e0
introduce OnyxAwareParser
robertKozik Apr 19, 2024
959af6b
Merge branch 'refs/heads/main' into mention-editing
war-in Apr 19, 2024
7b3fe65
`parser.htmlTo` -> `parseHtmlTo`
war-in Apr 19, 2024
baf2a8c
add fallback when displayName does not exist
war-in Apr 19, 2024
63a7098
bring back removed trim
war-in Apr 19, 2024
94e4adf
Merge branch 'refs/heads/main' into mention-editing
war-in Apr 22, 2024
de7ddaf
Merge branch 'refs/heads/main' into mention-editing
war-in Apr 23, 2024
8daba9d
update expensify-common
war-in Apr 23, 2024
d2bb72f
Merge branch 'refs/heads/main' into mention-editing
war-in Apr 26, 2024
1f08080
Merge branch 'refs/heads/main' into mention-editing
war-in Apr 29, 2024
46a1f6e
fix failing typecheck
war-in Apr 29, 2024
1828050
Merge branch 'refs/heads/main' into mention-editing
war-in May 13, 2024
2dc7236
Merge branch 'refs/heads/main' into mention-editing
war-in May 13, 2024
98c0acf
remove leftovers
war-in May 13, 2024
dafa5a4
replace htmlToText in `OptionRowLHN`
war-in May 14, 2024
6bac08a
Merge branch 'refs/heads/main' into mention-editing
war-in May 24, 2024
01aa522
Merge remote-tracking branch 'refs/remotes/origin/main' into mention-…
war-in Jun 3, 2024
a8fbfa6
use `personalDetails.login` as account name
war-in Jun 4, 2024
5132b5c
Merge branch 'refs/heads/main' into mention-editing
war-in Jun 7, 2024
f0b59c0
allow passing custom extras to OnyxAwareParser
war-in Jun 7, 2024
8846545
Merge branch 'refs/heads/main' into mention-editing
war-in Jun 14, 2024
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
6 changes: 2 additions & 4 deletions src/components/LHNOptionsList/OptionRowLHN.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {useFocusEffect} from '@react-navigation/native';
import {ExpensiMark} from 'expensify-common';
import React, {useCallback, useRef, useState} from 'react';
import type {GestureResponderEvent, ViewStyle} from 'react-native';
import {StyleSheet, View} from 'react-native';
Expand All @@ -20,6 +19,7 @@ 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 Performance from '@libs/Performance';
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
Expand All @@ -29,8 +29,6 @@ import CONST from '@src/CONST';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {OptionRowLHNProps} from './types';

const parser = new ExpensiMark();

function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment}: OptionRowLHNProps) {
const theme = useTheme();
const styles = useThemeStyles();
Expand Down Expand Up @@ -239,7 +237,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
numberOfLines={1}
accessibilityLabel={translate('accessibilityHints.lastChatMessagePreview')}
>
{parser.htmlToText(optionItem.alternateText)}
{parseHtmlToText(optionItem.alternateText)}
</Text>
) : null}
</View>
Expand Down
7 changes: 3 additions & 4 deletions src/hooks/useCopySelectionHelper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ExpensiMark} from 'expensify-common';
import {useEffect} from 'react';
import Clipboard from '@libs/Clipboard';
import KeyboardShortcut from '@libs/KeyboardShortcut';
import {parseHtmlToMarkdown, parseHtmlToText} from '@libs/OnyxAwareParser';
import SelectionScraper from '@libs/SelectionScraper';
import CONST from '@src/CONST';

Expand All @@ -10,12 +10,11 @@ function copySelectionToClipboard() {
if (!selection) {
return;
}
const parser = new ExpensiMark();
if (!Clipboard.canSetHtml()) {
Clipboard.setString(parser.htmlToMarkdown(selection));
Clipboard.setString(parseHtmlToMarkdown(selection));
return;
}
Clipboard.setHtml(selection, parser.htmlToText(selection));
Clipboard.setHtml(selection, parseHtmlToText(selection));
}

export default function useCopySelectionHelper() {
Expand Down
5 changes: 2 additions & 3 deletions src/hooks/useHtmlPaste/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useNavigation} from '@react-navigation/native';
import {ExpensiMark} from 'expensify-common';
import {useCallback, useEffect} from 'react';
import {parseHtmlToMarkdown} from '@libs/OnyxAwareParser';
import type UseHtmlPaste from './types';

const insertByCommand = (text: string) => {
Expand Down Expand Up @@ -62,8 +62,7 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi
*/
const handlePastedHTML = useCallback(
(html: string) => {
const parser = new ExpensiMark();
paste(parser.htmlToMarkdown(html));
paste(parseHtmlToMarkdown(html));
},
[paste],
);
Expand Down
42 changes: 42 additions & 0 deletions src/libs/OnyxAwareParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {ExpensiMark} from 'expensify-common';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';

const parser = new ExpensiMark();

const reportIDToNameMap: Record<string, string> = {};
const accountIDToNameMap: Record<string, string> = {};

Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
callback: (report) => {
if (!report) {
return;
}

reportIDToNameMap[report.reportID] = report.reportName ?? report.displayName ?? report.reportID;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if we should fallback to id, but we did it before so should be fine

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I think it makes sense to fallback to reportID. And if we would prefer reportID to empty string "", then we can use logical OR || and add comment to disable lint check

// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing

Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity, do you know when displayName is filled?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tbh I don't know 😅 It's useful for the users but I'm not sure about the rooms

},
});

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);
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't make sense to use accountID as fallback.
Later, it caused #45259.
We fixed it by setting it as empty string as fallback to show "Hidden"

});
},
});

function parseHtmlToMarkdown(html: string, reportIDToName?: Record<string, string>, accountIDToName?: Record<string, string>): string {
return parser.htmlToMarkdown(html, {reportIDToName: reportIDToName ?? reportIDToNameMap, accountIDToName: accountIDToName ?? accountIDToNameMap});
}

function parseHtmlToText(html: string, reportIDToName?: Record<string, string>, accountIDToName?: Record<string, string>): string {
return parser.htmlToText(html, {reportIDToName: reportIDToName ?? reportIDToNameMap, accountIDToName: accountIDToName ?? accountIDToNameMap});
}

export {parseHtmlToMarkdown, parseHtmlToText};
15 changes: 6 additions & 9 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,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 Permissions from './Permissions';
import * as PersonalDetailsUtils from './PersonalDetailsUtils';
import * as PhoneNumber from './PhoneNumber';
Expand Down Expand Up @@ -3186,8 +3187,7 @@ function parseReportActionHtmlToText(reportAction: OnyxEntry<ReportAction>, repo
const logins = PersonalDetailsUtils.getLoginsByAccountIDs(accountIDs);
accountIDs.forEach((id, index) => (accountIDToName[id] = logins[index]));

const parser = new ExpensiMark();
const textMessage = Str.removeSMSDomain(parser.htmlToText(html, {reportIDToName, accountIDToName}));
const textMessage = Str.removeSMSDomain(parseHtmlToText(html, reportIDToName, accountIDToName));
parsedReportActionMessageCache[key] = textMessage;

return textMessage;
Expand Down Expand Up @@ -3557,17 +3557,15 @@ function getReportDescriptionText(report: Report): string {
return '';
}

const parser = new ExpensiMark();
return parser.htmlToText(report.description);
return parseHtmlToText(report.description);
}

function getPolicyDescriptionText(policy: OnyxEntry<Policy>): string {
if (!policy?.description) {
return '';
}

const parser = new ExpensiMark();
return parser.htmlToText(policy.description);
return parseHtmlToText(policy.description);
}

function buildOptimisticAddCommentReportAction(
Expand All @@ -3578,7 +3576,6 @@ function buildOptimisticAddCommentReportAction(
shouldEscapeText?: boolean,
reportID?: string,
): OptimisticReportAction {
const parser = new ExpensiMark();
const commentText = getParsedComment(text ?? '', {shouldEscapeText, reportID});
const isAttachmentOnly = file && !text;
const isTextOnly = text && !file;
Expand All @@ -3590,10 +3587,10 @@ function buildOptimisticAddCommentReportAction(
textForNewComment = CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML;
} else if (isTextOnly) {
htmlForNewComment = commentText;
textForNewComment = parser.htmlToText(htmlForNewComment);
textForNewComment = parseHtmlToText(htmlForNewComment);
} else {
htmlForNewComment = `${commentText}\n${CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML}`;
textForNewComment = `${parser.htmlToText(commentText)}\n${CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML}`;
textForNewComment = `${parseHtmlToText(commentText)}\n${CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML}`;
}

const isAttachment = !text && file !== undefined;
Expand Down
5 changes: 3 additions & 2 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import Log from '@libs/Log';
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 * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils';
Expand Down Expand Up @@ -1482,15 +1483,15 @@ function editReportComment(reportID: string, originalReportAction: OnyxEntry<Rep
// https://github.com/Expensify/App/issues/9090
// https://github.com/Expensify/App/issues/13221
const originalCommentHTML = originalReportAction.message?.[0]?.html;
const originalCommentMarkdown = parser.htmlToMarkdown(originalCommentHTML ?? '').trim();
const originalCommentMarkdown = parseHtmlToMarkdown(originalCommentHTML ?? '').trim();

// Skip the Edit if draft is not changed
if (originalCommentMarkdown === textForNewComment) {
return;
}

const htmlForNewComment = handleUserDeletedLinksInHtml(textForNewComment, originalCommentMarkdown);
const reportComment = parser.htmlToText(htmlForNewComment);
const reportComment = parseHtmlToText(htmlForNewComment);

// 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 and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!!
Expand Down
8 changes: 4 additions & 4 deletions src/pages/PrivateNotes/PrivateNotesEditPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useFocusEffect} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import {ExpensiMark, Str} from 'expensify-common';
import {Str} from 'expensify-common';
import lodashDebounce from 'lodash/debounce';
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {Keyboard} from 'react-native';
Expand All @@ -19,6 +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 * as ReportUtils from '@libs/ReportUtils';
import updateMultilineInputRange from '@libs/updateMultilineInputRange';
import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound';
Expand Down Expand Up @@ -50,9 +51,8 @@ function PrivateNotesEditPage({route, personalDetailsList, report, session}: Pri
const {translate} = useLocalize();

// We need to edit the note in markdown format, but display it in HTML format
const parser = new ExpensiMark();
const [privateNote, setPrivateNote] = useState(
() => ReportActions.getDraftPrivateNote(report.reportID).trim() || parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim(),
() => ReportActions.getDraftPrivateNote(report.reportID).trim() || parseHtmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim(),
);

/**
Expand Down Expand Up @@ -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(), parser.htmlToMarkdown(originalNote).trim());
editedNote = ReportActions.handleUserDeletedLinksInHtml(privateNote.trim(), parseHtmlToMarkdown(originalNote).trim());
ReportActions.updatePrivateNotes(report.reportID, Number(route.params.accountID), editedNote);
}

Expand Down
5 changes: 2 additions & 3 deletions src/pages/RoomDescriptionPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {useFocusEffect} from '@react-navigation/native';
import {ExpensiMark} from 'expensify-common';
import React, {useCallback, useRef, useState} from 'react';
import {View} from 'react-native';
import type {OnyxCollection} from 'react-native-onyx';
Expand All @@ -13,6 +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 * as ReportUtils from '@libs/ReportUtils';
import updateMultilineInputRange from '@libs/updateMultilineInputRange';
import variables from '@styles/variables';
Expand All @@ -32,8 +32,7 @@ type RoomDescriptionPageProps = {

function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) {
const styles = useThemeStyles();
const parser = new ExpensiMark();
const [description, setDescription] = useState(() => parser.htmlToMarkdown(report?.description ?? ''));
const [description, setDescription] = useState(() => parseHtmlToMarkdown(report?.description ?? ''));
const reportDescriptionInputRef = useRef<BaseTextInputRef | null>(null);
const focusTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const {translate} = useLocalize();
Expand Down
11 changes: 5 additions & 6 deletions src/pages/home/report/ContextMenu/ContextMenuActions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ExpensiMark, Str} from 'expensify-common';
import {Str} from 'expensify-common';
import type {MutableRefObject} from 'react';
import React from 'react';
import {InteractionManager} from 'react-native';
Expand All @@ -18,6 +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 ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
Expand All @@ -40,13 +41,12 @@ function getActionHtml(reportAction: OnyxEntry<ReportAction>): string {

/** Sets the HTML string to Clipboard */
function setClipboardMessage(content: string) {
const parser = new ExpensiMark();
if (!Clipboard.canSetHtml()) {
Clipboard.setString(parser.htmlToMarkdown(content));
Clipboard.setString(parseHtmlToMarkdown(content));
} else {
const anchorRegex = CONST.REGEX_LINK_IN_ANCHOR;
const isAnchorTag = anchorRegex.test(content);
const plainText = isAnchorTag ? parser.htmlToMarkdown(content) : parser.htmlToText(content);
const plainText = isAnchorTag ? parseHtmlToMarkdown(content) : parseHtmlToText(content);
Clipboard.setHtml(content, plainText);
}
}
Expand Down Expand Up @@ -238,8 +238,7 @@ const ContextMenuActions: ContextMenuAction[] = [
}
const editAction = () => {
if (!draftMessage) {
const parser = new ExpensiMark();
Report.saveReportActionDraft(reportID, reportAction, parser.htmlToMarkdown(getActionHtml(reportAction)));
Report.saveReportActionDraft(reportID, reportAction, parseHtmlToMarkdown(getActionHtml(reportAction)));
} else {
Report.deleteReportActionDraft(reportID, reportAction);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {useIsFocused, useNavigation} from '@react-navigation/native';
import {ExpensiMark} from 'expensify-common';
import lodashDebounce from 'lodash/debounce';
import type {ForwardedRef, MutableRefObject, RefAttributes, RefObject} from 'react';
import React, {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
Expand Down Expand Up @@ -35,6 +34,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 ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
Expand Down Expand Up @@ -542,8 +542,7 @@ function ComposerWithSuggestions(
) {
event.preventDefault();
if (lastReportAction) {
const parser = new ExpensiMark();
Report.saveReportActionDraft(reportID, lastReportAction, parser.htmlToMarkdown(lastReportAction.message?.at(-1)?.html ?? ''));
Report.saveReportActionDraft(reportID, lastReportAction, parseHtmlToMarkdown(lastReportAction.message?.at(-1)?.html ?? ''));
}
}
},
Expand Down
5 changes: 2 additions & 3 deletions src/pages/home/report/ReportActionItemMessageEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {ExpensiMark} from 'expensify-common';
import lodashDebounce from 'lodash/debounce';
import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react';
Expand Down Expand Up @@ -27,6 +26,7 @@ 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 ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
Expand Down Expand Up @@ -105,8 +105,7 @@ function ReportActionItemMessageEdit(
const isCommentPendingSaved = useRef(false);

useEffect(() => {
const parser = new ExpensiMark();
const originalMessage = parser.htmlToMarkdown(action.message?.[0]?.html ?? '');
const originalMessage = parseHtmlToMarkdown(action.message?.[0]?.html ?? '');
if (ReportActionsUtils.isDeletedAction(action) || !!(action.message && draftMessage === originalMessage) || !!(prevDraftMessage === draftMessage || isCommentPendingSaved.current)) {
return;
}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/tasks/NewTaskDescriptionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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 updateMultilineInputRange from '@libs/updateMultilineInputRange';
import variables from '@styles/variables';
import * as TaskActions from '@userActions/Task';
Expand Down Expand Up @@ -78,7 +79,7 @@ function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) {
<View style={styles.mb5}>
<InputWrapperWithRef
InputComponent={TextInput}
defaultValue={parser.htmlToMarkdown(parser.replace(task?.description ?? ''))}
defaultValue={parseHtmlToMarkdown(parser.replace(task?.description ?? ''))}
inputID={INPUT_IDS.TASK_DESCRIPTION}
label={translate('newTaskPage.descriptionOptional')}
accessibilityLabel={translate('newTaskPage.descriptionOptional')}
Expand Down
Loading
Loading