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