Hook threads notification state to UI (#7298)
This commit is contained in:
parent
55eda7314b
commit
ce570ab827
6 changed files with 139 additions and 28 deletions
|
@ -24,6 +24,24 @@ $left-gutter: 64px;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&[data-shape=thread_list][data-notification]::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
right: -16px;
|
||||||
|
top: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-shape=thread_list][data-notification=total]::before {
|
||||||
|
background-color: $roomtile-default-badge-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-shape=thread_list][data-notification=highlight]::before {
|
||||||
|
background-color: $alert;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo {
|
.mx_ThreadInfo {
|
||||||
margin-right: 110px;
|
margin-right: 110px;
|
||||||
margin-left: 64px;
|
margin-left: 64px;
|
||||||
|
|
|
@ -30,6 +30,7 @@ import {
|
||||||
} from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
|
} from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
|
||||||
import type { EventSubscription } from "fbemitter";
|
import type { EventSubscription } from "fbemitter";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { NotificationColor } from '../../../stores/notifications/NotificationColor';
|
||||||
|
|
||||||
export enum HeaderKind {
|
export enum HeaderKind {
|
||||||
Room = "room",
|
Room = "room",
|
||||||
|
@ -39,6 +40,7 @@ export enum HeaderKind {
|
||||||
interface IState {
|
interface IState {
|
||||||
headerKind: HeaderKind;
|
headerKind: HeaderKind;
|
||||||
phase: RightPanelPhases;
|
phase: RightPanelPhases;
|
||||||
|
threadNotificationColor: NotificationColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
@ -54,6 +56,7 @@ export default abstract class HeaderButtons<P = {}> extends React.Component<IPro
|
||||||
const rps = RightPanelStore.getSharedInstance();
|
const rps = RightPanelStore.getSharedInstance();
|
||||||
this.state = {
|
this.state = {
|
||||||
headerKind: kind,
|
headerKind: kind,
|
||||||
|
threadNotificationColor: NotificationColor.None,
|
||||||
phase: kind === HeaderKind.Room ? rps.visibleRoomPanelPhase : rps.visibleGroupPanelPhase,
|
phase: kind === HeaderKind.Room ? rps.visibleRoomPanelPhase : rps.visibleGroupPanelPhase,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -36,6 +37,8 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||||
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
||||||
|
import { ThreadsRoomNotificationState } from "../../../stores/notifications/ThreadsRoomNotificationState";
|
||||||
|
import { NotificationStateEvents } from "../../../stores/notifications/NotificationState";
|
||||||
|
|
||||||
const ROOM_INFO_PHASES = [
|
const ROOM_INFO_PHASES = [
|
||||||
RightPanelPhases.RoomSummary,
|
RightPanelPhases.RoomSummary,
|
||||||
|
@ -48,14 +51,22 @@ const ROOM_INFO_PHASES = [
|
||||||
];
|
];
|
||||||
|
|
||||||
interface IUnreadIndicatorProps {
|
interface IUnreadIndicatorProps {
|
||||||
className: string;
|
color?: NotificationColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UnreadIndicator = ({ className }: IUnreadIndicatorProps) => {
|
const UnreadIndicator = ({ color }: IUnreadIndicatorProps) => {
|
||||||
return <React.Fragment>
|
if (color === NotificationColor.None) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = classNames({
|
||||||
|
"mx_RightPanel_headerButton_unreadIndicator": true,
|
||||||
|
"mx_Indicator_gray": color === NotificationColor.Grey,
|
||||||
|
});
|
||||||
|
return <>
|
||||||
<div className="mx_RightPanel_headerButton_unreadIndicator_bg" />
|
<div className="mx_RightPanel_headerButton_unreadIndicator_bg" />
|
||||||
<div className={className} />
|
<div className={classes} />
|
||||||
</React.Fragment>;
|
</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IHeaderButtonProps {
|
interface IHeaderButtonProps {
|
||||||
|
@ -72,7 +83,7 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }: IHeaderBut
|
||||||
|
|
||||||
let unreadIndicator;
|
let unreadIndicator;
|
||||||
if (pinnedEvents.some(id => !readPinnedEvents.has(id))) {
|
if (pinnedEvents.some(id => !readPinnedEvents.has(id))) {
|
||||||
unreadIndicator = <UnreadIndicator className="mx_RightPanel_headerButton_unreadIndicator" />;
|
unreadIndicator = <UnreadIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <HeaderButton
|
return <HeaderButton
|
||||||
|
@ -89,17 +100,11 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }: IHeaderBut
|
||||||
const TimelineCardHeaderButton = ({ room, isHighlighted, onClick }: IHeaderButtonProps) => {
|
const TimelineCardHeaderButton = ({ room, isHighlighted, onClick }: IHeaderButtonProps) => {
|
||||||
if (!SettingsStore.getValue("feature_maximised_widgets")) return null;
|
if (!SettingsStore.getValue("feature_maximised_widgets")) return null;
|
||||||
let unreadIndicator;
|
let unreadIndicator;
|
||||||
switch (RoomNotificationStateStore.instance.getRoomState(room).color) {
|
const color = RoomNotificationStateStore.instance.getRoomState(room).color;
|
||||||
|
switch (color) {
|
||||||
case NotificationColor.Grey:
|
case NotificationColor.Grey:
|
||||||
unreadIndicator =
|
|
||||||
<UnreadIndicator className="mx_RightPanel_headerButton_unreadIndicator mx_Indicator_gray" />;
|
|
||||||
break;
|
|
||||||
case NotificationColor.Red:
|
case NotificationColor.Red:
|
||||||
unreadIndicator =
|
unreadIndicator = <UnreadIndicator color={color} />;
|
||||||
<UnreadIndicator className="mx_RightPanel_headerButton_unreadIndicator" />;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return <HeaderButton
|
return <HeaderButton
|
||||||
name="timelineCardButton"
|
name="timelineCardButton"
|
||||||
|
@ -123,11 +128,30 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
||||||
RightPanelPhases.ThreadPanel,
|
RightPanelPhases.ThreadPanel,
|
||||||
RightPanelPhases.ThreadView,
|
RightPanelPhases.ThreadView,
|
||||||
];
|
];
|
||||||
|
private threadNotificationState: ThreadsRoomNotificationState;
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props, HeaderKind.Room);
|
super(props, HeaderKind.Room);
|
||||||
|
|
||||||
|
this.threadNotificationState = RoomNotificationStateStore.instance.getThreadsRoomState(this.props.room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public componentDidMount(): void {
|
||||||
|
super.componentDidMount();
|
||||||
|
this.threadNotificationState.on(NotificationStateEvents.Update, this.onThreadNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount(): void {
|
||||||
|
super.componentWillUnmount();
|
||||||
|
this.threadNotificationState.off(NotificationStateEvents.Update, this.onThreadNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onThreadNotification = (): void => {
|
||||||
|
this.setState({
|
||||||
|
threadNotificationColor: this.threadNotificationState.color,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
protected onAction(payload: ActionPayload) {
|
protected onAction(payload: ActionPayload) {
|
||||||
if (payload.action === Action.ViewUser) {
|
if (payload.action === Action.ViewUser) {
|
||||||
if (payload.member) {
|
if (payload.member) {
|
||||||
|
@ -188,12 +212,14 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
||||||
|
|
||||||
rightPanelPhaseButtons.set(RightPanelPhases.PinnedMessages,
|
rightPanelPhaseButtons.set(RightPanelPhases.PinnedMessages,
|
||||||
<PinnedMessagesHeaderButton
|
<PinnedMessagesHeaderButton
|
||||||
|
key="pinnedMessagesButton"
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
isHighlighted={this.isPhase(RightPanelPhases.PinnedMessages)}
|
isHighlighted={this.isPhase(RightPanelPhases.PinnedMessages)}
|
||||||
onClick={this.onPinnedMessagesClicked} />,
|
onClick={this.onPinnedMessagesClicked} />,
|
||||||
);
|
);
|
||||||
rightPanelPhaseButtons.set(RightPanelPhases.Timeline,
|
rightPanelPhaseButtons.set(RightPanelPhases.Timeline,
|
||||||
<TimelineCardHeaderButton
|
<TimelineCardHeaderButton
|
||||||
|
key="timelineButton"
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
isHighlighted={this.isPhase(RightPanelPhases.Timeline)}
|
isHighlighted={this.isPhase(RightPanelPhases.Timeline)}
|
||||||
onClick={this.onTimelineCardClicked} />,
|
onClick={this.onTimelineCardClicked} />,
|
||||||
|
@ -205,11 +231,14 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
||||||
title={_t("Threads")}
|
title={_t("Threads")}
|
||||||
onClick={this.onThreadsPanelClicked}
|
onClick={this.onThreadsPanelClicked}
|
||||||
isHighlighted={this.isPhase(RoomHeaderButtons.THREAD_PHASES)}
|
isHighlighted={this.isPhase(RoomHeaderButtons.THREAD_PHASES)}
|
||||||
analytics={['Right Panel', 'Threads List Button', 'click']} />
|
analytics={['Right Panel', 'Threads List Button', 'click']}>
|
||||||
|
<UnreadIndicator color={this.threadNotificationState.color} />
|
||||||
|
</HeaderButton>
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
rightPanelPhaseButtons.set(RightPanelPhases.NotificationPanel,
|
rightPanelPhaseButtons.set(RightPanelPhases.NotificationPanel,
|
||||||
<HeaderButton
|
<HeaderButton
|
||||||
|
key="notifsButton"
|
||||||
name="notifsButton"
|
name="notifsButton"
|
||||||
title={_t('Notifications')}
|
title={_t('Notifications')}
|
||||||
isHighlighted={this.isPhase(RightPanelPhases.NotificationPanel)}
|
isHighlighted={this.isPhase(RightPanelPhases.NotificationPanel)}
|
||||||
|
@ -218,6 +247,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
||||||
);
|
);
|
||||||
rightPanelPhaseButtons.set(RightPanelPhases.RoomSummary,
|
rightPanelPhaseButtons.set(RightPanelPhases.RoomSummary,
|
||||||
<HeaderButton
|
<HeaderButton
|
||||||
|
key="roomSummaryButton"
|
||||||
name="roomSummaryButton"
|
name="roomSummaryButton"
|
||||||
title={_t('Room Info')}
|
title={_t('Room Info')}
|
||||||
isHighlighted={this.isPhase(ROOM_INFO_PHASES)}
|
isHighlighted={this.isPhase(ROOM_INFO_PHASES)}
|
||||||
|
|
|
@ -23,6 +23,7 @@ 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";
|
||||||
import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
|
import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { NotificationCountType } from 'matrix-js-sdk/src/models/room';
|
||||||
|
|
||||||
import ReplyChain from "../elements/ReplyChain";
|
import ReplyChain from "../elements/ReplyChain";
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -67,6 +68,10 @@ import Toolbar from '../../../accessibility/Toolbar';
|
||||||
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
|
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
|
||||||
import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton';
|
import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton';
|
||||||
import ThreadListContextMenu from '../context_menus/ThreadListContextMenu';
|
import ThreadListContextMenu from '../context_menus/ThreadListContextMenu';
|
||||||
|
import { ThreadNotificationState } from '../../../stores/notifications/ThreadNotificationState';
|
||||||
|
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
|
||||||
|
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
|
||||||
|
import { NotificationColor } from '../../../stores/notifications/NotificationColor';
|
||||||
|
|
||||||
const eventTileTypes = {
|
const eventTileTypes = {
|
||||||
[EventType.RoomMessage]: 'messages.MessageEvent',
|
[EventType.RoomMessage]: 'messages.MessageEvent',
|
||||||
|
@ -346,6 +351,7 @@ interface IState {
|
||||||
hover: boolean;
|
hover: boolean;
|
||||||
isQuoteExpanded?: boolean;
|
isQuoteExpanded?: boolean;
|
||||||
thread?: Thread;
|
thread?: Thread;
|
||||||
|
threadNotification?: NotificationCountType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.rooms.EventTile")
|
@replaceableComponent("views.rooms.EventTile")
|
||||||
|
@ -355,6 +361,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
// TODO: Types
|
// TODO: Types
|
||||||
private tile = React.createRef<unknown>();
|
private tile = React.createRef<unknown>();
|
||||||
private replyChain = React.createRef<ReplyChain>();
|
private replyChain = React.createRef<ReplyChain>();
|
||||||
|
private threadState: ThreadNotificationState;
|
||||||
|
|
||||||
public readonly ref = createRef<HTMLElement>();
|
public readonly ref = createRef<HTMLElement>();
|
||||||
|
|
||||||
|
@ -492,17 +499,55 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
if (SettingsStore.getValue("feature_thread")) {
|
if (SettingsStore.getValue("feature_thread")) {
|
||||||
this.props.mxEvent.once(ThreadEvent.Ready, this.updateThread);
|
this.props.mxEvent.once(ThreadEvent.Ready, this.updateThread);
|
||||||
this.props.mxEvent.on(ThreadEvent.Update, this.updateThread);
|
this.props.mxEvent.on(ThreadEvent.Update, this.updateThread);
|
||||||
|
|
||||||
|
if (this.thread) {
|
||||||
|
this.setupNotificationListener(this.thread);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||||
room?.on(ThreadEvent.New, this.onNewThread);
|
room?.on(ThreadEvent.New, this.onNewThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateThread = (thread) => {
|
private setupNotificationListener = (thread): void => {
|
||||||
|
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
const notifications = RoomNotificationStateStore.instance.getThreadsRoomState(room);
|
||||||
|
|
||||||
|
this.threadState = notifications.threadsState.get(thread);
|
||||||
|
|
||||||
|
this.threadState.on(NotificationStateEvents.Update, this.onThreadStateUpdate);
|
||||||
|
this.onThreadStateUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
private onThreadStateUpdate = (): void => {
|
||||||
|
let threadNotification = null;
|
||||||
|
switch (this.threadState?.color) {
|
||||||
|
case NotificationColor.Grey:
|
||||||
|
threadNotification = NotificationCountType.Total;
|
||||||
|
break;
|
||||||
|
case NotificationColor.Red:
|
||||||
|
threadNotification = NotificationCountType.Highlight;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
thread,
|
threadNotification,
|
||||||
});
|
});
|
||||||
this.forceUpdate();
|
};
|
||||||
|
|
||||||
|
private updateThread = (thread) => {
|
||||||
|
if (thread !== this.state.thread) {
|
||||||
|
if (this.threadState) {
|
||||||
|
this.threadState.off(NotificationStateEvents.Update, this.onThreadStateUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setupNotificationListener(thread);
|
||||||
|
this.setState({
|
||||||
|
thread,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
|
@ -540,6 +585,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||||
room?.off(ThreadEvent.New, this.onNewThread);
|
room?.off(ThreadEvent.New, this.onNewThread);
|
||||||
|
if (this.threadState) {
|
||||||
|
this.threadState.off(NotificationStateEvents.Update, this.onThreadStateUpdate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
@ -1358,6 +1406,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
"data-shape": this.props.tileShape,
|
"data-shape": this.props.tileShape,
|
||||||
"data-self": isOwnEvent,
|
"data-self": isOwnEvent,
|
||||||
"data-has-reply": !!replyChain,
|
"data-has-reply": !!replyChain,
|
||||||
|
"data-notification": this.state.threadNotification,
|
||||||
"onMouseEnter": () => this.setState({ hover: true }),
|
"onMouseEnter": () => this.setState({ hover: true }),
|
||||||
"onMouseLeave": () => this.setState({ hover: false }),
|
"onMouseLeave": () => this.setState({ hover: false }),
|
||||||
|
|
||||||
|
|
|
@ -40,20 +40,26 @@ export class ThreadNotificationState extends NotificationState implements IDestr
|
||||||
this.thread.off(ThreadEvent.ViewThread, this.resetThreadNotification);
|
this.thread.off(ThreadEvent.ViewThread, this.resetThreadNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleNewThreadReply(thread: Thread, event: MatrixEvent) {
|
private handleNewThreadReply = (thread: Thread, event: MatrixEvent) => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
const isOwn = client.getUserId() === event.getSender();
|
const myUserId = client.getUserId();
|
||||||
if (!isOwn) {
|
|
||||||
|
const isOwn = myUserId === event.getSender();
|
||||||
|
const readReceipt = this.room.getReadReceiptForUserId(myUserId);
|
||||||
|
|
||||||
|
if (!isOwn && !readReceipt || event.getTs() >= readReceipt.data.ts) {
|
||||||
const actions = client.getPushActionsForEvent(event, true);
|
const actions = client.getPushActionsForEvent(event, true);
|
||||||
|
|
||||||
const color = !!actions.tweaks.highlight
|
if (actions?.tweaks) {
|
||||||
? NotificationColor.Red
|
const color = !!actions.tweaks.highlight
|
||||||
: NotificationColor.Grey;
|
? NotificationColor.Red
|
||||||
|
: NotificationColor.Grey;
|
||||||
|
|
||||||
this.updateNotificationState(color);
|
this.updateNotificationState(color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private resetThreadNotification = (): void => {
|
private resetThreadNotification = (): void => {
|
||||||
this.updateNotificationState(NotificationColor.None);
|
this.updateNotificationState(NotificationColor.None);
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { ThreadNotificationState } from "./ThreadNotificationState";
|
||||||
import { NotificationColor } from "./NotificationColor";
|
import { NotificationColor } from "./NotificationColor";
|
||||||
|
|
||||||
export class ThreadsRoomNotificationState extends NotificationState implements IDestroyable {
|
export class ThreadsRoomNotificationState extends NotificationState implements IDestroyable {
|
||||||
private threadsState = new Map<Thread, ThreadNotificationState>();
|
public readonly threadsState = new Map<Thread, ThreadNotificationState>();
|
||||||
|
|
||||||
protected _symbol = null;
|
protected _symbol = null;
|
||||||
protected _count = 0;
|
protected _count = 0;
|
||||||
|
@ -31,6 +31,11 @@ export class ThreadsRoomNotificationState extends NotificationState implements I
|
||||||
|
|
||||||
constructor(public readonly room: Room) {
|
constructor(public readonly room: Room) {
|
||||||
super();
|
super();
|
||||||
|
if (this.room?.threads) {
|
||||||
|
for (const [, thread] of this.room.threads) {
|
||||||
|
this.onNewThread(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.room.on(ThreadEvent.New, this.onNewThread);
|
this.room.on(ThreadEvent.New, this.onNewThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue