diff --git a/src/RoomNotifs.js b/src/RoomNotifs.ts similarity index 66% rename from src/RoomNotifs.js rename to src/RoomNotifs.ts index 5d109094af..5abee9a6ad 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.ts @@ -17,27 +17,31 @@ limitations under the License. import { MatrixClientPeg } from './MatrixClientPeg'; import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor'; +import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room"; +import { IAnnotatedPushRule, PushRuleKind } from "matrix-js-sdk/src/@types/PushRules"; -export const ALL_MESSAGES_LOUD = 'all_messages_loud'; -export const ALL_MESSAGES = 'all_messages'; -export const MENTIONS_ONLY = 'mentions_only'; -export const MUTE = 'mute'; +export enum RoomNotifState { + AllMessagesLoud = 'all_messages_loud', + AllMessages = 'all_messages', + MentionsOnly = 'mentions_only', + Mute = 'mute', +} -export const BADGE_STATES = [ALL_MESSAGES, ALL_MESSAGES_LOUD]; -export const MENTION_BADGE_STATES = [...BADGE_STATES, MENTIONS_ONLY]; +export const BADGE_STATES = [RoomNotifState.AllMessages, RoomNotifState.AllMessagesLoud]; +export const MENTION_BADGE_STATES = [...BADGE_STATES, RoomNotifState.MentionsOnly]; -export function shouldShowNotifBadge(roomNotifState) { +export function shouldShowNotifBadge(roomNotifState: RoomNotifState): boolean { return BADGE_STATES.includes(roomNotifState); } -export function shouldShowMentionBadge(roomNotifState) { +export function shouldShowMentionBadge(roomNotifState: RoomNotifState): boolean { return MENTION_BADGE_STATES.includes(roomNotifState); } -export function aggregateNotificationCount(rooms) { - return rooms.reduce((result, room) => { +export function aggregateNotificationCount(rooms: Room[]): {count: number, highlight: boolean} { + return rooms.reduce<{count: number, highlight: boolean}>((result, room) => { const roomNotifState = getRoomNotifsState(room.roomId); - const highlight = room.getUnreadNotificationCount('highlight') > 0; + const highlight = room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0; // use helper method to include highlights in the previous version of the room const notificationCount = getUnreadNotificationCount(room); @@ -55,9 +59,9 @@ export function aggregateNotificationCount(rooms) { }, { count: 0, highlight: false }); } -export function getRoomHasBadge(room) { +export function getRoomHasBadge(room: Room): boolean { const roomNotifState = getRoomNotifsState(room.roomId); - const highlight = room.getUnreadNotificationCount('highlight') > 0; + const highlight = room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0; const notificationCount = room.getUnreadNotificationCount(); const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState); @@ -66,14 +70,14 @@ export function getRoomHasBadge(room) { return notifBadges || mentionBadges; } -export function getRoomNotifsState(roomId) { - if (MatrixClientPeg.get().isGuest()) return ALL_MESSAGES; +export function getRoomNotifsState(roomId: string): RoomNotifState { + if (MatrixClientPeg.get().isGuest()) return RoomNotifState.AllMessages; // look through the override rules for a rule affecting this room: // if one exists, it will take precedence. const muteRule = findOverrideMuteRule(roomId); if (muteRule) { - return MUTE; + return RoomNotifState.Mute; } // for everything else, look at the room rule. @@ -89,27 +93,27 @@ export function getRoomNotifsState(roomId) { // XXX: We have to assume the default is to notify for all messages // (in particular this will be 'wrong' for one to one rooms because // they will notify loudly for all messages) - if (!roomRule || !roomRule.enabled) return ALL_MESSAGES; + if (!roomRule || !roomRule.enabled) return RoomNotifState.AllMessages; // a mute at the room level will still allow mentions // to notify - if (isMuteRule(roomRule)) return MENTIONS_ONLY; + if (isMuteRule(roomRule)) return RoomNotifState.MentionsOnly; const actionsObject = PushProcessor.actionListToActionsObject(roomRule.actions); - if (actionsObject.tweaks.sound) return ALL_MESSAGES_LOUD; + if (actionsObject.tweaks.sound) return RoomNotifState.AllMessagesLoud; return null; } -export function setRoomNotifsState(roomId, newState) { - if (newState === MUTE) { +export function setRoomNotifsState(roomId: string, newState: RoomNotifState): Promise { + if (newState === RoomNotifState.Mute) { return setRoomNotifsStateMuted(roomId); } else { return setRoomNotifsStateUnmuted(roomId, newState); } } -export function getUnreadNotificationCount(room, type=null) { +export function getUnreadNotificationCount(room: Room, type: NotificationCountType = null): number { let notificationCount = room.getUnreadNotificationCount(type); // Check notification counts in the old room just in case there's some lost @@ -124,21 +128,21 @@ export function getUnreadNotificationCount(room, type=null) { // notifying the user for unread messages because they would have extreme // difficulty changing their notification preferences away from "All Messages" // and "Noisy". - notificationCount += oldRoom.getUnreadNotificationCount("highlight"); + notificationCount += oldRoom.getUnreadNotificationCount(NotificationCountType.Highlight); } } return notificationCount; } -function setRoomNotifsStateMuted(roomId) { +function setRoomNotifsStateMuted(roomId: string): Promise { const cli = MatrixClientPeg.get(); const promises = []; // delete the room rule const roomRule = cli.getRoomPushRule('global', roomId); if (roomRule) { - promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + promises.push(cli.deletePushRule('global', PushRuleKind.RoomSpecific, roomRule.rule_id)); } // add/replace an override rule to squelch everything in this room @@ -146,7 +150,7 @@ function setRoomNotifsStateMuted(roomId) { // is an override rule, not a room rule: it still pertains to this room // though, so using the room ID as the rule ID is logical and prevents // duplicate copies of the rule. - promises.push(cli.addPushRule('global', 'override', roomId, { + promises.push(cli.addPushRule('global', PushRuleKind.Override, roomId, { conditions: [ { kind: 'event_match', @@ -162,30 +166,30 @@ function setRoomNotifsStateMuted(roomId) { return Promise.all(promises); } -function setRoomNotifsStateUnmuted(roomId, newState) { +function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise { const cli = MatrixClientPeg.get(); const promises = []; const overrideMuteRule = findOverrideMuteRule(roomId); if (overrideMuteRule) { - promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id)); + promises.push(cli.deletePushRule('global', PushRuleKind.Override, overrideMuteRule.rule_id)); } - if (newState === 'all_messages') { + if (newState === RoomNotifState.AllMessages) { const roomRule = cli.getRoomPushRule('global', roomId); if (roomRule) { - promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + promises.push(cli.deletePushRule('global', PushRuleKind.RoomSpecific, roomRule.rule_id)); } - } else if (newState === 'mentions_only') { - promises.push(cli.addPushRule('global', 'room', roomId, { + } else if (newState === RoomNotifState.MentionsOnly) { + promises.push(cli.addPushRule('global', PushRuleKind.RoomSpecific, roomId, { actions: [ 'dont_notify', ], })); // https://matrix.org/jira/browse/SPEC-400 - promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); - } else if ('all_messages_loud') { - promises.push(cli.addPushRule('global', 'room', roomId, { + promises.push(cli.setPushRuleEnabled('global', PushRuleKind.RoomSpecific, roomId, true)); + } else if (newState === RoomNotifState.AllMessagesLoud) { + promises.push(cli.addPushRule('global', PushRuleKind.RoomSpecific, roomId, { actions: [ 'notify', { @@ -195,13 +199,13 @@ function setRoomNotifsStateUnmuted(roomId, newState) { ], })); // https://matrix.org/jira/browse/SPEC-400 - promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); + promises.push(cli.setPushRuleEnabled('global', PushRuleKind.RoomSpecific, roomId, true)); } return Promise.all(promises); } -function findOverrideMuteRule(roomId) { +function findOverrideMuteRule(roomId: string): IAnnotatedPushRule { const cli = MatrixClientPeg.get(); if (!cli.pushRules || !cli.pushRules['global'] || @@ -218,7 +222,7 @@ function findOverrideMuteRule(roomId) { return null; } -function isRuleForRoom(roomId, rule) { +function isRuleForRoom(roomId: string, rule: IAnnotatedPushRule): boolean { if (rule.conditions.length !== 1) { return false; } @@ -226,6 +230,6 @@ function isRuleForRoom(roomId, rule) { return (cond.kind === 'event_match' && cond.key === 'room_id' && cond.pattern === roomId); } -function isMuteRule(rule) { +function isMuteRule(rule: IAnnotatedPushRule): boolean { return (rule.actions.length === 1 && rule.actions[0] === 'dont_notify'); } diff --git a/src/RoomNotifsTypes.ts b/src/RoomNotifsTypes.ts deleted file mode 100644 index 0e7093e434..0000000000 --- a/src/RoomNotifsTypes.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { - ALL_MESSAGES, - ALL_MESSAGES_LOUD, - MENTIONS_ONLY, - MUTE, -} from "./RoomNotifs"; - -export type Volume = ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE; diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 970915d653..dbefcbd333 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -29,10 +29,9 @@ import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextM import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; -import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs"; +import { RoomNotifState } from "../../../RoomNotifs"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import NotificationBadge from "./NotificationBadge"; -import { Volume } from "../../../RoomNotifsTypes"; import RoomListStore from "../../../stores/room-list/RoomListStore"; import RoomListActions from "../../../actions/RoomListActions"; import { ActionPayload } from "../../../dispatcher/payloads"; @@ -364,7 +363,7 @@ export default class RoomTile extends React.PureComponent { this.setState({ generalMenuPosition: null }); // hide the menu }; - private async saveNotifState(ev: ButtonEvent, newState: Volume) { + private async saveNotifState(ev: ButtonEvent, newState: RoomNotifState) { ev.preventDefault(); ev.stopPropagation(); if (MatrixClientPeg.get().isGuest()) return; @@ -378,10 +377,10 @@ export default class RoomTile extends React.PureComponent { } } - private onClickAllNotifs = ev => this.saveNotifState(ev, ALL_MESSAGES); - private onClickAlertMe = ev => this.saveNotifState(ev, ALL_MESSAGES_LOUD); - private onClickMentions = ev => this.saveNotifState(ev, MENTIONS_ONLY); - private onClickMute = ev => this.saveNotifState(ev, MUTE); + private onClickAllNotifs = ev => this.saveNotifState(ev, RoomNotifState.AllMessages); + private onClickAlertMe = ev => this.saveNotifState(ev, RoomNotifState.AllMessagesLoud); + private onClickMentions = ev => this.saveNotifState(ev, RoomNotifState.MentionsOnly); + private onClickMute = ev => this.saveNotifState(ev, RoomNotifState.Mute); private renderNotificationsMenu(isActive: boolean): React.ReactElement { if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived || @@ -404,25 +403,25 @@ export default class RoomTile extends React.PureComponent { @@ -432,14 +431,14 @@ export default class RoomTile extends React.PureComponent { const classes = classNames("mx_RoomTile_notificationsButton", { // Show bell icon for the default case too. - mx_RoomTile_iconBell: state === ALL_MESSAGES, - mx_RoomTile_iconBellDot: state === ALL_MESSAGES_LOUD, - mx_RoomTile_iconBellMentions: state === MENTIONS_ONLY, - mx_RoomTile_iconBellCrossed: state === MUTE, + mx_RoomTile_iconBell: state === RoomNotifState.AllMessages, + mx_RoomTile_iconBellDot: state === RoomNotifState.AllMessagesLoud, + mx_RoomTile_iconBellMentions: state === RoomNotifState.MentionsOnly, + mx_RoomTile_iconBellCrossed: state === RoomNotifState.Mute, // Only show the icon by default if the room is overridden to muted. // TODO: [FTUE Notifications] Probably need to detect global mute state - mx_RoomTile_notificationsButton_show: state === MUTE, + mx_RoomTile_notificationsButton_show: state === RoomNotifState.Mute, }); return ( diff --git a/src/stores/local-echo/RoomEchoChamber.ts b/src/stores/local-echo/RoomEchoChamber.ts index e113f68c32..fb40e23a85 100644 --- a/src/stores/local-echo/RoomEchoChamber.ts +++ b/src/stores/local-echo/RoomEchoChamber.ts @@ -15,20 +15,17 @@ limitations under the License. */ import { GenericEchoChamber, implicitlyReverted, PROPERTY_UPDATED } from "./GenericEchoChamber"; -import { getRoomNotifsState, setRoomNotifsState } from "../../RoomNotifs"; +import { getRoomNotifsState, RoomNotifState, setRoomNotifsState } from "../../RoomNotifs"; import { RoomEchoContext } from "./RoomEchoContext"; import { _t } from "../../languageHandler"; -import { Volume } from "../../RoomNotifsTypes"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -export type CachedRoomValues = Volume; - export enum CachedRoomKey { NotificationVolume, } -export class RoomEchoChamber extends GenericEchoChamber { - private properties = new Map(); +export class RoomEchoChamber extends GenericEchoChamber { + private properties = new Map(); public constructor(context: RoomEchoContext) { super(context, (k) => this.properties.get(k)); @@ -50,8 +47,8 @@ export class RoomEchoChamber extends GenericEchoChamber { if (event.getType() === "m.push_rules") { - const currentVolume = this.properties.get(CachedRoomKey.NotificationVolume) as Volume; - const newVolume = getRoomNotifsState(this.context.room.roomId) as Volume; + const currentVolume = this.properties.get(CachedRoomKey.NotificationVolume) as RoomNotifState; + const newVolume = getRoomNotifsState(this.context.room.roomId) as RoomNotifState; if (currentVolume !== newVolume) { this.updateNotificationVolume(); } @@ -66,11 +63,11 @@ export class RoomEchoChamber extends GenericEchoChamber { return setRoomNotifsState(this.context.room.roomId, v); }, implicitlyReverted); diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts index d0479200bd..3ad0080183 100644 --- a/src/stores/notifications/RoomNotificationState.ts +++ b/src/stores/notifications/RoomNotificationState.ts @@ -20,7 +20,7 @@ import { MatrixClientPeg } from "../../MatrixClientPeg"; import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership"; import { readReceiptChangeIsFor } from "../../utils/read-receipts"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { Room } from "matrix-js-sdk/src/models/room"; +import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room"; import * as RoomNotifs from '../../RoomNotifs'; import * as Unread from '../../Unread'; import { NotificationState } from "./NotificationState"; @@ -91,7 +91,7 @@ export class RoomNotificationState extends NotificationState implements IDestroy this._color = NotificationColor.Unsent; this._symbol = "!"; this._count = 1; // not used, technically - } else if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.MUTE) { + } else if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.RoomNotifState.Mute) { // When muted we suppress all notification states, even if we have context on them. this._color = NotificationColor.None; this._symbol = null; @@ -101,8 +101,8 @@ export class RoomNotificationState extends NotificationState implements IDestroy this._symbol = "!"; this._count = 1; // not used, technically } else { - const redNotifs = RoomNotifs.getUnreadNotificationCount(this.room, 'highlight'); - const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.room, 'total'); + const redNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Highlight); + const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Total); // For a 'true count' we pick the grey notifications first because they include the // red notifications. If we don't have a grey count for some reason we use the red