Fix pinned messages card saying nothing pinned while loading (#10385)

This commit is contained in:
Michael Telatynski 2023-03-15 12:47:21 +00:00 committed by GitHub
parent 0c38bd7beb
commit 87fb493783
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 43 deletions

View file

@ -45,50 +45,53 @@ interface IProps {
onClose(): void; onClose(): void;
} }
function getPinnedEventIds(room?: Room): string[] {
return room?.currentState.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent()?.pinned ?? [];
}
export const usePinnedEvents = (room?: Room): string[] => { export const usePinnedEvents = (room?: Room): string[] => {
const [pinnedEvents, setPinnedEvents] = useState<string[]>([]); const [pinnedEvents, setPinnedEvents] = useState<string[]>(getPinnedEventIds(room));
const update = useCallback( const update = useCallback(
(ev?: MatrixEvent) => { (ev?: MatrixEvent) => {
if (!room) return;
if (ev && ev.getType() !== EventType.RoomPinnedEvents) return; if (ev && ev.getType() !== EventType.RoomPinnedEvents) return;
setPinnedEvents( setPinnedEvents(getPinnedEventIds(room));
room.currentState.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent()?.pinned || [],
);
}, },
[room], [room],
); );
useTypedEventEmitter(room?.currentState, RoomStateEvent.Events, update); useTypedEventEmitter(room?.currentState, RoomStateEvent.Events, update);
useEffect(() => { useEffect(() => {
update(); setPinnedEvents(getPinnedEventIds(room));
return () => { return () => {
setPinnedEvents([]); setPinnedEvents([]);
}; };
}, [update]); }, [room]);
return pinnedEvents; return pinnedEvents;
}; };
export const useReadPinnedEvents = (room: Room): Set<string> => { function getReadPinnedEventIds(room?: Room): Set<string> {
return new Set(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []);
}
export const useReadPinnedEvents = (room?: Room): Set<string> => {
const [readPinnedEvents, setReadPinnedEvents] = useState<Set<string>>(new Set()); const [readPinnedEvents, setReadPinnedEvents] = useState<Set<string>>(new Set());
const update = useCallback( const update = useCallback(
(ev?: MatrixEvent) => { (ev?: MatrixEvent) => {
if (!room) return;
if (ev && ev.getType() !== ReadPinsEventId) return; if (ev && ev.getType() !== ReadPinsEventId) return;
const readPins = room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids; setReadPinnedEvents(getReadPinnedEventIds(room));
setReadPinnedEvents(new Set(readPins || []));
}, },
[room], [room],
); );
useTypedEventEmitter(room, RoomEvent.AccountData, update); useTypedEventEmitter(room, RoomEvent.AccountData, update);
useEffect(() => { useEffect(() => {
update(); setReadPinnedEvents(getReadPinnedEventIds(room));
return () => { return () => {
setReadPinnedEvents(new Set()); setReadPinnedEvents(new Set());
}; };
}, [update]); }, [room]);
return readPinnedEvents; return readPinnedEvents;
}; };
@ -157,34 +160,8 @@ const PinnedMessagesCard: React.FC<IProps> = ({ room, onClose, permalinkCreator
null, null,
); );
let content; let content: JSX.Element[] | JSX.Element | undefined;
if (!pinnedEvents) { if (!pinnedEventIds.length) {
content = <Spinner />;
} else if (pinnedEvents.length > 0) {
const onUnpinClicked = async (event: MatrixEvent): Promise<void> => {
const pinnedEvents = room.currentState.getStateEvents(EventType.RoomPinnedEvents, "");
if (pinnedEvents?.getContent()?.pinned) {
const pinned = pinnedEvents.getContent().pinned;
const index = pinned.indexOf(event.getId());
if (index !== -1) {
pinned.splice(index, 1);
await cli.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned }, "");
}
}
};
// show them in reverse, with latest pinned at the top
content = filterBoolean(pinnedEvents)
.reverse()
.map((ev) => (
<PinnedEventTile
key={ev.getId()}
event={ev}
onUnpinClicked={canUnpin ? () => onUnpinClicked(ev) : undefined}
permalinkCreator={permalinkCreator}
/>
));
} else {
content = ( content = (
<div className="mx_PinnedMessagesCard_empty_wrapper"> <div className="mx_PinnedMessagesCard_empty_wrapper">
<div className="mx_PinnedMessagesCard_empty"> <div className="mx_PinnedMessagesCard_empty">
@ -215,6 +192,32 @@ const PinnedMessagesCard: React.FC<IProps> = ({ room, onClose, permalinkCreator
</div> </div>
</div> </div>
); );
} else if (pinnedEvents?.length) {
const onUnpinClicked = async (event: MatrixEvent): Promise<void> => {
const pinnedEvents = room.currentState.getStateEvents(EventType.RoomPinnedEvents, "");
if (pinnedEvents?.getContent()?.pinned) {
const pinned = pinnedEvents.getContent().pinned;
const index = pinned.indexOf(event.getId());
if (index !== -1) {
pinned.splice(index, 1);
await cli.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned }, "");
}
}
};
// show them in reverse, with latest pinned at the top
content = filterBoolean(pinnedEvents)
.reverse()
.map((ev) => (
<PinnedEventTile
key={ev.getId()}
event={ev}
onUnpinClicked={canUnpin ? () => onUnpinClicked(ev) : undefined}
permalinkCreator={permalinkCreator}
/>
));
} else {
content = <Spinner />;
} }
return ( return (

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { render, act, RenderResult } from "@testing-library/react"; import { render, act, RenderResult, fireEvent, waitForElementToBeRemoved, screen } from "@testing-library/react";
import { mocked } from "jest-mock"; import { mocked } from "jest-mock";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType, RelationType, MsgType } from "matrix-js-sdk/src/@types/event"; import { EventType, RelationType, MsgType } from "matrix-js-sdk/src/@types/event";
@ -240,7 +240,7 @@ describe("<PinnedMessagesCard />", () => {
...PollResponseEvent.from([answers[option as number].id], poll.getId()!).serialize(), ...PollResponseEvent.from([answers[option as number].id], poll.getId()!).serialize(),
event: true, event: true,
room: "!room:example.org", room: "!room:example.org",
user: user as string, user,
}), }),
); );
@ -284,4 +284,28 @@ describe("<PinnedMessagesCard />", () => {
expect(pinTile[0].querySelectorAll(".mx_PollOption_optionVoteCount")[0]).toHaveTextContent("2 votes"); expect(pinTile[0].querySelectorAll(".mx_PollOption_optionVoteCount")[0]).toHaveTextContent("2 votes");
expect([...pinTile[0].querySelectorAll(".mx_PollOption_optionVoteCount")].at(-1)).toHaveTextContent("1 vote"); expect([...pinTile[0].querySelectorAll(".mx_PollOption_optionVoteCount")].at(-1)).toHaveTextContent("1 vote");
}); });
it("should allow admins to unpin messages", async () => {
const nonLocalPins = [pin1];
const room = mkRoom([], nonLocalPins);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
const sendStateEvent = jest.spyOn(cli, "sendStateEvent");
const pins = await mountPins(room);
const pinTile = pins.container.querySelectorAll(".mx_PinnedEventTile");
expect(pinTile).toHaveLength(1);
fireEvent.click(pinTile[0].querySelector(".mx_PinnedEventTile_unpinButton")!);
expect(sendStateEvent).toHaveBeenCalledWith(room.roomId, "m.room.pinned_events", { pinned: [] }, "");
nonLocalPins.pop();
await Promise.all([waitForElementToBeRemoved(pinTile[0]), emitPinUpdates(room)]);
});
it("should show spinner whilst loading", async () => {
const room = mkRoom([], [pin1]);
mountPins(room);
const spinner = await screen.findByTestId("spinner");
await waitForElementToBeRemoved(spinner);
});
}); });