Render custom images in reactions (#11087)
* Add support for rendering custom emojis in reactions Signed-off-by: Sumner Evans <sumner@beeper.com> * Include custom reaction short names in tooltips Signed-off-by: Sumner Evans <sumner@beeper.com> * Use custom reaction shortcode for accessibility This uses the shortcode in the following places: * The aria-label of the reaction buttons * The alt text for the reaction image Signed-off-by: Sumner Evans <sumner@beeper.com> * Remove explicit instantiation of `customReactionName` variable and add types Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com> * Put custom reaction images behind a labs flag Signed-off-by: Sumner Evans <sumner@beeper.com> * Use UnstableValue for finding the shortcode Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> Signed-off-by: Sumner Evans <sumner@beeper.com> * Move calculation of whether to render custom reaction images up to ReactionRow Signed-off-by: Sumner Evans <sumner@beeper.com> * Make alt text more friendly when custom reaction doesn't have shortcode Signed-off-by: Sumner Evans <sumner@beeper.com> * Add test for ReactionsRowButton Signed-off-by: Sumner Evans <sumner@beeper.com> * Apply suggestions from code review Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com> * Don't use Optional Signed-off-by: Sumner Evans <sumner@beeper.com> * Fix ReactionsRowButton test Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> Signed-off-by: Sumner Evans <sumner@beeper.com> --------- Signed-off-by: Sumner Evans <sumner@beeper.com> Co-authored-by: Tulir Asokan <tulir@maunium.net> Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
d551469543
commit
a54f2ff878
8 changed files with 283 additions and 6 deletions
|
@ -18,6 +18,7 @@ import React, { SyntheticEvent } from "react";
|
|||
import classNames from "classnames";
|
||||
import { MatrixEvent, MatrixEventEvent, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { uniqBy } from "lodash";
|
||||
import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { isContentActionable } from "../../../utils/EventUtils";
|
||||
|
@ -27,10 +28,13 @@ import ReactionPicker from "../emojipicker/ReactionPicker";
|
|||
import ReactionsRowButton from "./ReactionsRowButton";
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
// The maximum number of reactions to initially show on a message.
|
||||
const MAX_ITEMS_WHEN_LIMITED = 8;
|
||||
|
||||
export const REACTION_SHORTCODE_KEY = new UnstableValue("shortcode", "com.beeper.reaction.shortcode");
|
||||
|
||||
const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||
|
||||
|
@ -169,6 +173,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
|||
if (!reactions || !isContentActionable(mxEvent)) {
|
||||
return null;
|
||||
}
|
||||
const customReactionImagesEnabled = SettingsStore.getValue("feature_render_reaction_images");
|
||||
|
||||
let items = reactions
|
||||
.getSortedAnnotationsByKey()
|
||||
|
@ -195,6 +200,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
|||
mxEvent={mxEvent}
|
||||
reactionEvents={deduplicatedEvents}
|
||||
myReactionEvent={myReactionEvent}
|
||||
customReactionImagesEnabled={customReactionImagesEnabled}
|
||||
disabled={
|
||||
!this.context.canReact ||
|
||||
(myReactionEvent && !myReactionEvent.isRedacted() && !this.context.canSelfRedact)
|
||||
|
|
|
@ -18,13 +18,15 @@ import React from "react";
|
|||
import classNames from "classnames";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
interface IProps {
|
||||
import { REACTION_SHORTCODE_KEY } from "./ReactionsRow";
|
||||
export interface IProps {
|
||||
// The event we're displaying reactions for
|
||||
mxEvent: MatrixEvent;
|
||||
// The reaction content / key / emoji
|
||||
|
@ -37,6 +39,8 @@ interface IProps {
|
|||
myReactionEvent?: MatrixEvent;
|
||||
// Whether to prevent quick-reactions by clicking on this reaction
|
||||
disabled?: boolean;
|
||||
// Whether to render custom image reactions
|
||||
customReactionImagesEnabled?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -100,27 +104,56 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
|
|||
content={content}
|
||||
reactionEvents={reactionEvents}
|
||||
visible={this.state.tooltipVisible}
|
||||
customReactionImagesEnabled={this.props.customReactionImagesEnabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const room = this.context.getRoom(mxEvent.getRoomId());
|
||||
let label: string | undefined;
|
||||
let customReactionName: string | undefined;
|
||||
if (room) {
|
||||
const senders: string[] = [];
|
||||
for (const reactionEvent of reactionEvents) {
|
||||
const member = room.getMember(reactionEvent.getSender()!);
|
||||
senders.push(member?.name || reactionEvent.getSender()!);
|
||||
customReactionName =
|
||||
(this.props.customReactionImagesEnabled &&
|
||||
REACTION_SHORTCODE_KEY.findIn(reactionEvent.getContent())) ||
|
||||
undefined;
|
||||
}
|
||||
|
||||
const reactors = formatCommaSeparatedList(senders, 6);
|
||||
if (content) {
|
||||
label = _t("%(reactors)s reacted with %(content)s", { reactors, content });
|
||||
label = _t("%(reactors)s reacted with %(content)s", {
|
||||
reactors,
|
||||
content: customReactionName || content,
|
||||
});
|
||||
} else {
|
||||
label = reactors;
|
||||
}
|
||||
}
|
||||
|
||||
let reactionContent = (
|
||||
<span className="mx_ReactionsRowButton_content" aria-hidden="true">
|
||||
{content}
|
||||
</span>
|
||||
);
|
||||
if (this.props.customReactionImagesEnabled && content.startsWith("mxc://")) {
|
||||
const imageSrc = mediaFromMxc(content).srcHttp;
|
||||
if (imageSrc) {
|
||||
reactionContent = (
|
||||
<img
|
||||
className="mx_ReactionsRowButton_content"
|
||||
alt={customReactionName || _t("Custom reaction")}
|
||||
src={imageSrc}
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AccessibleButton
|
||||
className={classes}
|
||||
|
@ -130,9 +163,7 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
|
|||
onMouseOver={this.onMouseOver}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
<span className="mx_ReactionsRowButton_content" aria-hidden="true">
|
||||
{content}
|
||||
</span>
|
||||
{reactionContent}
|
||||
<span className="mx_ReactionsRowButton_count" aria-hidden="true">
|
||||
{count}
|
||||
</span>
|
||||
|
|
|
@ -22,6 +22,7 @@ import { _t } from "../../../languageHandler";
|
|||
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils";
|
||||
import Tooltip from "../elements/Tooltip";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { REACTION_SHORTCODE_KEY } from "./ReactionsRow";
|
||||
interface IProps {
|
||||
// The event we're displaying reactions for
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -30,6 +31,8 @@ interface IProps {
|
|||
// A list of Matrix reaction events for this key
|
||||
reactionEvents: MatrixEvent[];
|
||||
visible: boolean;
|
||||
// Whether to render custom image reactions
|
||||
customReactionImagesEnabled?: boolean;
|
||||
}
|
||||
|
||||
export default class ReactionsRowButtonTooltip extends React.PureComponent<IProps> {
|
||||
|
@ -43,12 +46,17 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<IProp
|
|||
let tooltipLabel: JSX.Element | undefined;
|
||||
if (room) {
|
||||
const senders: string[] = [];
|
||||
let customReactionName: string | undefined;
|
||||
for (const reactionEvent of reactionEvents) {
|
||||
const member = room.getMember(reactionEvent.getSender()!);
|
||||
const name = member?.name ?? reactionEvent.getSender()!;
|
||||
senders.push(name);
|
||||
customReactionName =
|
||||
(this.props.customReactionImagesEnabled &&
|
||||
REACTION_SHORTCODE_KEY.findIn(reactionEvent.getContent())) ||
|
||||
undefined;
|
||||
}
|
||||
const shortName = unicodeToShortcode(content);
|
||||
const shortName = unicodeToShortcode(content) || customReactionName;
|
||||
tooltipLabel = (
|
||||
<div>
|
||||
{_t(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue