Fix getRelationsForEvent under TypeScript strict mode (#9558)

* Fix getRelationsForEvent tsc strictness

* Use shared type for GetRelationsForEvent

* Fix lint

* Add alternative type

* getRelationsForEvent is not required

* Relations are optional

* Reactions are optional

* We expect relations in these tests

* Add more protection if the eventID is not defined

* Allow null too

* Better test typing

* User ID is not necessary unless something is selected

* It's okay to [].includes(null)

* Null is as good as undefined here

* Null or undefined is good here

* We have some expectations for the tests

* The room and user can be undefined too

* Protec

* Reactions are optional

* Try match signatures

* Null or undefined

* More null or undefined

* Protec

* Fix typo (wrong variable)

* Remove optional params

See https://github.com/matrix-org/matrix-react-sdk/pull/9558#discussion_r1017515913

* Fix up last maaaaybe relevant lint

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Eric Eastwood 2022-11-21 21:54:24 -06:00 committed by GitHub
parent 2cd1fad10b
commit 2393510a7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 181 additions and 126 deletions

View file

@ -20,7 +20,6 @@ import classNames from 'classnames';
import { Room } from 'matrix-js-sdk/src/models/room'; import { Room } from 'matrix-js-sdk/src/models/room';
import { EventType } from 'matrix-js-sdk/src/@types/event'; import { EventType } from 'matrix-js-sdk/src/@types/event';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { Relations } from "matrix-js-sdk/src/models/relations";
import { logger } from 'matrix-js-sdk/src/logger'; import { logger } from 'matrix-js-sdk/src/logger';
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon'; import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
@ -35,7 +34,7 @@ import SettingsStore from '../../settings/SettingsStore';
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import { Layout } from "../../settings/enums/Layout"; import { Layout } from "../../settings/enums/Layout";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import EventTile, { UnwrappedEventTile, IReadReceiptProps } from "../views/rooms/EventTile"; import EventTile, { UnwrappedEventTile, GetRelationsForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
import { hasText } from "../../TextForEvent"; import { hasText } from "../../TextForEvent";
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer"; import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
import DMRoomMap from "../../utils/DMRoomMap"; import DMRoomMap from "../../utils/DMRoomMap";
@ -186,7 +185,7 @@ interface IProps {
// helper function to access relations for an event // helper function to access relations for an event
onUnfillRequest?(backwards: boolean, scrollToken: string): void; onUnfillRequest?(backwards: boolean, scrollToken: string): void;
getRelationsForEvent?(eventId: string, relationType: string, eventType: string): Relations; getRelationsForEvent?: GetRelationsForEvent;
hideThreadedMessages?: boolean; hideThreadedMessages?: boolean;
disableGrouping?: boolean; disableGrouping?: boolean;

View file

@ -1714,7 +1714,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
private getRelationsForEvent = ( private getRelationsForEvent = (
eventId: string, eventId: string,
relationType: RelationType, relationType: RelationType | string,
eventType: EventType | string, eventType: EventType | string,
) => this.props.timelineSet.relations?.getChildEventsForEvent(eventId, relationType, eventType); ) => this.props.timelineSet.relations?.getChildEventsForEvent(eventId, relationType, eventType);

View file

@ -130,7 +130,7 @@ interface IProps extends IPosition {
// True if the menu is being used as a right click menu // True if the menu is being used as a right click menu
rightClick?: boolean; rightClick?: boolean;
// The Relations model from the JS SDK for reactions to `mxEvent` // The Relations model from the JS SDK for reactions to `mxEvent`
reactions?: Relations; reactions?: Relations | null | undefined;
// A permalink to this event or an href of an anchor element the user has clicked // A permalink to this event or an href of an anchor element the user has clicked
link?: string; link?: string;

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from "react"; import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { Relations } from "matrix-js-sdk/src/models/relations";
import { PollEndEvent } from "matrix-events-sdk"; import { PollEndEvent } from "matrix-events-sdk";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -26,16 +25,13 @@ import QuestionDialog from "./QuestionDialog";
import { findTopAnswer } from "../messages/MPollBody"; import { findTopAnswer } from "../messages/MPollBody";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import ErrorDialog from "./ErrorDialog"; import ErrorDialog from "./ErrorDialog";
import { GetRelationsForEvent } from "../rooms/EventTile";
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
matrixClient: MatrixClient; matrixClient: MatrixClient;
event: MatrixEvent; event: MatrixEvent;
onFinished: (success: boolean) => void; onFinished: (success: boolean) => void;
getRelationsForEvent?: ( getRelationsForEvent?: GetRelationsForEvent;
eventId: string,
relationType: string,
eventType: string
) => Relations;
} }
export default class EndPollDialog extends React.Component<IProps> { export default class EndPollDialog extends React.Component<IProps> {

View file

@ -19,7 +19,6 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { Room } from 'matrix-js-sdk/src/models/room'; import { Room } from 'matrix-js-sdk/src/models/room';
import { Relations } from 'matrix-js-sdk/src/models/relations';
import { MatrixClient } from 'matrix-js-sdk/src/client'; import { MatrixClient } from 'matrix-js-sdk/src/client';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -36,6 +35,7 @@ import AccessibleButton, { ButtonEvent } from './AccessibleButton';
import { getParentEventId, shouldDisplayReply } from '../../../utils/Reply'; import { getParentEventId, shouldDisplayReply } from '../../../utils/Reply';
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { GetRelationsForEvent } from "../rooms/EventTile";
/** /**
* This number is based on the previous behavior - if we have message of height * This number is based on the previous behavior - if we have message of height
@ -56,9 +56,7 @@ interface IProps {
forExport?: boolean; forExport?: boolean;
isQuoteExpanded?: boolean; isQuoteExpanded?: boolean;
setQuoteExpanded: (isExpanded: boolean) => void; setQuoteExpanded: (isExpanded: boolean) => void;
getRelationsForEvent?: ( getRelationsForEvent?: GetRelationsForEvent;
(eventId: string, relationType: string, eventType: string) => Relations
);
} }
interface IState { interface IState {

View file

@ -29,7 +29,7 @@ import { FocusComposerPayload } from '../../../dispatcher/payloads/FocusComposer
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
reactions?: Relations; reactions?: Relations | null | undefined;
onFinished(): void; onFinished(): void;
} }

View file

@ -16,11 +16,11 @@ limitations under the License.
import React, { LegacyRef } from "react"; import React, { LegacyRef } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Relations } from "matrix-js-sdk/src/models/relations";
import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import { MediaEventHelper } from "../../../utils/MediaEventHelper";
import EditorStateTransfer from "../../../utils/EditorStateTransfer"; import EditorStateTransfer from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { GetRelationsForEvent } from "../rooms/EventTile";
export interface IBodyProps { export interface IBodyProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -52,7 +52,7 @@ export interface IBodyProps {
isSeeingThroughMessageHiddenForModeration?: boolean; isSeeingThroughMessageHiddenForModeration?: boolean;
// helper function to access relations for this event // helper function to access relations for this event
getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations; getRelationsForEvent?: GetRelationsForEvent;
ref?: React.RefObject<any> | LegacyRef<any>; ref?: React.RefObject<any> | LegacyRef<any>;
} }

View file

@ -101,8 +101,8 @@ const useUniqueId = (eventId: string): string => {
// remove related beacon locations on beacon redaction // remove related beacon locations on beacon redaction
const useHandleBeaconRedaction = ( const useHandleBeaconRedaction = (
event: MatrixEvent, event: MatrixEvent,
getRelationsForEvent: GetRelationsForEvent, matrixClient: MatrixClient,
cli: MatrixClient, getRelationsForEvent?: GetRelationsForEvent,
): void => { ): void => {
const onBeforeBeaconInfoRedaction = useCallback((_event: MatrixEvent, redactionEvent: MatrixEvent) => { const onBeforeBeaconInfoRedaction = useCallback((_event: MatrixEvent, redactionEvent: MatrixEvent) => {
const relations = getRelationsForEvent ? const relations = getRelationsForEvent ?
@ -110,14 +110,14 @@ const useHandleBeaconRedaction = (
undefined; undefined;
relations?.getRelations()?.forEach(locationEvent => { relations?.getRelations()?.forEach(locationEvent => {
cli.redactEvent( matrixClient.redactEvent(
locationEvent.getRoomId(), locationEvent.getRoomId(),
locationEvent.getId(), locationEvent.getId(),
undefined, undefined,
redactionEvent.getContent(), redactionEvent.getContent(),
); );
}); });
}, [event, getRelationsForEvent, cli]); }, [event, matrixClient, getRelationsForEvent]);
useEffect(() => { useEffect(() => {
event.addListener(MatrixEventEvent.BeforeRedaction, onBeforeBeaconInfoRedaction); event.addListener(MatrixEventEvent.BeforeRedaction, onBeforeBeaconInfoRedaction);
@ -151,7 +151,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined; const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId(); const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId();
useHandleBeaconRedaction(mxEvent, getRelationsForEvent, matrixClient); useHandleBeaconRedaction(mxEvent, matrixClient, getRelationsForEvent);
const onClick = () => { const onClick = () => {
if (displayStatus !== BeaconDisplayStatus.Active) { if (displayStatus !== BeaconDisplayStatus.Active) {

View file

@ -16,6 +16,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
import { Relations, RelationsEvent } from 'matrix-js-sdk/src/models/relations'; import { Relations, RelationsEvent } from 'matrix-js-sdk/src/models/relations';
import { MatrixClient } from 'matrix-js-sdk/src/matrix'; import { MatrixClient } from 'matrix-js-sdk/src/matrix';
@ -43,49 +44,59 @@ import PollCreateDialog from "../elements/PollCreateDialog";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
interface IState { interface IState {
selected?: string; // Which option was clicked by the local user selected?: string | null | undefined; // Which option was clicked by the local user
voteRelations: RelatedRelations; // Voting (response) events voteRelations: RelatedRelations; // Voting (response) events
endRelations: RelatedRelations; // Poll end events endRelations: RelatedRelations; // Poll end events
} }
export function createVoteRelations( export function createVoteRelations(
getRelationsForEvent: ( getRelationsForEvent: GetRelationsForEvent,
eventId: string,
relationType: string,
eventType: string
) => Relations,
eventId: string, eventId: string,
) { ) {
return new RelatedRelations([ const relationsList: Relations[] = [];
getRelationsForEvent(
eventId, const pollResponseRelations = getRelationsForEvent(
"m.reference", eventId,
M_POLL_RESPONSE.name, "m.reference",
), M_POLL_RESPONSE.name,
getRelationsForEvent( );
eventId, if (pollResponseRelations) {
"m.reference", relationsList.push(pollResponseRelations);
M_POLL_RESPONSE.altName, }
),
]); const pollResposnseAltRelations = getRelationsForEvent(
eventId,
"m.reference",
M_POLL_RESPONSE.altName,
);
if (pollResposnseAltRelations) {
relationsList.push(pollResposnseAltRelations);
}
return new RelatedRelations(relationsList);
} }
export function findTopAnswer( export function findTopAnswer(
pollEvent: MatrixEvent, pollEvent: MatrixEvent,
matrixClient: MatrixClient, matrixClient: MatrixClient,
getRelationsForEvent?: ( getRelationsForEvent?: GetRelationsForEvent,
eventId: string,
relationType: string,
eventType: string
) => Relations,
): string { ): string {
if (!getRelationsForEvent) { if (!getRelationsForEvent) {
return ""; return "";
} }
const pollEventId = pollEvent.getId();
if (!pollEventId) {
logger.warn(
"findTopAnswer: Poll event needs an event ID to fetch relations in order to determine " +
"the top answer - assuming no best answer",
);
return "";
}
const poll = pollEvent.unstableExtensibleEvent as PollStartEvent; const poll = pollEvent.unstableExtensibleEvent as PollStartEvent;
if (!poll?.isEquivalentTo(M_POLL_START)) { if (!poll?.isEquivalentTo(M_POLL_START)) {
console.warn("Failed to parse poll to determine top answer - assuming no best answer"); logger.warn("Failed to parse poll to determine top answer - assuming no best answer");
return ""; return "";
} }
@ -93,25 +104,32 @@ export function findTopAnswer(
return poll.answers.find(a => a.id === answerId)?.text ?? ""; return poll.answers.find(a => a.id === answerId)?.text ?? "";
}; };
const voteRelations = createVoteRelations(getRelationsForEvent, pollEvent.getId()); const voteRelations = createVoteRelations(getRelationsForEvent, pollEventId);
const endRelations = new RelatedRelations([ const relationsList: Relations[] = [];
getRelationsForEvent(
pollEvent.getId(), const pollEndRelations = getRelationsForEvent(
"m.reference", pollEventId,
M_POLL_END.name, "m.reference",
), M_POLL_END.name,
getRelationsForEvent( );
pollEvent.getId(), if (pollEndRelations) {
"m.reference", relationsList.push(pollEndRelations);
M_POLL_END.altName, }
),
]); const pollEndAltRelations = getRelationsForEvent(
pollEventId,
"m.reference",
M_POLL_END.altName,
);
if (pollEndAltRelations) {
relationsList.push(pollEndAltRelations);
}
const endRelations = new RelatedRelations(relationsList);
const userVotes: Map<string, UserVote> = collectUserVotes( const userVotes: Map<string, UserVote> = collectUserVotes(
allVotes(pollEvent, matrixClient, voteRelations, endRelations), allVotes(pollEvent, matrixClient, voteRelations, endRelations),
matrixClient.getUserId(),
null,
); );
const votes: Map<string, number> = countVotes(userVotes, poll); const votes: Map<string, number> = countVotes(userVotes, poll);
@ -132,36 +150,60 @@ export function findTopAnswer(
export function isPollEnded( export function isPollEnded(
pollEvent: MatrixEvent, pollEvent: MatrixEvent,
matrixClient: MatrixClient, matrixClient: MatrixClient,
getRelationsForEvent?: ( getRelationsForEvent?: GetRelationsForEvent,
eventId: string,
relationType: string,
eventType: string
) => Relations,
): boolean { ): boolean {
if (!getRelationsForEvent) { if (!getRelationsForEvent) {
return false; return false;
} }
const roomCurrentState = matrixClient.getRoom(pollEvent.getRoomId()).currentState; const pollEventId = pollEvent.getId();
if (!pollEventId) {
logger.warn(
"isPollEnded: Poll event must have event ID in order to determine whether it has ended " +
"- assuming poll has not ended",
);
return false;
}
const roomId = pollEvent.getRoomId();
if (!roomId) {
logger.warn(
"isPollEnded: Poll event must have room ID in order to determine whether it has ended " +
"- assuming poll has not ended",
);
return false;
}
const roomCurrentState = matrixClient.getRoom(roomId)?.currentState;
function userCanRedact(endEvent: MatrixEvent) { function userCanRedact(endEvent: MatrixEvent) {
return roomCurrentState.maySendRedactionForEvent( const endEventSender = endEvent.getSender();
return endEventSender && roomCurrentState && roomCurrentState.maySendRedactionForEvent(
pollEvent, pollEvent,
endEvent.getSender(), endEventSender,
); );
} }
const endRelations = new RelatedRelations([ const relationsList: Relations[] = [];
getRelationsForEvent(
pollEvent.getId(), const pollEndRelations = getRelationsForEvent(
"m.reference", pollEventId,
M_POLL_END.name, "m.reference",
), M_POLL_END.name,
getRelationsForEvent( );
pollEvent.getId(), if (pollEndRelations) {
"m.reference", relationsList.push(pollEndRelations);
M_POLL_END.altName, }
),
]); const pollEndAltRelations = getRelationsForEvent(
pollEventId,
"m.reference",
M_POLL_END.altName,
);
if (pollEndAltRelations) {
relationsList.push(pollEndAltRelations);
}
const endRelations = new RelatedRelations(relationsList);
if (!endRelations) { if (!endRelations) {
return false; return false;
@ -175,7 +217,10 @@ export function isPollEnded(
export function pollAlreadyHasVotes(mxEvent: MatrixEvent, getRelationsForEvent?: GetRelationsForEvent): boolean { export function pollAlreadyHasVotes(mxEvent: MatrixEvent, getRelationsForEvent?: GetRelationsForEvent): boolean {
if (!getRelationsForEvent) return false; if (!getRelationsForEvent) return false;
const voteRelations = createVoteRelations(getRelationsForEvent, mxEvent.getId()); const eventId = mxEvent.getId();
if (!eventId) return false;
const voteRelations = createVoteRelations(getRelationsForEvent, eventId);
return voteRelations.getRelations().length > 0; return voteRelations.getRelations().length > 0;
} }
@ -335,18 +380,35 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
private fetchRelations(eventType: NamespacedValue<string, string>): RelatedRelations | null { private fetchRelations(eventType: NamespacedValue<string, string>): RelatedRelations | null {
if (this.props.getRelationsForEvent) { if (this.props.getRelationsForEvent) {
return new RelatedRelations([ const relationsList: Relations[] = [];
this.props.getRelationsForEvent(
this.props.mxEvent.getId(), const eventId = this.props.mxEvent.getId();
"m.reference", if (!eventId) {
eventType.name, return null;
), }
this.props.getRelationsForEvent(
this.props.mxEvent.getId(), const relations = this.props.getRelationsForEvent(
eventId,
"m.reference",
eventType.name,
);
if (relations) {
relationsList.push(relations);
}
// If there is an alternatve experimental event type, also look for that
if (eventType.altName) {
const altRelations = this.props.getRelationsForEvent(
eventId,
"m.reference", "m.reference",
eventType.altName, eventType.altName,
), );
]); if (altRelations) {
relationsList.push(altRelations);
}
}
return new RelatedRelations(relationsList);
} else { } else {
return null; return null;
} }
@ -380,7 +442,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
const newEvents: MatrixEvent[] = this.state.voteRelations.getRelations() const newEvents: MatrixEvent[] = this.state.voteRelations.getRelations()
.filter(isPollResponse) .filter(isPollResponse)
.filter((mxEvent: MatrixEvent) => .filter((mxEvent: MatrixEvent) =>
!this.seenEventIds.includes(mxEvent.getId())); !this.seenEventIds.includes(mxEvent.getId()!));
let newSelected = this.state.selected; let newSelected = this.state.selected;
if (newEvents.length > 0) { if (newEvents.length > 0) {
@ -422,7 +484,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
const totalVotes = this.totalVotes(votes); const totalVotes = this.totalVotes(votes);
const winCount = Math.max(...votes.values()); const winCount = Math.max(...votes.values());
const userId = this.context.getUserId(); const userId = this.context.getUserId();
const myVote = userVotes.get(userId)?.answers[0]; const myVote = userVotes?.get(userId!)?.answers[0];
const disclosed = M_POLL_KIND_DISCLOSED.matches(poll.kind.name); const disclosed = M_POLL_KIND_DISCLOSED.matches(poll.kind.name);
// Disclosed: votes are hidden until I vote or the poll ends // Disclosed: votes are hidden until I vote or the poll ends
@ -655,12 +717,16 @@ function isPollResponse(responseEvent: MatrixEvent): boolean {
/** /**
* Figure out the correct vote for each user. * Figure out the correct vote for each user.
* @param userResponses current vote responses in the poll
* @param {string?} userId The userId for which the `selected` option will apply to.
* Should be set to the current user ID.
* @param {string?} selected Local echo selected option for the userId
* @returns a Map of user ID to their vote info * @returns a Map of user ID to their vote info
*/ */
function collectUserVotes( function collectUserVotes(
userResponses: Array<UserVote>, userResponses: Array<UserVote>,
userId: string, userId?: string | null | undefined,
selected?: string, selected?: string | null | undefined,
): Map<string, UserVote> { ): Map<string, UserVote> {
const userVotes: Map<string, UserVote> = new Map(); const userVotes: Map<string, UserVote> = new Map();
@ -671,7 +737,7 @@ function collectUserVotes(
} }
} }
if (selected) { if (selected && userId) {
userVotes.set(userId, new UserVote(0, userId, [selected])); userVotes.set(userId, new UserVote(0, userId, [selected]));
} }

View file

@ -59,6 +59,7 @@ import { Action } from '../../../dispatcher/actions';
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; import useFavouriteMessages from '../../../hooks/useFavouriteMessages';
import { GetRelationsForEvent } from '../rooms/EventTile';
interface IOptionsButtonProps { interface IOptionsButtonProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -67,11 +68,7 @@ interface IOptionsButtonProps {
getReplyChain: () => ReplyChain; getReplyChain: () => ReplyChain;
permalinkCreator: RoomPermalinkCreator; permalinkCreator: RoomPermalinkCreator;
onFocusChange: (menuDisplayed: boolean) => void; onFocusChange: (menuDisplayed: boolean) => void;
getRelationsForEvent?: ( getRelationsForEvent?: GetRelationsForEvent;
eventId: string,
relationType: string,
eventType: string
) => Relations;
} }
const OptionsButton: React.FC<IOptionsButtonProps> = ({ const OptionsButton: React.FC<IOptionsButtonProps> = ({
@ -135,7 +132,7 @@ const OptionsButton: React.FC<IOptionsButtonProps> = ({
interface IReactButtonProps { interface IReactButtonProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
reactions: Relations; reactions?: Relations | null | undefined;
onFocusChange: (menuDisplayed: boolean) => void; onFocusChange: (menuDisplayed: boolean) => void;
} }
@ -299,7 +296,7 @@ const FavouriteButton = ({ mxEvent }: IFavouriteButtonProp) => {
interface IMessageActionBarProps { interface IMessageActionBarProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
reactions?: Relations; reactions?: Relations | null | undefined;
// TODO: Types // TODO: Types
getTile: () => any | null; getTile: () => any | null;
getReplyChain: () => ReplyChain | undefined; getReplyChain: () => ReplyChain | undefined;
@ -307,11 +304,7 @@ interface IMessageActionBarProps {
onFocusChange?: (menuDisplayed: boolean) => void; onFocusChange?: (menuDisplayed: boolean) => void;
toggleThreadExpanded: () => void; toggleThreadExpanded: () => void;
isQuoteExpanded?: boolean; isQuoteExpanded?: boolean;
getRelationsForEvent?: ( getRelationsForEvent?: GetRelationsForEvent;
eventId: string,
relationType: RelationType | string,
eventType: string
) => Relations;
} }
export default class MessageActionBar extends React.PureComponent<IMessageActionBarProps> { export default class MessageActionBar extends React.PureComponent<IMessageActionBarProps> {

View file

@ -16,7 +16,6 @@ limitations under the License.
import React, { createRef } from 'react'; import React, { createRef } from 'react';
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { Relations } from 'matrix-js-sdk/src/models/relations';
import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon'; import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
import { M_LOCATION } from 'matrix-js-sdk/src/@types/location'; import { M_LOCATION } from 'matrix-js-sdk/src/@types/location';
import { M_POLL_START } from "matrix-events-sdk"; import { M_POLL_START } from "matrix-events-sdk";
@ -41,7 +40,7 @@ import MPollBody from "./MPollBody";
import MLocationBody from "./MLocationBody"; import MLocationBody from "./MLocationBody";
import MjolnirBody from "./MjolnirBody"; import MjolnirBody from "./MjolnirBody";
import MBeaconBody from "./MBeaconBody"; import MBeaconBody from "./MBeaconBody";
import { IEventTileOps } from "../rooms/EventTile"; import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile";
import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from '../../../voice-broadcast'; import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from '../../../voice-broadcast';
// onMessageAllowed is handled internally // onMessageAllowed is handled internally
@ -51,7 +50,7 @@ interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper
overrideEventTypes?: Record<string, typeof React.Component>; overrideEventTypes?: Record<string, typeof React.Component>;
// helper function to access relations for this event // helper function to access relations for this event
getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations; getRelationsForEvent?: GetRelationsForEvent;
isSeeingThroughMessageHiddenForModeration?: boolean; isSeeingThroughMessageHiddenForModeration?: boolean;
} }

View file

@ -65,7 +65,7 @@ interface IProps {
// The event we're displaying reactions for // The event we're displaying reactions for
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
// The Relations model from the JS SDK for reactions to `mxEvent` // The Relations model from the JS SDK for reactions to `mxEvent`
reactions?: Relations; reactions?: Relations | null | undefined;
} }
interface IState { interface IState {
@ -114,7 +114,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
} }
componentDidUpdate(prevProps: IProps) { componentDidUpdate(prevProps: IProps) {
if (prevProps.reactions !== this.props.reactions) { if (this.props.reactions && prevProps.reactions !== this.props.reactions) {
this.props.reactions.on(RelationsEvent.Add, this.onReactionsChange); this.props.reactions.on(RelationsEvent.Add, this.onReactionsChange);
this.props.reactions.on(RelationsEvent.Remove, this.onReactionsChange); this.props.reactions.on(RelationsEvent.Remove, this.onReactionsChange);
this.props.reactions.on(RelationsEvent.Redaction, this.onReactionsChange); this.props.reactions.on(RelationsEvent.Redaction, this.onReactionsChange);

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, { createRef, forwardRef, MouseEvent, RefObject } from 'react'; import React, { createRef, forwardRef, MouseEvent, RefObject } from 'react';
import classNames from "classnames"; import classNames from "classnames";
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
import { EventStatus, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { EventStatus, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
import { Relations } from "matrix-js-sdk/src/models/relations"; import { Relations } from "matrix-js-sdk/src/models/relations";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
@ -87,7 +87,11 @@ import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom';
import { ElementCall } from "../../../models/Call"; import { ElementCall } from "../../../models/Call";
import { UnreadNotificationBadge } from './NotificationBadge/UnreadNotificationBadge'; import { UnreadNotificationBadge } from './NotificationBadge/UnreadNotificationBadge';
export type GetRelationsForEvent = (eventId: string, relationType: string, eventType: string) => Relations; export type GetRelationsForEvent = (
eventId: string,
relationType: RelationType | string,
eventType: EventType | string,
) => Relations | null | undefined;
// Our component structure for EventTiles on the timeline is: // Our component structure for EventTiles on the timeline is:
// //
@ -233,7 +237,7 @@ interface IState {
// Whether onRequestKeysClick has been called since mounting. // Whether onRequestKeysClick has been called since mounting.
previouslyRequestedKeys: boolean; previouslyRequestedKeys: boolean;
// The Relations model from the JS SDK for reactions to `mxEvent` // The Relations model from the JS SDK for reactions to `mxEvent`
reactions: Relations; reactions?: Relations | null | undefined;
hover: boolean; hover: boolean;

View file

@ -19,7 +19,6 @@ import classNames from 'classnames';
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
import { EventType, MsgType } from 'matrix-js-sdk/src/@types/event'; import { EventType, MsgType } from 'matrix-js-sdk/src/@types/event';
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Relations } from 'matrix-js-sdk/src/models/relations';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
@ -33,6 +32,7 @@ import MFileBody from "../messages/MFileBody";
import MVoiceMessageBody from "../messages/MVoiceMessageBody"; import MVoiceMessageBody from "../messages/MVoiceMessageBody";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { renderReplyTile } from "../../../events/EventTileFactory"; import { renderReplyTile } from "../../../events/EventTileFactory";
import { GetRelationsForEvent } from "../rooms/EventTile";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -41,9 +41,7 @@ interface IProps {
highlightLink?: string; highlightLink?: string;
onHeightChanged?(): void; onHeightChanged?(): void;
toggleExpandedQuote?: () => void; toggleExpandedQuote?: () => void;
getRelationsForEvent?: ( getRelationsForEvent?: GetRelationsForEvent;
(eventId: string, relationType: string, eventType: string) => Relations
);
} }
export default class ReplyTile extends React.PureComponent<IProps> { export default class ReplyTile extends React.PureComponent<IProps> {

View file

@ -321,12 +321,13 @@ describe("MPollBody", () => {
const votes = [responseEvent("@me:example.com", "pizza", 100)]; const votes = [responseEvent("@me:example.com", "pizza", 100)];
const body = newMPollBody(votes); const body = newMPollBody(votes);
const props: IBodyProps = body.instance().props as IBodyProps; const props: IBodyProps = body.instance().props as IBodyProps;
const voteRelations: Relations = props.getRelationsForEvent( const voteRelations = props!.getRelationsForEvent!(
"$mypoll", "m.reference", M_POLL_RESPONSE.name); "$mypoll", "m.reference", M_POLL_RESPONSE.name);
expect(voteRelations).toBeDefined();
clickRadio(body, "pizza"); clickRadio(body, "pizza");
// When a new vote from me comes in // When a new vote from me comes in
voteRelations.addEvent(responseEvent("@me:example.com", "wings", 101)); voteRelations!.addEvent(responseEvent("@me:example.com", "wings", 101));
// Then the new vote is counted, not the old one // Then the new vote is counted, not the old one
expect(votesCount(body, "pizza")).toBe("0 votes"); expect(votesCount(body, "pizza")).toBe("0 votes");
@ -342,12 +343,13 @@ describe("MPollBody", () => {
const votes = [responseEvent("@me:example.com", "pizza")]; const votes = [responseEvent("@me:example.com", "pizza")];
const body = newMPollBody(votes); const body = newMPollBody(votes);
const props: IBodyProps = body.instance().props as IBodyProps; const props: IBodyProps = body.instance().props as IBodyProps;
const voteRelations: Relations = props.getRelationsForEvent( const voteRelations = props!.getRelationsForEvent!(
"$mypoll", "m.reference", M_POLL_RESPONSE.name); "$mypoll", "m.reference", M_POLL_RESPONSE.name);
expect(voteRelations).toBeDefined();
clickRadio(body, "pizza"); clickRadio(body, "pizza");
// When a new vote from someone else comes in // When a new vote from someone else comes in
voteRelations.addEvent(responseEvent("@xx:example.com", "wings", 101)); voteRelations!.addEvent(responseEvent("@xx:example.com", "wings", 101));
// Then my vote is still for pizza // Then my vote is still for pizza
// NOTE: the new event does not affect the counts for other people - // NOTE: the new event does not affect the counts for other people -