Skip to content

Commit ed46baa

Browse files
committed
emotes show up in messages
1 parent e22e316 commit ed46baa

File tree

10 files changed

+1594
-182
lines changed

10 files changed

+1594
-182
lines changed

res/css/views/rooms/_EventTile.pcss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,9 @@ $left-gutter: 64px;
646646
font-size: inherit !important;
647647
}
648648
}
649-
649+
.mx_Emote{
650+
height: 30px;
651+
}
650652
.mx_EventTile_e2eIcon {
651653
position: relative;
652654
width: 14px;

src/HtmlUtils.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ interface IOpts {
396396
returnString?: boolean;
397397
forComposerQuote?: boolean;
398398
ref?: React.Ref<HTMLSpanElement>;
399+
emotes?: Dictionary<string>;
399400
}
400401

401402
export interface IOptsReturnNode extends IOpts {
@@ -468,8 +469,8 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
468469
if (opts.forComposerQuote) {
469470
sanitizeParams = composerSanitizeHtmlParams;
470471
}
471-
472472
let strippedBody: string;
473+
473474
let safeBody: string; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
474475

475476
try {
@@ -486,20 +487,19 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
486487
if (opts.stripReplyFallback && formattedBody) formattedBody = stripHTMLReply(formattedBody);
487488
strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody;
488489
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody : plainBody);
489-
490490
const highlighter = safeHighlights?.length
491491
? new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink)
492492
: null;
493-
494493
if (isFormattedBody) {
495494
if (highlighter) {
496495
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
497496
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
498497
// are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted
499498
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
500499
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
500+
501501
sanitizeParams.textFilter = function(safeText) {
502-
return highlighter.applyHighlights(safeText, safeHighlights).join('');
502+
return highlighter.applyHighlights(safeText, safeHighlights).join('').replace(/:[\w+-]+:/g, m => opts.emotes[m] ? opts.emotes[m] : m);
503503
};
504504
}
505505

@@ -524,7 +524,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
524524
// @ts-ignore - `e` can be an Element, not just a Node
525525
displayMode: e.name == 'div',
526526
output: "htmlAndMathml",
527-
});
527+
})
528528
});
529529
safeBody = phtml.html();
530530
}
@@ -538,11 +538,12 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
538538
delete sanitizeParams.textFilter;
539539
}
540540

541-
const contentBody = safeBody ?? strippedBody;
541+
let contentBody = safeBody ?? strippedBody;
542+
contentBody=contentBody.replace(/:[\w+-]+:/g, m => opts.emotes[m] ? opts.emotes[m] : m);
542543
if (opts.returnString) {
543544
return contentBody;
544545
}
545-
546+
546547
let emojiBody = false;
547548
if (!opts.disableBigEmoji && bodyHasEmoji) {
548549
let contentBodyTrimmed = contentBody !== undefined ? contentBody.trim() : '';
@@ -574,12 +575,17 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
574575
'mx_EventTile_bigEmoji': emojiBody,
575576
'markdown-body': isHtmlMessage && !emojiBody,
576577
});
577-
578+
let tmp=strippedBody?.replace(/:[\w+-]+:/g, m => opts.emotes[m] ? opts.emotes[m] : m);
579+
if(tmp!=strippedBody){
580+
safeBody=tmp;
581+
}
582+
583+
578584
let emojiBodyElements: JSX.Element[];
579585
if (!safeBody && bodyHasEmoji) {
580586
emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[];
581587
}
582-
588+
583589
return safeBody ?
584590
<span
585591
key="body"

src/components/structures/MessagePanel.tsx

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ import React, { createRef, KeyboardEvent, ReactNode, TransitionEvent } from 'rea
1818
import ReactDOM from 'react-dom';
1919
import classNames from 'classnames';
2020
import { Room } from 'matrix-js-sdk/src/models/room';
21-
import { EventType } from 'matrix-js-sdk/src/@types/event';
21+
import { EventType, MsgType } from 'matrix-js-sdk/src/@types/event';
2222
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
2323
import { Relations } from "matrix-js-sdk/src/models/relations";
2424
import { logger } from 'matrix-js-sdk/src/logger';
2525
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
2626
import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
2727
import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
2828

