diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 48f2e2a39b4..1a8d5c65e7b 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -18,7 +18,6 @@ import classNames from 'classnames'; import React, { createRef, ClipboardEvent } from 'react'; import { Room } from 'matrix-js-sdk/src/models/room'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; -import EMOTICON_REGEX from 'emojibase-regex/emoticon'; import EditorModel from '../../../editor/model'; import HistoryManager from '../../../editor/history'; @@ -37,7 +36,6 @@ import { renderModel } from '../../../editor/render'; import TypingStore from "../../../stores/TypingStore"; import SettingsStore from "../../../settings/SettingsStore"; import { Key } from "../../../Keyboard"; -import { EMOTICON_TO_EMOJI } from "../../../emoji"; import { CommandCategories, CommandMap, parseCommandString } from "../../../SlashCommands"; import Range from "../../../editor/range"; import MessageComposerFormatBar, { Formatting } from "./MessageComposerFormatBar"; @@ -49,9 +47,6 @@ import { ICompletion } from "../../../autocomplete/Autocompleter"; import { AutocompleteAction, getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -// matches emoticons which follow the start of a line or whitespace -const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$'); - const IS_MAC = navigator.platform.indexOf("Mac") !== -1; const SURROUND_WITH_CHARACTERS = ["\"", "_", "`", "'", "*", "~", "$"]; @@ -123,7 +118,6 @@ export default class BasicMessageEditor extends React.Component private lastCaret: DocumentOffset; private lastSelection: ReturnType; - private readonly emoticonSettingHandle: string; private readonly shouldShowPillAvatarSettingHandle: string; private readonly surroundWithHandle: string; private readonly historyManager = new HistoryManager(); @@ -136,9 +130,6 @@ export default class BasicMessageEditor extends React.Component showVisualBell: false, }; - this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null, - this.configureEmoticonAutoReplace); - this.configureEmoticonAutoReplace(); this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting("Pill.shouldShowPillAvatar", null, this.configureShouldShowPillAvatar); this.surroundWithHandle = SettingsStore.watchSetting("MessageComposerInput.surroundWith", null, @@ -161,38 +152,6 @@ export default class BasicMessageEditor extends React.Component } } - private replaceEmoticon = (caretPosition: DocumentPosition): number => { - const { model } = this.props; - const range = model.startRange(caretPosition); - // expand range max 8 characters backwards from caretPosition, - // as a space to look for an emoticon - let n = 8; - range.expandBackwardsWhile((index, offset) => { - const part = model.parts[index]; - n -= 1; - return n >= 0 && (part.type === Type.Plain || part.type === Type.PillCandidate); - }); - const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text); - if (emoticonMatch) { - const query = emoticonMatch[1].replace("-", ""); - // try both exact match and lower-case, this means that xd won't match xD but :P will match :p - const data = EMOTICON_TO_EMOJI.get(query) || EMOTICON_TO_EMOJI.get(query.toLowerCase()); - - if (data) { - const { partCreator } = model; - const hasPrecedingSpace = emoticonMatch[0][0] === " "; - // we need the range to only comprise of the emoticon - // because we'll replace the whole range with an emoji, - // so move the start forward to the start of the emoticon. - // Take + 1 because index is reported without the possible preceding space. - range.moveStart(emoticonMatch.index + (hasPrecedingSpace ? 1 : 0)); - // this returns the amount of added/removed characters during the replace - // so the caret position can be adjusted. - return range.replace([partCreator.plain(data.unicode + " ")]); - } - } - }; - private updateEditorState = (selection: Caret, inputType?: string, diff?: IDiff): void => { renderModel(this.editorRef.current, this.props.model); if (selection) { // set the caret/selection @@ -606,11 +565,6 @@ export default class BasicMessageEditor extends React.Component this.setState({ completionIndex }); }; - private configureEmoticonAutoReplace = (): void => { - const shouldReplace = SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji'); - this.props.model.setTransformCallback(shouldReplace ? this.replaceEmoticon : null); - }; - private configureShouldShowPillAvatar = (): void => { const showPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar"); this.setState({ showPillAvatar }); @@ -626,7 +580,6 @@ export default class BasicMessageEditor extends React.Component this.editorRef.current.removeEventListener("input", this.onInput, true); this.editorRef.current.removeEventListener("compositionstart", this.onCompositionStart, true); this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true); - SettingsStore.unwatchSetting(this.emoticonSettingHandle); SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle); SettingsStore.unwatchSetting(this.surroundWithHandle); } diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 38a73cc945c..266777133ef 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -23,9 +23,14 @@ import SettingsStore from '../settings/SettingsStore'; import SdkConfig from '../SdkConfig'; import cheerio from 'cheerio'; import { Type } from './parts'; +import EMOTICON_REGEX from 'emojibase-regex/emoticon'; +import { EMOTICON_TO_EMOJI } from "../emoji"; + +// matches emoticons which follow the start of a line or whitespace +const REGEX_EMOTICON_WHITESPACE = new RegExp("(?<=^|\\s)("+ EMOTICON_REGEX.source +")(?=\\s|$)", "g"); export function mdSerialize(model: EditorModel): string { - return model.parts.reduce((html, part) => { + const md = model.parts.reduce((html, part) => { switch (part.type) { case Type.Newline: return html + "\n"; @@ -44,6 +49,8 @@ export function mdSerialize(model: EditorModel): string { `[${part.text.replace(/[[\\\]]/g, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`; } }, ""); + + return replaceEmoticonsIfNeeded(md); } export function htmlSerializeIfNeeded(model: EditorModel, { forceHTML = false } = {}): string { @@ -158,7 +165,7 @@ export function htmlSerializeIfNeeded(model: EditorModel, { forceHTML = false } } export function textSerialize(model: EditorModel): string { - return model.parts.reduce((text, part) => { + const text = model.parts.reduce((text, part) => { switch (part.type) { case Type.Newline: return text + "\n"; @@ -175,6 +182,8 @@ export function textSerialize(model: EditorModel): string { return text + `${part.text}`; } }, ""); + + return replaceEmoticonsIfNeeded(text); } export function containsEmote(model: EditorModel): boolean { @@ -217,3 +226,12 @@ export function unescapeMessage(model: EditorModel): EditorModel { } return model; } + +export function replaceEmoticonsIfNeeded(str: string): string { + if (!SettingsStore.getValue("MessageComposerInput.autoReplaceEmoji")) return str; + + return str.replace(REGEX_EMOTICON_WHITESPACE, (substring: string) => { + // Try both exact match and lower-case, this means that xd won't match xD but :P will match :p + return (EMOTICON_TO_EMOJI.get(substring) || EMOTICON_TO_EMOJI.get(substring.toLowerCase()))?.unicode; + }); +}