riot-web/src/utils/PinningUtils.ts

153 lines
5.6 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
Copyright 2017 Travis Ralston
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { MatrixEvent, EventType, M_POLL_START, MatrixClient, EventTimeline, Room } from "matrix-js-sdk/src/matrix";
import { isContentActionable } from "./EventUtils";
import { ReadPinsEventId } from "../components/views/right_panel/types";
export default class PinningUtils {
/**
* Event types that may be pinned.
*/
public static readonly PINNABLE_EVENT_TYPES: (EventType | string)[] = [
EventType.RoomMessage,
M_POLL_START.name,
M_POLL_START.altName,
];
/**
* Determines if the given event can be pinned.
* This is a simple check to see if the event is of a type that can be pinned.
* @param {MatrixEvent} event The event to check.
* @return {boolean} True if the event may be pinned, false otherwise.
*/
public static isPinnable(event: MatrixEvent): boolean {
if (event.isRedacted()) return false;
return PinningUtils.isUnpinnable(event);
}
/**
* Determines if the given event may be unpinned.
* @param {MatrixEvent} event The event to check.
* @return {boolean} True if the event may be unpinned, false otherwise.
*/
public static isUnpinnable(event: MatrixEvent): boolean {
if (!event) return false;
if (event.isRedacted()) return true;
return this.PINNABLE_EVENT_TYPES.includes(event.getType());
}
/**
* Determines if the given event is pinned.
* @param matrixClient
* @param mxEvent
*/
public static isPinned(matrixClient: MatrixClient, mxEvent: MatrixEvent): boolean {
const room = matrixClient.getRoom(mxEvent.getRoomId());
if (!room) return false;
const pinnedEvent = room
.getLiveTimeline()
.getState(EventTimeline.FORWARDS)
?.getStateEvents(EventType.RoomPinnedEvents, "");
if (!pinnedEvent) return false;
const content = pinnedEvent.getContent();
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(mxEvent.getId());
}
/**
* Determines if the given event may be pinned by the current user.
* This checks if the user has the necessary permissions to pin or unpin the event, and if the event is pinnable.
* @param matrixClient
* @param mxEvent
*/
public static canPin(matrixClient: MatrixClient, mxEvent: MatrixEvent): boolean {
if (!isContentActionable(mxEvent)) return false;
const room = matrixClient.getRoom(mxEvent.getRoomId());
if (!room) return false;
return PinningUtils.userHasPinOrUnpinPermission(matrixClient, room) && PinningUtils.isPinnable(mxEvent);
}
/**
* Determines if the given event may be unpinned by the current user.
* This checks if the user has the necessary permissions to pin or unpin the event, and if the event is unpinnable.
* @param matrixClient
* @param mxEvent
*/
public static canUnpin(matrixClient: MatrixClient, mxEvent: MatrixEvent): boolean {
const room = matrixClient.getRoom(mxEvent.getRoomId());
if (!room) return false;
return PinningUtils.userHasPinOrUnpinPermission(matrixClient, room) && PinningUtils.isUnpinnable(mxEvent);
}
/**
* Determines if the current user has permission to pin or unpin events in the given room.
* @param matrixClient
* @param room
*/
public static userHasPinOrUnpinPermission(matrixClient: MatrixClient, room: Room): boolean {
return Boolean(
room
.getLiveTimeline()
.getState(EventTimeline.FORWARDS)
?.mayClientSendStateEvent(EventType.RoomPinnedEvents, matrixClient),
);
}
/**
* Pin or unpin the given event.
* @param matrixClient
* @param mxEvent
*/
public static async pinOrUnpinEvent(matrixClient: MatrixClient, mxEvent: MatrixEvent): Promise<void> {
const room = matrixClient.getRoom(mxEvent.getRoomId());
if (!room) return;
const eventId = mxEvent.getId();
if (!eventId) return;
// Get the current pinned events of the room
const pinnedIds: Array<string> =
room
.getLiveTimeline()
.getState(EventTimeline.FORWARDS)
?.getStateEvents(EventType.RoomPinnedEvents, "")
?.getContent().pinned || [];
let roomAccountDataPromise: Promise<{} | void> = Promise.resolve();
// If the event is already pinned, unpin it
if (pinnedIds.includes(eventId)) {
pinnedIds.splice(pinnedIds.indexOf(eventId), 1);
} else {
// Otherwise, pin it
pinnedIds.push(eventId);
// We don't want to wait for the roomAccountDataPromise to resolve before sending the state event
roomAccountDataPromise = matrixClient.setRoomAccountData(room.roomId, ReadPinsEventId, {
event_ids: [...(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids || []), eventId],
});
}
await Promise.all([
matrixClient.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned: pinnedIds }, ""),
roomAccountDataPromise,
]);
}
/**
* Unpin all events in the given room.
* @param matrixClient
* @param roomId
*/
public static async unpinAllEvents(matrixClient: MatrixClient, roomId: string): Promise<void> {
await matrixClient.sendStateEvent(roomId, EventType.RoomPinnedEvents, { pinned: [] }, "");
}
}