diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 6a8595c128..c1efce8c5e 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -211,17 +211,31 @@ export function TextInput({ const isNotSelection = view.state.selection.empty if (isNotSelection) { const cursorPosition = view.state.selection.$anchor.pos - const textBefore = view.state.doc.textBetween(0, cursorPosition) + const textBefore = view.state.doc.textBetween( + 0, + cursorPosition, + // important - use \n as a block separator, otherwise + // all the lines get mushed together -sfn + '\n', + ) const graphemes = new Graphemer().splitGraphemes(textBefore) if (graphemes.length > 0) { const lastGrapheme = graphemes[graphemes.length - 1] - const deleteFrom = cursorPosition - lastGrapheme.length - editor?.commands.deleteRange({ - from: deleteFrom, - to: cursorPosition, - }) - return true + // deleteRange doesn't work on newlines, because tiptap + // treats them as separate 'blocks' and we're using \n + // as a stand-in. bail out if the last grapheme is a newline + // to let the default behavior handle it -sfn + if (lastGrapheme !== '\n') { + // otherwise, delete the last grapheme using deleteRange, + // so that emojis are deleted as a whole + const deleteFrom = cursorPosition - lastGrapheme.length + editor?.commands.deleteRange({ + from: deleteFrom, + to: cursorPosition, + }) + return true + } } } }