Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #6784 from SimonBrandner/fix/end-of-line-emoji
Browse files Browse the repository at this point in the history
Replace plain text emoji at the end of a line
  • Loading branch information
turt2live committed Sep 14, 2021
2 parents fdb004b + f4ca073 commit 7b9dc09
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 18 deletions.
30 changes: 20 additions & 10 deletions src/components/views/rooms/BasicMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ import { AutocompleteAction, getKeyBindingsManager, MessageComposerAction } from
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 REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s|:^$');
export const REGEX_EMOTICON = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')$');

const IS_MAC = navigator.platform.indexOf("Mac") !== -1;

Expand Down Expand Up @@ -161,7 +162,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
}
}

private replaceEmoticon = (caretPosition: DocumentPosition): number => {
public replaceEmoticon(caretPosition: DocumentPosition, regex: RegExp): number {
const { model } = this.props;
const range = model.startRange(caretPosition);
// expand range max 8 characters backwards from caretPosition,
Expand All @@ -170,28 +171,33 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
range.expandBackwardsWhile((index, offset) => {
const part = model.parts[index];
n -= 1;
return n >= 0 && (part.type === Type.Plain || part.type === Type.PillCandidate);
return n >= 0 && [Type.Plain, Type.PillCandidate, Type.Newline].includes(part.type);
});
const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text);
const emoticonMatch = regex.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] === " ";
const moveStart = emoticonMatch[0][0] === " " ? 1 : 0;
const moveEnd = emoticonMatch[0].length - emoticonMatch.length - moveStart;

// 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));
range.moveStartForwards(emoticonMatch.index + moveStart);
// and move end backwards so that we don't replace the trailing space/newline
range.moveEndBackwards(moveEnd);

// 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 + " ")]);
return range.replace([partCreator.plain(data.unicode)]);
}
}
};
}

private updateEditorState = (selection: Caret, inputType?: string, diff?: IDiff): void => {
renderModel(this.editorRef.current, this.props.model);
Expand Down Expand Up @@ -607,8 +613,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
};

private configureEmoticonAutoReplace = (): void => {
const shouldReplace = SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji');
this.props.model.setTransformCallback(shouldReplace ? this.replaceEmoticon : null);
this.props.model.setTransformCallback(this.transform);
};

private configureShouldShowPillAvatar = (): void => {
Expand All @@ -621,6 +626,11 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.setState({ surroundWith });
};

private transform = (documentPosition: DocumentPosition): void => {
const shouldReplace = SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji');
if (shouldReplace) this.replaceEmoticon(documentPosition, REGEX_EMOTICON_WHITESPACE);
};

componentWillUnmount() {
document.removeEventListener("selectionchange", this.onSelectionChange);
this.editorRef.current.removeEventListener("input", this.onInput, true);
Expand Down
23 changes: 16 additions & 7 deletions src/components/views/rooms/SendMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ import {
textSerialize,
unescapeMessage,
} from '../../../editor/serialize';
import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer";
import { CommandPartCreator, Part, PartCreator, SerializedPart, Type } from '../../../editor/parts';
import BasicMessageComposer from "./BasicMessageComposer";
import ReplyThread from "../elements/ReplyThread";
import { findEditableEvent } from '../../../utils/EventUtils';
import SendHistoryManager from "../../../SendHistoryManager";
Expand Down Expand Up @@ -347,15 +347,24 @@ export default class SendMessageComposer extends React.Component<IProps> {
}

public async sendMessage(): Promise<void> {
if (this.model.isEmpty) {
const model = this.model;

if (model.isEmpty) {
return;
}

// Replace emoticon at the end of the message
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
const caret = this.editorRef.current?.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON);
}

const replyToEvent = this.props.replyToEvent;
let shouldSend = true;
let content;

if (!containsEmote(this.model) && this.isSlashCommand()) {
if (!containsEmote(model) && this.isSlashCommand()) {
const [cmd, args, commandText] = this.getSlashCommand();
if (cmd) {
if (cmd.category === CommandCategories.messages) {
Expand Down Expand Up @@ -400,7 +409,7 @@ export default class SendMessageComposer extends React.Component<IProps> {
}
}

if (isQuickReaction(this.model)) {
if (isQuickReaction(model)) {
shouldSend = false;
this.sendQuickReaction();
}
Expand All @@ -410,7 +419,7 @@ export default class SendMessageComposer extends React.Component<IProps> {
const { roomId } = this.props.room;
if (!content) {
content = createMessageContent(
this.model,
model,
replyToEvent,
this.props.replyInThread,
this.props.permalinkCreator,
Expand Down Expand Up @@ -446,9 +455,9 @@ export default class SendMessageComposer extends React.Component<IProps> {
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content);
}

this.sendHistoryManager.save(this.model, replyToEvent);
this.sendHistoryManager.save(model, replyToEvent);
// clear composer
this.model.reset([]);
model.reset([]);
this.editorRef.current?.clearUndoHistory();
this.editorRef.current?.focus();
this.clearStoredEditorState();
Expand Down
9 changes: 8 additions & 1 deletion src/editor/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,20 @@ export default class Range {
this._end = bIsLarger ? positionB : positionA;
}

public moveStart(delta: number): void {
public moveStartForwards(delta: number): void {
this._start = this._start.forwardsWhile(this.model, () => {
delta -= 1;
return delta >= 0;
});
}

public moveEndBackwards(delta: number): void {
this._end = this._end.backwardsWhile(this.model, () => {
delta -= 1;
return delta >= 0;
});
}

public trim(): void {
this._start = this._start.forwardsWhile(this.model, whitespacePredicate);
this._end = this._end.backwardsWhile(this.model, whitespacePredicate);
Expand Down

0 comments on commit 7b9dc09

Please sign in to comment.