···211211 const isNotSelection = view.state.selection.empty
212212 if (isNotSelection) {
213213 const cursorPosition = view.state.selection.$anchor.pos
214214- const textBefore = view.state.doc.textBetween(0, cursorPosition)
214214+ const textBefore = view.state.doc.textBetween(
215215+ 0,
216216+ cursorPosition,
217217+ // important - use \n as a block separator, otherwise
218218+ // all the lines get mushed together -sfn
219219+ '\n',
220220+ )
215221 const graphemes = new Graphemer().splitGraphemes(textBefore)
216222217223 if (graphemes.length > 0) {
218224 const lastGrapheme = graphemes[graphemes.length - 1]
219219- const deleteFrom = cursorPosition - lastGrapheme.length
220220- editor?.commands.deleteRange({
221221- from: deleteFrom,
222222- to: cursorPosition,
223223- })
224224- return true
225225+ // deleteRange doesn't work on newlines, because tiptap
226226+ // treats them as separate 'blocks' and we're using \n
227227+ // as a stand-in. bail out if the last grapheme is a newline
228228+ // to let the default behavior handle it -sfn
229229+ if (lastGrapheme !== '\n') {
230230+ // otherwise, delete the last grapheme using deleteRange,
231231+ // so that emojis are deleted as a whole
232232+ const deleteFrom = cursorPosition - lastGrapheme.length
233233+ editor?.commands.deleteRange({
234234+ from: deleteFrom,
235235+ to: cursorPosition,
236236+ })
237237+ return true
238238+ }
225239 }
226240 }
227241 }