Add mute list atom
This commit is contained in:
parent
4d802d918e
commit
899a5b934e
3 changed files with 219 additions and 2 deletions
93
src/app/state/mutedRoomList.ts
Normal file
93
src/app/state/mutedRoomList.ts
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import { atom, WritableAtom, useSetAtom } from 'jotai';
|
||||||
|
import { ClientEvent, IPushRule, IPushRules, MatrixClient, MatrixEvent } from 'matrix-js-sdk';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { MuteChanges } from '../../types/matrix/room';
|
||||||
|
import { findMutedRule, isMutedRule } from '../utils/room';
|
||||||
|
|
||||||
|
type MutedRoomsUpdate =
|
||||||
|
| {
|
||||||
|
type: 'INITIALIZE';
|
||||||
|
addRooms: string[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'UPDATE';
|
||||||
|
addRooms: string[];
|
||||||
|
removeRooms: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const muteChangesAtom = atom<MuteChanges>({
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const baseMutedRoomsAtom = atom(new Set<string>());
|
||||||
|
export const mutedRoomsAtom = atom<Set<string>, MutedRoomsUpdate>(
|
||||||
|
(get) => get(baseMutedRoomsAtom),
|
||||||
|
(get, set, action) => {
|
||||||
|
const mutedRooms = new Set([...get(mutedRoomsAtom)]);
|
||||||
|
if (action.type === 'UPDATE') {
|
||||||
|
action.removeRooms.forEach((roomId) => mutedRooms.delete(roomId));
|
||||||
|
action.addRooms.forEach((roomId) => mutedRooms.add(roomId));
|
||||||
|
set(baseMutedRoomsAtom, mutedRooms);
|
||||||
|
set(muteChangesAtom, {
|
||||||
|
added: [...action.addRooms],
|
||||||
|
removed: [...action.removeRooms],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useBindMutedRoomsAtom = (
|
||||||
|
mx: MatrixClient,
|
||||||
|
mutedAtom: WritableAtom<Set<string>, MutedRoomsUpdate>
|
||||||
|
) => {
|
||||||
|
const setMuted = useSetAtom(mutedAtom);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const overrideRules = mx.getAccountData('m.push_rules')?.getContent<IPushRules>()
|
||||||
|
?.global?.override;
|
||||||
|
if (overrideRules) {
|
||||||
|
const mutedRooms = overrideRules.reduce<string[]>((rooms, rule) => {
|
||||||
|
if (isMutedRule(rule)) rooms.push(rule.rule_id);
|
||||||
|
return rooms;
|
||||||
|
}, []);
|
||||||
|
setMuted({
|
||||||
|
type: 'INITIALIZE',
|
||||||
|
addRooms: mutedRooms,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [mx, setMuted]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handlePushRules = (mEvent: MatrixEvent, oldMEvent?: MatrixEvent) => {
|
||||||
|
if (mEvent.getType() === 'm.push_rules') {
|
||||||
|
const override = mEvent?.getContent()?.global?.override as IPushRule[] | undefined;
|
||||||
|
const oldOverride = oldMEvent?.getContent()?.global?.override as IPushRule[] | undefined;
|
||||||
|
if (!override || !oldOverride) return;
|
||||||
|
|
||||||
|
const isMuteToggled = (rule: IPushRule, otherOverride: IPushRule[]) => {
|
||||||
|
const roomId = rule.rule_id;
|
||||||
|
|
||||||
|
const isMuted = isMutedRule(rule);
|
||||||
|
if (!isMuted) return false;
|
||||||
|
const isOtherMuted = findMutedRule(otherOverride, roomId);
|
||||||
|
if (isOtherMuted) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mutedRules = override.filter((rule) => isMuteToggled(rule, oldOverride));
|
||||||
|
const unMutedRules = oldOverride.filter((rule) => isMuteToggled(rule, override));
|
||||||
|
|
||||||
|
setMuted({
|
||||||
|
type: 'UPDATE',
|
||||||
|
addRooms: mutedRules.map((rule) => rule.rule_id),
|
||||||
|
removeRooms: unMutedRules.map((rule) => rule.rule_id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mx.on(ClientEvent.AccountData, handlePushRules);
|
||||||
|
return () => {
|
||||||
|
mx.removeListener(ClientEvent.AccountData, handlePushRules);
|
||||||
|
};
|
||||||
|
}, [mx, setMuted]);
|
||||||
|
};
|
|
@ -1,6 +1,19 @@
|
||||||
import { MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk';
|
import {
|
||||||
|
IPushRule,
|
||||||
|
IPushRules,
|
||||||
|
MatrixClient,
|
||||||
|
MatrixEvent,
|
||||||
|
NotificationCountType,
|
||||||
|
Room,
|
||||||
|
} from 'matrix-js-sdk';
|
||||||
import { AccountDataEvent } from '../../types/matrix/accountData';
|
import { AccountDataEvent } from '../../types/matrix/accountData';
|
||||||
import { RoomToParents, RoomType, StateEvent } from '../../types/matrix/room';
|
import {
|
||||||
|
NotificationType,
|
||||||
|
RoomToParents,
|
||||||
|
RoomType,
|
||||||
|
StateEvent,
|
||||||
|
UnreadInfo,
|
||||||
|
} from '../../types/matrix/room';
|
||||||
|
|
||||||
export const getStateEvent = (
|
export const getStateEvent = (
|
||||||
room: Room,
|
room: Room,
|
||||||
|
@ -116,3 +129,89 @@ export const getRoomToParents = (mx: MatrixClient): RoomToParents => {
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isMutedRule = (rule: IPushRule) =>
|
||||||
|
rule.actions[0] === 'dont_notify' && rule.conditions?.[0]?.kind === 'event_match';
|
||||||
|
|
||||||
|
export const findMutedRule = (overrideRules: IPushRule[], roomId: string) =>
|
||||||
|
overrideRules.find((rule) => rule.rule_id === roomId && isMutedRule(rule));
|
||||||
|
|
||||||
|
export const getNotificationType = (mx: MatrixClient, roomId: string): NotificationType => {
|
||||||
|
let roomPushRule: IPushRule | undefined;
|
||||||
|
try {
|
||||||
|
roomPushRule = mx.getRoomPushRule('global', roomId);
|
||||||
|
} catch {
|
||||||
|
roomPushRule = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!roomPushRule) {
|
||||||
|
const overrideRules = mx.getAccountData('m.push_rules')?.getContent<IPushRules>()
|
||||||
|
?.global?.override;
|
||||||
|
if (!overrideRules) return NotificationType.Default;
|
||||||
|
|
||||||
|
return findMutedRule(overrideRules, roomId) ? NotificationType.Mute : NotificationType.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roomPushRule.actions[0] === 'notify') return NotificationType.AllMessages;
|
||||||
|
return NotificationType.MentionsAndKeywords;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isNotificationEvent = (mEvent: MatrixEvent) => {
|
||||||
|
const eType = mEvent.getType();
|
||||||
|
if (
|
||||||
|
['m.room.create', 'm.room.message', 'm.room.encrypted', 'm.room.member', 'm.sticker'].find(
|
||||||
|
(type) => type === eType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
if (eType === 'm.room.member') return false;
|
||||||
|
|
||||||
|
if (mEvent.isRedacted()) return false;
|
||||||
|
if (mEvent.getRelation()?.rel_type === 'm.replace') return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const roomHaveUnread = (mx: MatrixClient, room: Room) => {
|
||||||
|
const userId = mx.getUserId();
|
||||||
|
if (!userId) return false;
|
||||||
|
const readUpToId = room.getEventReadUpTo(userId);
|
||||||
|
const liveEvents = room.getLiveTimeline().getEvents();
|
||||||
|
|
||||||
|
if (liveEvents[liveEvents.length - 1]?.getSender() === userId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = liveEvents.length - 1; i >= 0; i -= 1) {
|
||||||
|
const event = liveEvents[i];
|
||||||
|
if (!event) return false;
|
||||||
|
if (event.getId() === readUpToId) return false;
|
||||||
|
if (isNotificationEvent(event)) return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUnreadInfo = (room: Room): UnreadInfo => {
|
||||||
|
const total = room.getUnreadNotificationCount(NotificationCountType.Total);
|
||||||
|
const highlight = room.getUnreadNotificationCount(NotificationCountType.Highlight);
|
||||||
|
return {
|
||||||
|
roomId: room.roomId,
|
||||||
|
highlight,
|
||||||
|
total: highlight > total ? highlight : total,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUnreadInfos = (mx: MatrixClient): UnreadInfo[] => {
|
||||||
|
const unreadInfos = mx.getRooms().reduce<UnreadInfo[]>((unread, room) => {
|
||||||
|
if (room.isSpaceRoom()) return unread;
|
||||||
|
if (room.getMyMembership() !== 'join') return unread;
|
||||||
|
if (getNotificationType(mx, room.roomId) === NotificationType.Mute) return unread;
|
||||||
|
|
||||||
|
if (roomHaveUnread(mx, room)) {
|
||||||
|
unread.push(getUnreadInfo(room));
|
||||||
|
}
|
||||||
|
|
||||||
|
return unread;
|
||||||
|
}, []);
|
||||||
|
return unreadInfos;
|
||||||
|
};
|
||||||
|
|
|
@ -31,4 +31,29 @@ export enum RoomType {
|
||||||
Space = 'm.space',
|
Space = 'm.space',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum NotificationType {
|
||||||
|
Default = 'default',
|
||||||
|
AllMessages = 'all_messages',
|
||||||
|
MentionsAndKeywords = 'mentions_and_keywords',
|
||||||
|
Mute = 'mute',
|
||||||
|
}
|
||||||
|
|
||||||
export type RoomToParents = Map<string, Set<string>>;
|
export type RoomToParents = Map<string, Set<string>>;
|
||||||
|
export type RoomToUnread = Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
total: number;
|
||||||
|
highlight: number;
|
||||||
|
from: Set<string> | null;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
export type UnreadInfo = {
|
||||||
|
roomId: string;
|
||||||
|
total: number;
|
||||||
|
highlight: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MuteChanges = {
|
||||||
|
added: string[];
|
||||||
|
removed: string[];
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue