Skip to content

Commit

Permalink
Merge pull request #30955 from aswin-s/fix/emoji-with-whitespace
Browse files Browse the repository at this point in the history
feat: append whitespace after inserting emoji
  • Loading branch information
flodnv authored Jan 26, 2024
2 parents d223824 + 27f5395 commit 1439ac0
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 11 deletions.
28 changes: 27 additions & 1 deletion src/libs/ComposerUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ function insertText(text: string, selection: Selection, textToInsert: string): s
return text.slice(0, selection.start) + textToInsert + text.slice(selection.end, text.length);
}

/**
* Insert a white space at given index of text
* @param text - text that needs whitespace to be appended to
*/
function insertWhiteSpaceAtIndex(text: string, index: number) {
return `${text.slice(0, index)} ${text.slice(index)}`;
}

/**
* Check whether we can skip trigger hotkeys on some specific devices.
*/
Expand All @@ -23,4 +31,22 @@ function canSkipTriggerHotkeys(isSmallScreenWidth: boolean, isKeyboardShown: boo
return (isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen()) || isKeyboardShown;
}

export {getNumberOfLines, updateNumberOfLines, insertText, canSkipTriggerHotkeys};
/**
* Finds the length of common suffix between two texts
*/
function findCommonSuffixLength(str1: string, str2: string, cursorPosition: number) {
let commonSuffixLength = 0;
const minLength = Math.min(str1.length - cursorPosition, str2.length);

for (let i = 1; i <= minLength; i++) {
if (str1.charAt(str1.length - i) === str2.charAt(str2.length - i)) {
commonSuffixLength++;
} else {
break;
}
}

return commonSuffixLength;
}

export {getNumberOfLines, updateNumberOfLines, insertText, canSkipTriggerHotkeys, insertWhiteSpaceAtIndex, findCommonSuffixLength};
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ function ComposerWithSuggestions({
return draft;
});
const commentRef = useRef(value);
const lastTextRef = useRef(value);

const {isSmallScreenWidth} = useWindowDimensions();
const maxComposerLines = isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES;
Expand Down Expand Up @@ -189,6 +190,47 @@ function ComposerWithSuggestions({
[],
);

/**
* Find the newly added characters between the previous text and the new text based on the selection.
*
* @param {string} prevText - The previous text.
* @param {string} newText - The new text.
* @returns {object} An object containing information about the newly added characters.
* @property {number} startIndex - The start index of the newly added characters in the new text.
* @property {number} endIndex - The end index of the newly added characters in the new text.
* @property {string} diff - The newly added characters.
*/
const findNewlyAddedChars = useCallback(
(prevText, newText) => {
let startIndex = -1;
let endIndex = -1;
let currentIndex = 0;

// Find the first character mismatch with newText
while (currentIndex < newText.length && prevText.charAt(currentIndex) === newText.charAt(currentIndex) && selection.start > currentIndex) {
currentIndex++;
}

if (currentIndex < newText.length) {
startIndex = currentIndex;
const commonSuffixLength = ComposerUtils.findCommonSuffixLength(prevText, newText, selection.end);
// if text is getting pasted over find length of common suffix and subtract it from new text length
if (commonSuffixLength > 0 || selection.end - selection.start > 0) {
endIndex = newText.length - commonSuffixLength;
} else {
endIndex = currentIndex + newText.length;
}
}

return {
startIndex,
endIndex,
diff: newText.substring(startIndex, endIndex),
};
},
[selection.start, selection.end],
);

/**
* Update the value of the comment in Onyx
*
Expand All @@ -198,7 +240,13 @@ function ComposerWithSuggestions({
const updateComment = useCallback(
(commentValue, shouldDebounceSaveComment) => {
raiseIsScrollLikelyLayoutTriggered();
const {text: newComment, emojis, cursorPosition} = EmojiUtils.replaceAndExtractEmojis(commentValue, preferredSkinTone, preferredLocale);
const {startIndex, endIndex, diff} = findNewlyAddedChars(lastTextRef.current, commentValue);
const isEmojiInserted = diff.length && endIndex > startIndex && diff.trim() === diff && EmojiUtils.containsOnlyEmojis(diff);
const {
text: newComment,
emojis,
cursorPosition,
} = EmojiUtils.replaceAndExtractEmojis(isEmojiInserted ? ComposerUtils.insertWhiteSpaceAtIndex(commentValue, endIndex) : commentValue, preferredSkinTone, preferredLocale);
if (!_.isEmpty(emojis)) {
const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current);
if (!_.isEmpty(newEmojis)) {
Expand Down Expand Up @@ -255,6 +303,7 @@ function ComposerWithSuggestions({
},
[
debouncedUpdateFrequentlyUsedEmojis,
findNewlyAddedChars,
preferredLocale,
preferredSkinTone,
reportID,
Expand Down Expand Up @@ -309,17 +358,10 @@ function ComposerWithSuggestions({
/**
* Callback to add whatever text is chosen into the main input (used f.e as callback for the emoji picker)
* @param {String} text
* @param {Boolean} shouldAddTrailSpace
*/
const replaceSelectionWithText = useCallback(
(text, shouldAddTrailSpace = true) => {
const updatedText = shouldAddTrailSpace ? `${text} ` : text;
const selectionSpaceLength = shouldAddTrailSpace ? CONST.SPACE_LENGTH : 0;
updateComment(ComposerUtils.insertText(commentRef.current, selection, updatedText));
setSelection((prevSelection) => ({
start: prevSelection.start + text.length + selectionSpaceLength,
end: prevSelection.start + text.length + selectionSpaceLength,
}));
(text) => {
updateComment(ComposerUtils.insertText(commentRef.current, selection, text));
},
[selection, updateComment],
);
Expand Down Expand Up @@ -524,6 +566,10 @@ function ComposerWithSuggestions({
[blur, focus, prepareCommentAndResetComposer, replaceSelectionWithText],
);

useEffect(() => {
lastTextRef.current = value;
}, [value]);

useEffect(() => {
onValueChange(value);
}, [onValueChange, value]);
Expand Down

0 comments on commit 1439ac0

Please sign in to comment.