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:
parent
2cd1fad10b
commit
2393510a7f
15 changed files with 181 additions and 126 deletions
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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(
|
|
||||||
|
const pollResponseRelations = getRelationsForEvent(
|
||||||
eventId,
|
eventId,
|
||||||
"m.reference",
|
"m.reference",
|
||||||
M_POLL_RESPONSE.name,
|
M_POLL_RESPONSE.name,
|
||||||
),
|
);
|
||||||
getRelationsForEvent(
|
if (pollResponseRelations) {
|
||||||
|
relationsList.push(pollResponseRelations);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pollResposnseAltRelations = getRelationsForEvent(
|
||||||
eventId,
|
eventId,
|
||||||
"m.reference",
|
"m.reference",
|
||||||
M_POLL_RESPONSE.altName,
|
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(
|
||||||
|
pollEventId,
|
||||||
"m.reference",
|
"m.reference",
|
||||||
M_POLL_END.name,
|
M_POLL_END.name,
|
||||||
),
|
);
|
||||||
getRelationsForEvent(
|
if (pollEndRelations) {
|
||||||
pollEvent.getId(),
|
relationsList.push(pollEndRelations);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pollEndAltRelations = getRelationsForEvent(
|
||||||
|
pollEventId,
|
||||||
"m.reference",
|
"m.reference",
|
||||||
M_POLL_END.altName,
|
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(
|
||||||
|
pollEventId,
|
||||||
"m.reference",
|
"m.reference",
|
||||||
M_POLL_END.name,
|
M_POLL_END.name,
|
||||||
),
|
);
|
||||||
getRelationsForEvent(
|
if (pollEndRelations) {
|
||||||
pollEvent.getId(),
|
relationsList.push(pollEndRelations);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pollEndAltRelations = getRelationsForEvent(
|
||||||
|
pollEventId,
|
||||||
"m.reference",
|
"m.reference",
|
||||||
M_POLL_END.altName,
|
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();
|
||||||
|
if (!eventId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relations = this.props.getRelationsForEvent(
|
||||||
|
eventId,
|
||||||
"m.reference",
|
"m.reference",
|
||||||
eventType.name,
|
eventType.name,
|
||||||
),
|
);
|
||||||
this.props.getRelationsForEvent(
|
if (relations) {
|
||||||
this.props.mxEvent.getId(),
|
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]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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 -
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue