diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index 2f574351c94..cdf08232de2 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -102,7 +102,7 @@ function mightContainEmoji(str: string): boolean {
*/
export function unicodeToShortcode(char: string): string {
const shortcodes = getEmojiFromUnicode(char)?.shortcodes;
- return shortcodes?.length ? `:${shortcodes[0]}:` : '';
+ return shortcodes?.length ? `:${shortcodes[0]}:` : char;
}
export function processHtmlForSending(html: string): string {
diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx
index 2d62ce6ce7d..a894dbdd282 100644
--- a/src/components/views/messages/MImageBody.tsx
+++ b/src/components/views/messages/MImageBody.tsx
@@ -139,7 +139,7 @@ export default class MImageBody extends React.Component {
}
};
- private isGif = (): boolean => {
+ protected isGif = (): boolean => {
const content = this.props.mxEvent.getContent();
return content.info?.mimetype === "image/gif";
};
diff --git a/src/components/views/messages/ReactionImage.tsx b/src/components/views/messages/ReactionImage.tsx
new file mode 100644
index 00000000000..48a74578fb4
--- /dev/null
+++ b/src/components/views/messages/ReactionImage.tsx
@@ -0,0 +1,64 @@
+/*
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+
+import MImageBody from './MImageBody';
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { BLURHASH_FIELD } from "../../../ContentMessages";
+import { IMediaEventContent } from '../../../customisations/models/IMediaEventContent';
+import SettingsStore from '../../../settings/SettingsStore';
+
+const FORCED_IMAGE_HEIGHT = 20;
+
+@replaceableComponent("views.messages.ReactionImage")
+export default class ReactionImage extends MImageBody {
+ public onClick = (ev: React.MouseEvent): void => {
+ ev.preventDefault();
+ };
+
+ protected wrapImage(contentUrl: string, children: JSX.Element): JSX.Element {
+ return children;
+ }
+
+ protected getPlaceholder(width: number, height: number): JSX.Element {
+ if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) return super.getPlaceholder(width, height);
+ return
;
+ }
+
+ // Tooltip to show on mouse over
+ protected getTooltip(): JSX.Element {
+ return null;
+ }
+
+ // Don't show "Download this_file.png ..."
+ protected getFileBody() {
+ return null;
+ }
+
+ render() {
+ const contentUrl = this.getContentUrl();
+ const content = this.props.mxEvent.getContent();
+ let thumbUrl;
+ if (this.props.forExport || (this.isGif() && SettingsStore.getValue("autoplayGifs"))) {
+ thumbUrl = contentUrl;
+ } else {
+ thumbUrl = this.getThumbUrl();
+ }
+ const thumbnail = this.messageContent(contentUrl, thumbUrl, content, FORCED_IMAGE_HEIGHT);
+ return thumbnail;
+ }
+}
diff --git a/src/components/views/messages/ReactionsRowButton.tsx b/src/components/views/messages/ReactionsRowButton.tsx
index 242428ed367..3c65a4b1800 100644
--- a/src/components/views/messages/ReactionsRowButton.tsx
+++ b/src/components/views/messages/ReactionsRowButton.tsx
@@ -25,6 +25,8 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
import AccessibleButton from "../elements/AccessibleButton";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import ReactionImage from "./ReactionImage";
+import { MediaEventHelper } from "../../../utils/MediaEventHelper";
interface IProps {
// The event we're displaying reactions for
@@ -49,12 +51,26 @@ interface IState {
@replaceableComponent("views.messages.ReactionsRowButton")
export default class ReactionsRowButton extends React.PureComponent {
static contextType = MatrixClientContext;
+ private mediaHelper: MediaEventHelper;
+ private mediaEligible: boolean;
+ private mediaEvent: MatrixEvent;
state = {
tooltipRendered: false,
tooltipVisible: false,
};
+ public constructor(props: IProps, context: React.ContextType) {
+ super(props);
+ const mediaEvents = [...props.reactionEvents].filter(event => MediaEventHelper.isEligible(event));
+ if (mediaEvents.length > 0) {
+ this.mediaEligible = true;
+ // assume that reactors aren't sending different contents with the same key
+ this.mediaEvent = mediaEvents[0];
+ this.mediaHelper = new MediaEventHelper(this.mediaEvent);
+ }
+ }
+
onClick = () => {
const { mxEvent, myReactionEvent, content } = this.props;
if (myReactionEvent) {
@@ -64,6 +80,7 @@ export default class ReactionsRowButton extends React.PureComponent{ content }>;
+ if (this.mediaEligible) {
+ actualContent = { }}
+ onMessageAllowed={undefined}
+ permalinkCreator={undefined}
+ mediaEventHelper={this.mediaHelper} />;
+ }
+
const classes = classNames({
mx_ReactionsRowButton: true,
mx_ReactionsRowButton_selected: !!myReactionEvent,
@@ -133,7 +162,7 @@ export default class ReactionsRowButton extends React.PureComponent
- { content }
+ { actualContent }
{ count }
diff --git a/src/utils/MediaEventHelper.ts b/src/utils/MediaEventHelper.ts
index 24680668d94..bc1f35c52ef 100644
--- a/src/utils/MediaEventHelper.ts
+++ b/src/utils/MediaEventHelper.ts
@@ -105,7 +105,7 @@ export class MediaEventHelper implements IDestroyable {
if (!event) return false;
if (event.isRedacted()) return false;
if (event.getType() === EventType.Sticker) return true;
- if (event.getType() !== EventType.RoomMessage) return false;
+ if (event.getType() !== EventType.RoomMessage && event.getType() !== EventType.Reaction) return false;
const content = event.getContent();
const mediaMsgTypes: string[] = [