29+
2930
import shouldHideEvent from '../../shouldHideEvent';
3031
import { wantsDateSeparator } from '../../DateUtils';
3132
import { MatrixClientPeg } from '../../MatrixClientPeg';
@@ -57,6 +58,9 @@ import { IReadReceiptInfo } from "../views/rooms/ReadReceiptMarker";
5758
import { haveRendererForEvent } from "../../events/EventTileFactory";
5859
import { editorRoomKey } from "../../Editing";
5960
import { hasThreadSummary } from "../../utils/EventUtils";
61+
import { mediaFromMxc } from "../../customisations/Media";
62+
import { mockStateEventImplementation } from '../../../test/test-utils';
63+
6064

6165
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
6266
const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
@@ -195,6 +199,7 @@ interface IState {
195199
ghostReadMarkers: string[];
196200
showTypingNotifications: boolean;
197201
hideSender: boolean;
202+
emotes:Dictionary<string>;
198203
}
199204

200205
interface IReadReceiptForUser {
@@ -272,6 +277,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
272277
ghostReadMarkers: [],
273278
showTypingNotifications: SettingsStore.getValue("showTypingNotifications"),
274279
hideSender: this.shouldHideSender(),
280+
emotes: {},
275281
};
276282

277283
// Cache these settings on mount since Settings is expensive to query,
@@ -282,11 +288,20 @@ export default class MessagePanel extends React.Component<IProps, IState> {
282288

283289
this.showTypingNotificationsWatcherRef =
284290
SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange);
291+
292+
let emotesEvent=this.props.room.currentState.getStateEvents("m.room.emotes", "");
293+
let rawEmotes = emotesEvent ? (emotesEvent.getContent() || {}) : {};
294+
let finalEmotes = {};
295+
for (let key in rawEmotes) {
296+
this.state.emotes[":"+key+":"] = "<img src="+mediaFromMxc(rawEmotes[key]).srcHttp+"/>";
297+
}
298+
285299
}
286300

287301
componentDidMount() {
288302
this.calculateRoomMembersCount();
289303
this.props.room?.currentState.on(RoomStateEvent.Update, this.calculateRoomMembersCount);
304+
//this.props.room?.currentState.on(RoomStateEvent.Update, this.getEmotes);
290305
this.isMounted = true;
291306
}
292307

