From 568767c93df919b79f7bfc358d4c230af9e421dd Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Thu, 12 Oct 2023 17:16:50 +0530 Subject: [PATCH 1/7] RTL Text gets renderd properly --- src/components/Composer/index.js | 2 +- src/libs/convertToLTR/index.ts | 10 +++++++++- .../ReportActionCompose/ComposerWithSuggestions.js | 5 +++-- src/styles/styles.js | 4 ++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index ad7a84cc1828..97a592a546ab 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -457,7 +457,7 @@ function Composer({ placeholderTextColor={themeColors.placeholderText} ref={(el) => (textInput.current = el)} selection={selection} - style={inputStyleMemo} + style={(Browser.isMobileSafari() || Browser.isSafari()) ? [inputStyleMemo, styles.rtlTextRenderForSafari] : [inputStyleMemo]} value={value} forwardedRef={forwardedRef} defaultValue={defaultValue} diff --git a/src/libs/convertToLTR/index.ts b/src/libs/convertToLTR/index.ts index 58d8be93836e..1742f4bf2135 100644 --- a/src/libs/convertToLTR/index.ts +++ b/src/libs/convertToLTR/index.ts @@ -1,5 +1,13 @@ +import CONST from '../../CONST'; import ConvertToLTR from './types'; -const convertToLTR: ConvertToLTR = (text) => text; +const convertToLTR: ConvertToLTR = (text) => { + // Check if the text already starts with the LTR marker (if so, return as is). + if (text.startsWith(CONST.UNICODE.LTR)) { + return text; + } + // // Add the LTR marker to the beginning of the text. + return `${CONST.UNICODE.LTR}${text}`; +}; export default convertToLTR; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index faa710d2cd6b..043cda5d7562 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -1,5 +1,5 @@ import React, {useEffect, useCallback, useState, useRef, useMemo, useImperativeHandle} from 'react'; -import {View, NativeModules, findNodeHandle} from 'react-native'; +import {View, NativeModules, findNodeHandle, Platform} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import lodashGet from 'lodash/get'; @@ -33,6 +33,7 @@ import withKeyboardState from '../../../../components/withKeyboardState'; import {propTypes, defaultProps} from './composerWithSuggestionsProps'; import focusWithDelay from '../../../../libs/focusWithDelay'; import useDebounce from '../../../../hooks/useDebounce'; +import convertToLTR from '../../../../libs/convertToLTR'; const {RNTextInputReset} = NativeModules; @@ -212,7 +213,7 @@ function ComposerWithSuggestions({ } emojisPresentBefore.current = emojis; setIsCommentEmpty(!!newComment.match(/^(\s)*$/)); - setValue(newComment); + setValue(Platform.OS === 'android' ?newComment : convertToLTR(newComment)); if (commentValue !== newComment) { // Ensure emoji suggestions are hidden even when the selection is not changed (so calculateEmojiSuggestion would not be called). if (suggestionsRef.current) { diff --git a/src/styles/styles.js b/src/styles/styles.js index 8fa81cd98b21..1954205c2b59 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -195,6 +195,10 @@ const styles = (theme) => ({ alignItems: 'center', }, + rtlTextRenderForSafari: { + textAlign:"left", + }, + emojiSuggestionsEmoji: { fontSize: variables.fontSizeMedium, width: 51, From d13cfb8f19496b08da088c6b5d3cfd217a1ebb9b Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Fri, 13 Oct 2023 21:39:18 +0530 Subject: [PATCH 2/7] RTL text gets rendered properly for safari --- src/styles/styles.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/styles.js b/src/styles/styles.js index 1954205c2b59..94bbecd611b8 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -197,6 +197,7 @@ const styles = (theme) => ({ rtlTextRenderForSafari: { textAlign:"left", + ...writingDirection.ltr }, emojiSuggestionsEmoji: { From 669410f83ca39ba4c369a13d2dd9c2e840a0cf4a Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Fri, 13 Oct 2023 22:55:41 +0530 Subject: [PATCH 3/7] RTL text gets rendered properly for safari --- src/styles/styles.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/styles/styles.ts b/src/styles/styles.ts index ba3c4d888858..ec33be2f5c41 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -244,6 +244,11 @@ const styles = (theme: ThemeDefault) => alignItems: 'center', }, + rtlTextRenderForSafari: { + textAlign: 'left', + ...writingDirection.ltr, + }, + emojiSuggestionsEmoji: { fontSize: variables.fontSizeMedium, width: 51, From acd395eca58924bcacfc144848ef1900b60dabda Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Fri, 20 Oct 2023 00:47:39 +0530 Subject: [PATCH 4/7] Suggested changes applied --- src/components/Composer/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index 97a592a546ab..f845134acc51 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -445,6 +445,7 @@ function Composer({ StyleSheet.flatten([style, {outline: 'none'}]), StyleUtils.getComposeTextAreaPadding(numberOfLines, isComposerFullSize), + Browser.isMobileSafari() || Browser.isSafari() ? styles.rtlTextRenderForSafari : {}, ], [style, maxLines, numberOfLines, isComposerFullSize], ); @@ -457,7 +458,7 @@ function Composer({ placeholderTextColor={themeColors.placeholderText} ref={(el) => (textInput.current = el)} selection={selection} - style={(Browser.isMobileSafari() || Browser.isSafari()) ? [inputStyleMemo, styles.rtlTextRenderForSafari] : [inputStyleMemo]} + style={inputStyleMemo} value={value} forwardedRef={forwardedRef} defaultValue={defaultValue} From 1dd55f164afdd774b36031d472b5c5916b5a4ef0 Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Sat, 21 Oct 2023 19:28:18 +0530 Subject: [PATCH 5/7] Separate module convertToLTRForComposer created --- src/libs/convertToLTR/index.android.ts | 10 ++++++ src/libs/convertToLTR/index.ts | 10 +----- .../convertToLTRForComposer/index.android.ts | 8 +++++ src/libs/convertToLTRForComposer/index.ts | 34 +++++++++++++++++++ src/libs/convertToLTRForComposer/types.ts | 3 ++ .../ComposerWithSuggestions.js | 20 +++++------ 6 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 src/libs/convertToLTR/index.android.ts create mode 100644 src/libs/convertToLTRForComposer/index.android.ts create mode 100644 src/libs/convertToLTRForComposer/index.ts create mode 100644 src/libs/convertToLTRForComposer/types.ts diff --git a/src/libs/convertToLTR/index.android.ts b/src/libs/convertToLTR/index.android.ts new file mode 100644 index 000000000000..3a4755b1710f --- /dev/null +++ b/src/libs/convertToLTR/index.android.ts @@ -0,0 +1,10 @@ +import CONST from '../../CONST'; +import ConvertToLTR from './types'; + +/** + * Android only - convert RTL text to a LTR text using Unicode controls. + * https://www.w3.org/International/questions/qa-bidi-unicode-controls + */ +const convertToLTR: ConvertToLTR = (text) => `${CONST.UNICODE.LTR}${text}`; + +export default convertToLTR; diff --git a/src/libs/convertToLTR/index.ts b/src/libs/convertToLTR/index.ts index 5cdd39acac17..58d8be93836e 100644 --- a/src/libs/convertToLTR/index.ts +++ b/src/libs/convertToLTR/index.ts @@ -1,13 +1,5 @@ -import CONST from '../../CONST'; import ConvertToLTR from './types'; -const convertToLTR: ConvertToLTR = (text) => { - // Check if the text already starts with the LTR marker (if so, return as is). - if (text.startsWith(CONST.UNICODE.LTR)) { - return text; - } +const convertToLTR: ConvertToLTR = (text) => text; - // Add the LTR marker to the beginning of the text. - return `${CONST.UNICODE.LTR}${text}`; -}; export default convertToLTR; diff --git a/src/libs/convertToLTRForComposer/index.android.ts b/src/libs/convertToLTRForComposer/index.android.ts new file mode 100644 index 000000000000..09e7f2e5cd87 --- /dev/null +++ b/src/libs/convertToLTRForComposer/index.android.ts @@ -0,0 +1,8 @@ +import ConvertToLTRForComposer from './types'; + +/** + * Android only - Do not convert RTL text to a LTR text for input box using Unicode controls. + * Android does not properly support bidirectional text for mixed content for input box + */ +const convertToLTRForComposer: ConvertToLTRForComposer = (text) => text; +export default convertToLTRForComposer; diff --git a/src/libs/convertToLTRForComposer/index.ts b/src/libs/convertToLTRForComposer/index.ts new file mode 100644 index 000000000000..4b253f3a2ecb --- /dev/null +++ b/src/libs/convertToLTRForComposer/index.ts @@ -0,0 +1,34 @@ +import CONST from '../../CONST'; +import ConvertToLTRForComposer from './types'; + +function hasLTRorRTLCharacters(text: string): boolean { + // Regular expressions to match LTR and RTL character ranges. + // eslint-disable-next-line + const ltrPattern = /[\u0001-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/; + const rtlPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/; + + return ltrPattern.test(text) || rtlPattern.test(text); +} + +// Converts a given text to ensure it starts with the LTR (Left-to-Right) marker. +const convertToLTRForComposer: ConvertToLTRForComposer = (text) => { + // Ensure the text contains LTR or RTL characters to avoid an unwanted special character at the beginning, even after a backspace deletion. + if (!hasLTRorRTLCharacters(text)) { + return ''; + } + + // Check if the text contains only spaces. If it does, we do not concatenate it with CONST.UNICODE.LTR, + // as doing so would alter the normal behavior of the input box. + if (/^\s*$/.test(text)) { + return text; + } + + // Check if the text already starts with the LTR marker (if so, return as is). + if (text.startsWith(CONST.UNICODE.LTR)) { + return text; + } + + // Add the LTR marker to the beginning of the text. + return `${CONST.UNICODE.LTR}${text}`; +}; +export default convertToLTRForComposer; diff --git a/src/libs/convertToLTRForComposer/types.ts b/src/libs/convertToLTRForComposer/types.ts new file mode 100644 index 000000000000..c6edeaaba446 --- /dev/null +++ b/src/libs/convertToLTRForComposer/types.ts @@ -0,0 +1,3 @@ +type ConvertToLTRForComposer = (text: string) => string; + +export default ConvertToLTRForComposer; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index bdf15e6c81ac..fbed91449ae7 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -34,9 +34,9 @@ import withKeyboardState from '../../../../components/withKeyboardState'; import {propTypes, defaultProps} from './composerWithSuggestionsProps'; import focusWithDelay from '../../../../libs/focusWithDelay'; import useDebounce from '../../../../hooks/useDebounce'; -import convertToLTR from '../../../../libs/convertToLTR'; import updateMultilineInputRange from '../../../../libs/UpdateMultilineInputRange'; import * as InputFocus from '../../../../libs/actions/InputFocus'; +import convertToLTRForComposer from '../../../../libs/convertToLTRForComposer'; const {RNTextInputReset} = NativeModules; @@ -213,7 +213,6 @@ function ComposerWithSuggestions({ (commentValue, shouldDebounceSaveComment) => { raiseIsScrollLikelyLayoutTriggered(); const {text: newComment, emojis} = EmojiUtils.replaceAndExtractEmojis(commentValue, preferredSkinTone, preferredLocale); - if (!_.isEmpty(emojis)) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (!_.isEmpty(newEmojis)) { @@ -225,9 +224,10 @@ function ComposerWithSuggestions({ debouncedUpdateFrequentlyUsedEmojis(); } } + const newCommentConverted = convertToLTRForComposer(newComment); emojisPresentBefore.current = emojis; - setIsCommentEmpty(!!newComment.match(/^(\s)*$/)); - setValue(Platform.OS === 'android' ? newComment : convertToLTR(newComment)); + setIsCommentEmpty(!!newCommentConverted.match(/^(\s)*$/)); + setValue(newCommentConverted); if (commentValue !== newComment) { const remainder = ComposerUtils.getCommonSuffixLength(commentValue, newComment); setSelection({ @@ -237,22 +237,22 @@ function ComposerWithSuggestions({ } // Indicate that draft has been created. - if (commentRef.current.length === 0 && newComment.length !== 0) { + if (commentRef.current.length === 0 && newCommentConverted.length !== 0) { Report.setReportWithDraft(reportID, true); } // The draft has been deleted. - if (newComment.length === 0) { + if (newCommentConverted.length === 0) { Report.setReportWithDraft(reportID, false); } - commentRef.current = newComment; + commentRef.current = newCommentConverted; if (shouldDebounceSaveComment) { - debouncedSaveReportComment(reportID, newComment); + debouncedSaveReportComment(reportID, newCommentConverted); } else { - Report.saveReportComment(reportID, newComment || ''); + Report.saveReportComment(reportID, newCommentConverted || ''); } - if (newComment) { + if (newCommentConverted) { debouncedBroadcastUserIsTyping(reportID); } }, From 389f982fdfed813ac1328e7d07cff0872ab4515d Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Sat, 21 Oct 2023 19:30:15 +0530 Subject: [PATCH 6/7] Platform import removed --- .../home/report/ReportActionCompose/ComposerWithSuggestions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index fbed91449ae7..a1872bf0b714 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -1,5 +1,5 @@ import React, {useEffect, useCallback, useState, useRef, useMemo, useImperativeHandle} from 'react'; -import {View, NativeModules, findNodeHandle, Platform} from 'react-native'; +import {View, NativeModules, findNodeHandle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import lodashGet from 'lodash/get'; From ec7860f932378b9773afcb3ed81a99a2511d26e9 Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Mon, 23 Oct 2023 20:02:37 +0530 Subject: [PATCH 7/7] rule name specified --- src/libs/convertToLTRForComposer/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/convertToLTRForComposer/index.ts b/src/libs/convertToLTRForComposer/index.ts index 4b253f3a2ecb..eb14bfa8c11a 100644 --- a/src/libs/convertToLTRForComposer/index.ts +++ b/src/libs/convertToLTRForComposer/index.ts @@ -3,7 +3,7 @@ import ConvertToLTRForComposer from './types'; function hasLTRorRTLCharacters(text: string): boolean { // Regular expressions to match LTR and RTL character ranges. - // eslint-disable-next-line + // eslint-disable-next-line no-control-regex const ltrPattern = /[\u0001-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/; const rtlPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/;