@@ -608,7 +623,6 @@ export default class MessagePanel extends React.Component<IProps, IState> {
608623
// we also need to figure out which is the last event we show which isn't
609624
// a local echo, to manage the read-marker.
610625
let lastShownEvent;
611-
612626
let lastShownNonLocalEchoIndex = -1;
613627
for (i = this.props.events.length-1; i >= 0; i--) {
614628
const mxEv = this.props.events[i];
@@ -768,37 +782,77 @@ export default class MessagePanel extends React.Component<IProps, IState> {
768782
isLastSuccessful = isLastSuccessful && mxEv.getSender() === MatrixClientPeg.get().getUserId();
769783

770784
const callEventGrouper = this.props.callEventGroupers.get(mxEv.getContent().call_id);
785+
771786
// use txnId as key if available so that we don't remount during sending
787+
if(mxEv.getType()==="m.room.message"){
788+
let messageText = mxEv.getContent().body;
789+
//console.log(messageText);
790+
let editedMessageText = messageText.replace(/:[\w+-]+:/, m => this.state.emotes[m] ? this.state.emotes[m] : m);
791+
let m=[{body:messageText,
792+
mimetype:"text/plain",
793+
},
794+
{
795+
body:editedMessageText,
796+
mimetype:"text/html",
797+
}
798+
];
799+
// if(mxEv.clearEvent){
800+
// console.log("clearevent",mxEv.getRoomId());
801+
// mxEv.clearEvent.content={
802+
// "format":"org.matrix.custom.html",
803+
// "formatted_body":editedMessageText,
804+
// "body":messageText,
805+
// "msgtype":"m.text",
806+
// "org.matrix.msc1767.message":m
807+
// }
808+
// }
809+
// else{
810+
// console.log("no clearevent",mxEv);
811+
// mxEv.content={
812+
// "format":"org.matrix.custom.html",
813+
// "formatted_body":editedMessageText,
814+
// "body":messageText,
815+
// "msgtype":"m.text",
816+
// "org.matrix.msc1767.message":m
817+
// }
818+
// }
819+
820+
//mxEv.getContent().formatted_body = messageText;
821+
//mxEv.clearEvent.content["org.matrix.msc1767.text"] = "";
822+
//mxEv.getContent().formatted_body = (<span>hi</span>);
823+
//mxEv.getContent().format = "org.matrix.custom.html";
824+
825+
}
772826
ret.push(
773827
<EventTile
774-
key={mxEv.getTxnId() || eventId}
775-
as="li"
776-
ref={this.collectEventTile.bind(this, eventId)}
777-
alwaysShowTimestamps={this.props.alwaysShowTimestamps}
778-
mxEvent={mxEv}
779-
continuation={continuation}
780-
isRedacted={mxEv.isRedacted()}
781-
replacingEventId={mxEv.replacingEventId()}
782-
editState={isEditing && this.props.editState}
783-
onHeightChanged={this.onHeightChanged}
784-
readReceipts={readReceipts}
785-
readReceiptMap={this.readReceiptMap}
786-
showUrlPreview={this.props.showUrlPreview}
787-
checkUnmounting={this.isUnmounting}
788-
eventSendStatus={mxEv.getAssociatedStatus()}
789-
isTwelveHour={this.props.isTwelveHour}
790-
permalinkCreator={this.props.permalinkCreator}
791-
last={last}
792-
lastInSection={lastInSection}
793-
lastSuccessful={isLastSuccessful}
794-
isSelectedEvent={highlight}
795-
getRelationsForEvent={this.props.getRelationsForEvent}
796-
showReactions={this.props.showReactions}
797-
layout={this.props.layout}
798-
showReadReceipts={this.props.showReadReceipts}
799-
callEventGrouper={callEventGrouper}
800-
hideSender={this.state.hideSender}
801-
/>,
828+
key={mxEv.getTxnId() || eventId}
829+
as="li"
830+
ref={this.collectEventTile.bind(this, eventId)}
831+
alwaysShowTimestamps={this.props.alwaysShowTimestamps}
832+
mxEvent={mxEv}
833+
continuation={continuation}
834+
isRedacted={mxEv.isRedacted()}
835+
replacingEventId={mxEv.replacingEventId()}
836+
editState={isEditing && this.props.editState}
837+
onHeightChanged={this.onHeightChanged}
838+
readReceipts={readReceipts}
839+
readReceiptMap={this.readReceiptMap}
840+
showUrlPreview={this.props.showUrlPreview}
841+
checkUnmounting={this.isUnmounting}
842+
eventSendStatus={mxEv.getAssociatedStatus()}
843+
isTwelveHour={this.props.isTwelveHour}
844+
permalinkCreator={this.props.permalinkCreator}
845+
last={last}
846+
lastInSection={lastInSection}
847+
lastSuccessful={isLastSuccessful}
848+
isSelectedEvent={highlight}
849+
getRelationsForEvent={this.props.getRelationsForEvent}
850+
showReactions={this.props.showReactions}
851+
layout={this.props.layout}
852+
showReadReceipts={this.props.showReadReceipts}
853+
callEventGrouper={callEventGrouper}
854+
hideSender={this.state.hideSender}
855+
/>
802856
);
803857

804858
return ret;

src/components/views/dialogs/RoomSettingsDialog.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,7 @@ import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab
2626
import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab";
2727
import NotificationSettingsTab from "../settings/tabs/room/NotificationSettingsTab";
2828
import BridgeSettingsTab from "../settings/tabs/room/BridgeSettingsTab";
29-
<<<<<<< HEAD
3029
import EmoteSettingsTab from "../settings/tabs/room/EmoteSettingsTab";
31-
=======
32-
>>>>>>> 7eaed1a3f870eac582c66f0fc2949dc5663bad79
3330
import { MatrixClientPeg } from "../../../MatrixClientPeg";
3431
import dis from "../../../dispatcher/dispatcher";
3532
import SettingsStore from "../../../settings/SettingsStore";
@@ -42,10 +39,7 @@ export const ROOM_SECURITY_TAB = "ROOM_SECURITY_TAB";
4239
export const ROOM_ROLES_TAB = "ROOM_ROLES_TAB";
4340
export const ROOM_NOTIFICATIONS_TAB = "ROOM_NOTIFICATIONS_TAB";
4441
export const ROOM_BRIDGES_TAB = "ROOM_BRIDGES_TAB";
45-
<<<<<<< HEAD
4642
export const ROOM_EMOTES_TAB = "ROOM_EMOTES_TAB";
47-
=======
48-
>>>>>>> 7eaed1a3f870eac582c66f0fc2949dc5663bad79
4943
export const ROOM_ADVANCED_TAB = "ROOM_ADVANCED_TAB";
5044

5145
interface IProps {
@@ -128,17 +122,13 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
128122
<NotificationSettingsTab roomId={this.props.roomId} closeSettingsFn={() => this.props.onFinished(true)} />,
129123
"RoomSettingsNotifications",
130124
));
131-
<<<<<<< HEAD
132125
tabs.push(new Tab(
133126
ROOM_NOTIFICATIONS_TAB,
134127
_td("Emotes"),
135128
"mx_RoomSettingsDialog_emotesIcon",
136129
<EmoteSettingsTab roomId={this.props.roomId} />,
137130
"RoomSettingsNotifications",
138131
));
139-
=======
140-
141-
>>>>>>> 7eaed1a3f870eac582c66f0fc2949dc5663bad79
142132
if (SettingsStore.getValue("feature_bridge_state")) {
143133
tabs.push(new Tab(
144134
ROOM_BRIDGES_TAB,

src/components/views/messages/TextualBody.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ import RoomContext from "../../../contexts/RoomContext";
4848
import AccessibleButton from '../elements/AccessibleButton';
4949
import { options as linkifyOpts } from "../../../linkify-matrix";
5050
import { getParentEventId } from '../../../utils/Reply';
51-
51+
import { MatrixClientPeg } from "../../../MatrixClientPeg";
52+
import { mediaFromMxc } from "../../../customisations/Media";
5253
const MAX_HIGHLIGHT_LENGTH = 4096;
5354

5455
interface IState {
@@ -562,16 +563,23 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
562563
const content = mxEvent.getContent();
563564
let isNotice = false;
564565
let isEmote = false;
565-
566566
// only strip reply if this is the original replying event, edits thereafter do not have the fallback
567567
const stripReply = !mxEvent.replacingEvent() && !!getParentEventId(mxEvent);
568568
let body: ReactNode;
569+
const client = MatrixClientPeg.get();
570+
const room = client.getRoom(mxEvent.getRoomId());
571+
let emotesEvent=room.currentState.getStateEvents("m.room.emotes", "");
572+
let rawEmotes = emotesEvent ? (emotesEvent.getContent() || {}) : {};
573+
let finalEmotes = {};
574+
for (let key in rawEmotes) {
575+
finalEmotes[":"+key+":"] = "<img class='mx_Emote' src="+mediaFromMxc(rawEmotes[key]).srcHttp+"/>";
576+
}
569577
if (SettingsStore.isEnabled("feature_extensible_events")) {
570578
const extev = this.props.mxEvent.unstableExtensibleEvent as MessageEvent;
571579
if (extev?.isEquivalentTo(M_MESSAGE)) {
572580
isEmote = isEventLike(extev.wireFormat, LegacyMsgType.Emote);
573581
isNotice = isEventLike(extev.wireFormat, LegacyMsgType.Notice);
574-
body = HtmlUtils.bodyToHtml({
582+
body = (HtmlUtils.bodyToHtml({
575583
body: extev.text,
576584
format: extev.html ? "org.matrix.custom.html" : undefined,
577585
formatted_body: extev.html,
@@ -583,21 +591,25 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
583591
stripReplyFallback: stripReply,
584592
ref: this.contentRef,
585593
returnString: false,
586-
});
594+
emotes: finalEmotes,
595+
}));
587596
}
588597
}
589598
if (!body) {
590599
isEmote = content.msgtype === MsgType.Emote;
591600
isNotice = content.msgtype === MsgType.Notice;
592-
body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
601+
body = (HtmlUtils.bodyToHtml(content, this.props.highlights, {
593602
disableBigEmoji: isEmote
594603
|| !SettingsStore.getValue<boolean>('TextualBody.enableBigEmoji'),
595604
// Part of Replies fallback support
596605
stripReplyFallback: stripReply,
597606
ref: this.contentRef,
598607
returnString: false,
599-
});
608+
})as any).replace(/:[\w+-]+:/, m => finalEmotes[m] ? finalEmotes[m] : m);
609+
600610
}
611+
//console.log(body);
612+
//body.replace(/:[\w+-]+:/, m => finalEmotes[m] ? finalEmotes[m] : m)
601613
if (this.props.replacingEventId) {
602614
body = <>
603615
{ body }
@@ -634,6 +646,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
634646
/>;
635647
}
636648

649+
//console.log(body.props.children);
650+
//.replace(/:[\w+-]+:/, m => finalEmotes[m] ? finalEmotes[m] : m)
637651
if (isEmote) {
638652
return (
639653
<div className="mx_MEmoteBody mx_EventTile_content"
@@ -662,6 +676,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
662676
</div>
663677
);
664678
}
679+
//console.log(body)
665680
return (
666681
<div className="mx_MTextBody mx_EventTile_content" onClick={this.onBodyLinkClick}>
667682
{ body }

0 commit comments

Comments
 (0)