Implement sending read receipt in new pagination
Signed-off-by: Ajay Bura <ajbura@gmail.com>
This commit is contained in:
parent
50db137dea
commit
c1e3645d57
9 changed files with 244 additions and 132 deletions
|
@ -4,5 +4,7 @@ import { useState } from 'react';
|
||||||
export function useForceUpdate() {
|
export function useForceUpdate() {
|
||||||
const [data, setData] = useState(null);
|
const [data, setData] = useState(null);
|
||||||
|
|
||||||
return [data, () => setData({})];
|
return [data, function forceUpdateHook() {
|
||||||
|
setData({});
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import * as roomActions from '../../../client/action/room';
|
||||||
|
|
||||||
import ContextMenu, { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
|
import ContextMenu, { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
|
||||||
|
|
||||||
|
import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
|
||||||
import BellIC from '../../../../public/res/ic/outlined/bell.svg';
|
import BellIC from '../../../../public/res/ic/outlined/bell.svg';
|
||||||
import BellRingIC from '../../../../public/res/ic/outlined/bell-ring.svg';
|
import BellRingIC from '../../../../public/res/ic/outlined/bell-ring.svg';
|
||||||
import BellPingIC from '../../../../public/res/ic/outlined/bell-ping.svg';
|
import BellPingIC from '../../../../public/res/ic/outlined/bell-ping.svg';
|
||||||
|
@ -148,6 +149,14 @@ function RoomOptions() {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleMarkAsRead = () => {
|
||||||
|
const mx = initMatrix.matrixClient;
|
||||||
|
const room = mx.getRoom(roomId);
|
||||||
|
if (!room) return;
|
||||||
|
const events = room.getLiveTimeline().getEvents();
|
||||||
|
mx.sendReadReceipt(events[events.length - 1]);
|
||||||
|
};
|
||||||
|
|
||||||
const handleInviteClick = () => openInviteUser(roomId);
|
const handleInviteClick = () => openInviteUser(roomId);
|
||||||
const handleLeaveClick = (toggleMenu) => {
|
const handleLeaveClick = (toggleMenu) => {
|
||||||
if (confirm('Are you really want to leave this room?')) {
|
if (confirm('Are you really want to leave this room?')) {
|
||||||
|
@ -169,6 +178,14 @@ function RoomOptions() {
|
||||||
content={(toggleMenu) => (
|
content={(toggleMenu) => (
|
||||||
<>
|
<>
|
||||||
<MenuHeader>{twemojify(`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`)}</MenuHeader>
|
<MenuHeader>{twemojify(`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`)}</MenuHeader>
|
||||||
|
<MenuItem
|
||||||
|
iconSrc={TickMarkIC}
|
||||||
|
onClick={() => {
|
||||||
|
handleMarkAsRead(); toggleMenu();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Mark as read
|
||||||
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
iconSrc={AddUserIC}
|
iconSrc={AddUserIC}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -24,12 +24,10 @@ function RoomView({ roomTimeline, eventId }) {
|
||||||
<RoomViewContent
|
<RoomViewContent
|
||||||
eventId={eventId}
|
eventId={eventId}
|
||||||
roomTimeline={roomTimeline}
|
roomTimeline={roomTimeline}
|
||||||
viewEvent={viewEvent}
|
|
||||||
/>
|
/>
|
||||||
<RoomViewFloating
|
<RoomViewFloating
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
roomTimeline={roomTimeline}
|
roomTimeline={roomTimeline}
|
||||||
viewEvent={viewEvent}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="room-view__sticky">
|
<div className="room-view__sticky">
|
||||||
|
|
|
@ -122,15 +122,14 @@ function ViewCmd() {
|
||||||
function FollowingMembers({ roomId, roomTimeline, viewEvent }) {
|
function FollowingMembers({ roomId, roomTimeline, viewEvent }) {
|
||||||
const [followingMembers, setFollowingMembers] = useState([]);
|
const [followingMembers, setFollowingMembers] = useState([]);
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
|
const myUserId = mx.getUserId();
|
||||||
|
|
||||||
const handleOnMessageSent = () => setFollowingMembers([]);
|
const handleOnMessageSent = () => setFollowingMembers([]);
|
||||||
|
|
||||||
const updateFollowingMembers = () => {
|
|
||||||
const myUserId = mx.getUserId();
|
|
||||||
setFollowingMembers(roomTimeline.getLiveReaders().filter((userId) => userId !== myUserId));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const updateFollowingMembers = () => {
|
||||||
|
setFollowingMembers(roomTimeline.getLiveReaders());
|
||||||
|
};
|
||||||
updateFollowingMembers();
|
updateFollowingMembers();
|
||||||
roomTimeline.on(cons.events.roomTimeline.LIVE_RECEIPT, updateFollowingMembers);
|
roomTimeline.on(cons.events.roomTimeline.LIVE_RECEIPT, updateFollowingMembers);
|
||||||
viewEvent.on('message_sent', handleOnMessageSent);
|
viewEvent.on('message_sent', handleOnMessageSent);
|
||||||
|
@ -140,10 +139,11 @@ function FollowingMembers({ roomId, roomTimeline, viewEvent }) {
|
||||||
};
|
};
|
||||||
}, [roomTimeline]);
|
}, [roomTimeline]);
|
||||||
|
|
||||||
return followingMembers.length !== 0 && (
|
const filteredM = followingMembers.filter((userId) => userId !== myUserId);
|
||||||
|
return filteredM.length !== 0 && (
|
||||||
<TimelineChange
|
<TimelineChange
|
||||||
variant="follow"
|
variant="follow"
|
||||||
content={getUsersActionJsx(roomId, followingMembers, 'following the conversation.')}
|
content={getUsersActionJsx(roomId, filteredM, 'following the conversation.')}
|
||||||
time=""
|
time=""
|
||||||
onClick={() => openReadReceipts(roomId, followingMembers)}
|
onClick={() => openReadReceipts(roomId, followingMembers)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -90,7 +90,7 @@ function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
|
||||||
|
|
||||||
if (mEvent.getType() === 'm.room.member') {
|
if (mEvent.getType() === 'm.room.member') {
|
||||||
const timelineChange = parseTimelineChange(mEvent);
|
const timelineChange = parseTimelineChange(mEvent);
|
||||||
if (timelineChange === null) return false;
|
if (timelineChange === null) return <div key={mEvent.getId()} />;
|
||||||
return (
|
return (
|
||||||
<TimelineChange
|
<TimelineChange
|
||||||
key={mEvent.getId()}
|
key={mEvent.getId()}
|
||||||
|
@ -147,7 +147,7 @@ class TimelineScroll extends EventEmitter {
|
||||||
|
|
||||||
let scrollTop = 0;
|
let scrollTop = 0;
|
||||||
const ot = this.inTopHalf ? this.topMsg?.offsetTop : this.bottomMsg?.offsetTop;
|
const ot = this.inTopHalf ? this.topMsg?.offsetTop : this.bottomMsg?.offsetTop;
|
||||||
if (!ot) scrollTop = this.top;
|
if (!ot) scrollTop = Math.round(this.height - this.viewHeight);
|
||||||
else scrollTop = ot - this.diff;
|
else scrollTop = ot - this.diff;
|
||||||
|
|
||||||
this._scrollTo(scrollInfo, scrollTop);
|
this._scrollTo(scrollInfo, scrollTop);
|
||||||
|
@ -255,7 +255,7 @@ class TimelineScroll extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
let timelineScroll = null;
|
let timelineScroll = null;
|
||||||
let focusEventIndex = null;
|
let jumpToItemIndex = -1;
|
||||||
const throttle = new Throttle();
|
const throttle = new Throttle();
|
||||||
const limit = {
|
const limit = {
|
||||||
from: 0,
|
from: 0,
|
||||||
|
@ -282,24 +282,9 @@ const limit = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function useTimeline(roomTimeline, eventId) {
|
function useTimeline(roomTimeline, eventId, readEventStore) {
|
||||||
const [timelineInfo, setTimelineInfo] = useState(null);
|
const [timelineInfo, setTimelineInfo] = useState(null);
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// open specific event.
|
|
||||||
// 1. readUpTo event is in specific timeline
|
|
||||||
// 2. readUpTo event isn't in specific timeline
|
|
||||||
// 3. readUpTo event is specific event
|
|
||||||
// open live timeline.
|
|
||||||
// 1. readUpTo event is in live timeline
|
|
||||||
// 2. readUpTo event isn't in live timeline
|
|
||||||
const initTimeline = (eId) => {
|
|
||||||
limit.setFrom(roomTimeline.timeline.length - limit.getMaxEvents());
|
|
||||||
setTimelineInfo({
|
|
||||||
focusEventId: eId,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setEventTimeline = async (eId) => {
|
const setEventTimeline = async (eId) => {
|
||||||
if (typeof eId === 'string') {
|
if (typeof eId === 'string') {
|
||||||
const isLoaded = await roomTimeline.loadEventTimeline(eId);
|
const isLoaded = await roomTimeline.loadEventTimeline(eId);
|
||||||
|
@ -311,6 +296,35 @@ function useTimeline(roomTimeline, eventId) {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const initTimeline = (eId) => {
|
||||||
|
// NOTICE: eId can be id of readUpto, reply or specific event.
|
||||||
|
// readUpTo: when user click jump to unread message button.
|
||||||
|
// reply: when user click reply from timeline.
|
||||||
|
// specific event when user open a link of event. behave same as ^^^^
|
||||||
|
const readUpToId = roomTimeline.getReadUpToEventId();
|
||||||
|
let focusEventIndex = -1;
|
||||||
|
const isSpecificEvent = eId && eId !== readUpToId;
|
||||||
|
|
||||||
|
if (isSpecificEvent) {
|
||||||
|
focusEventIndex = roomTimeline.getEventIndex(eId);
|
||||||
|
} else if (!readEventStore.getItem()) {
|
||||||
|
// either opening live timeline or jump to unread.
|
||||||
|
focusEventIndex = roomTimeline.getUnreadEventIndex(readUpToId);
|
||||||
|
if (roomTimeline.hasEventInTimeline(readUpToId)) {
|
||||||
|
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
focusEventIndex = roomTimeline.getUnreadEventIndex(readEventStore.getItem().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focusEventIndex > -1) {
|
||||||
|
limit.setFrom(focusEventIndex - Math.round(limit.getMaxEvents() / 2));
|
||||||
|
} else {
|
||||||
|
limit.setFrom(roomTimeline.timeline.length - limit.getMaxEvents());
|
||||||
|
}
|
||||||
|
setTimelineInfo({ focusEventId: isSpecificEvent ? eId : null });
|
||||||
|
};
|
||||||
|
|
||||||
roomTimeline.on(cons.events.roomTimeline.READY, initTimeline);
|
roomTimeline.on(cons.events.roomTimeline.READY, initTimeline);
|
||||||
setEventTimeline(eventId);
|
setEventTimeline(eventId);
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -323,12 +337,16 @@ function useTimeline(roomTimeline, eventId) {
|
||||||
return timelineInfo;
|
return timelineInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePaginate(roomTimeline, forceUpdateLimit) {
|
function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) {
|
||||||
const [info, setInfo] = useState(null);
|
const [info, setInfo] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleOnPagination = (backwards, loaded, canLoadMore) => {
|
const handleOnPagination = (backwards, loaded, canLoadMore) => {
|
||||||
if (loaded === 0) return;
|
if (loaded === 0) return;
|
||||||
|
if (!readEventStore.getItem()) {
|
||||||
|
const readUpToId = roomTimeline.getReadUpToEventId();
|
||||||
|
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
|
||||||
|
}
|
||||||
limit.setFrom(limit.calcNextFrom(backwards, roomTimeline.timeline.length));
|
limit.setFrom(limit.calcNextFrom(backwards, roomTimeline.timeline.length));
|
||||||
setInfo({
|
setInfo({
|
||||||
backwards,
|
backwards,
|
||||||
|
@ -372,96 +390,147 @@ function usePaginate(roomTimeline, forceUpdateLimit) {
|
||||||
return [info, autoPaginate];
|
return [info, autoPaginate];
|
||||||
}
|
}
|
||||||
|
|
||||||
function useHandleScroll(roomTimeline, autoPaginate, viewEvent) {
|
function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdateLimit) {
|
||||||
return useCallback(() => {
|
const handleScroll = useCallback(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
// emit event to toggle scrollToBottom button visibility
|
// emit event to toggle scrollToBottom button visibility
|
||||||
const isAtBottom = (
|
const isAtBottom = (
|
||||||
timelineScroll.bottom < 16
|
timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()
|
||||||
&& !roomTimeline.canPaginateForward()
|
&& limit.getEndIndex() >= roomTimeline.timeline.length
|
||||||
&& limit.getEndIndex() === roomTimeline.length
|
|
||||||
);
|
);
|
||||||
viewEvent.emit('at-bottom', isAtBottom);
|
roomTimeline.emit(cons.events.roomTimeline.AT_BOTTOM, isAtBottom);
|
||||||
|
if (isAtBottom && readEventStore.getItem()) {
|
||||||
|
requestAnimationFrame(() => roomTimeline.markAllAsRead());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
autoPaginate();
|
autoPaginate();
|
||||||
}, [roomTimeline]);
|
}, [roomTimeline]);
|
||||||
|
|
||||||
|
const handleScrollToLive = useCallback(() => {
|
||||||
|
if (readEventStore.getItem()) {
|
||||||
|
requestAnimationFrame(() => roomTimeline.markAllAsRead());
|
||||||
|
}
|
||||||
|
if (roomTimeline.isServingLiveTimeline()) {
|
||||||
|
limit.setFrom(roomTimeline.timeline.length - limit.getMaxEvents());
|
||||||
|
timelineScroll.scrollToBottom();
|
||||||
|
forceUpdateLimit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
roomTimeline.loadLiveTimeline();
|
||||||
|
}, [roomTimeline]);
|
||||||
|
|
||||||
|
return [handleScroll, handleScrollToLive];
|
||||||
}
|
}
|
||||||
|
|
||||||
function useEventArrive(roomTimeline) {
|
function useEventArrive(roomTimeline, readEventStore) {
|
||||||
|
const myUserId = initMatrix.matrixClient.getUserId();
|
||||||
const [newEvent, setEvent] = useState(null);
|
const [newEvent, setEvent] = useState(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const sendReadReceipt = (event) => {
|
||||||
|
if (event.isSending()) return;
|
||||||
|
if (myUserId === event.getSender()) {
|
||||||
|
roomTimeline.markAllAsRead();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const readUpToEvent = readEventStore.getItem();
|
||||||
|
const readUpToId = roomTimeline.getReadUpToEventId();
|
||||||
|
|
||||||
|
// if user doesn't have focus on app don't mark messages as read.
|
||||||
|
if (document.visibilityState === 'hidden' || timelineScroll.bottom >= 16) {
|
||||||
|
if (readUpToEvent === readUpToId) return;
|
||||||
|
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (readUpToEvent?.getId() !== readUpToId) {
|
||||||
|
roomTimeline.markAllAsRead();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleEvent = (event) => {
|
const handleEvent = (event) => {
|
||||||
const tLength = roomTimeline.timeline.length;
|
const tLength = roomTimeline.timeline.length;
|
||||||
if (roomTimeline.isServingLiveTimeline() && tLength - 1 === limit.getEndIndex()) {
|
if (roomTimeline.isServingLiveTimeline()
|
||||||
|
&& limit.getEndIndex() >= tLength - 1
|
||||||
|
&& timelineScroll.bottom < SCROLL_TRIGGER_POS) {
|
||||||
limit.setFrom(tLength - limit.getMaxEvents());
|
limit.setFrom(tLength - limit.getMaxEvents());
|
||||||
}
|
sendReadReceipt(event);
|
||||||
setEvent(event);
|
setEvent(event);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEventRedact = (event) => setEvent(event);
|
||||||
|
|
||||||
roomTimeline.on(cons.events.roomTimeline.EVENT, handleEvent);
|
roomTimeline.on(cons.events.roomTimeline.EVENT, handleEvent);
|
||||||
|
roomTimeline.on(cons.events.roomTimeline.EVENT_REDACTED, handleEventRedact);
|
||||||
return () => {
|
return () => {
|
||||||
roomTimeline.removeListener(cons.events.roomTimeline.EVENT, handleEvent);
|
roomTimeline.removeListener(cons.events.roomTimeline.EVENT, handleEvent);
|
||||||
|
roomTimeline.removeListener(cons.events.roomTimeline.EVENT_REDACTED, handleEventRedact);
|
||||||
};
|
};
|
||||||
}, [roomTimeline]);
|
}, [roomTimeline]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!roomTimeline.initialized) return;
|
if (!roomTimeline.initialized) return;
|
||||||
if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()) {
|
if (timelineScroll.bottom < 16
|
||||||
|
&& !roomTimeline.canPaginateForward()
|
||||||
|
&& document.visibilityState === 'visible') {
|
||||||
timelineScroll.scrollToBottom();
|
timelineScroll.scrollToBottom();
|
||||||
}
|
}
|
||||||
}, [newEvent, roomTimeline]);
|
}, [newEvent, roomTimeline]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RoomViewContent({
|
function RoomViewContent({ eventId, roomTimeline }) {
|
||||||
eventId, roomTimeline, viewEvent,
|
|
||||||
}) {
|
|
||||||
const timelineSVRef = useRef(null);
|
const timelineSVRef = useRef(null);
|
||||||
const readEventStore = useStore(roomTimeline);
|
const readEventStore = useStore(roomTimeline);
|
||||||
const timelineInfo = useTimeline(roomTimeline, eventId);
|
const timelineInfo = useTimeline(roomTimeline, eventId, readEventStore);
|
||||||
const [onLimitUpdate, forceUpdateLimit] = useForceUpdate();
|
const [onLimitUpdate, forceUpdateLimit] = useForceUpdate();
|
||||||
const [paginateInfo, autoPaginate] = usePaginate(roomTimeline, forceUpdateLimit);
|
const [paginateInfo, autoPaginate] = usePaginate(roomTimeline, readEventStore, forceUpdateLimit);
|
||||||
const handleScroll = useHandleScroll(roomTimeline, autoPaginate, viewEvent);
|
const [handleScroll, handleScrollToLive] = useHandleScroll(
|
||||||
useEventArrive(roomTimeline);
|
roomTimeline, autoPaginate, readEventStore, forceUpdateLimit,
|
||||||
|
);
|
||||||
|
useEventArrive(roomTimeline, readEventStore);
|
||||||
const { timeline } = roomTimeline;
|
const { timeline } = roomTimeline;
|
||||||
|
|
||||||
const handleScrollToLive = useCallback(() => {
|
|
||||||
if (roomTimeline.isServingLiveTimeline()) {
|
|
||||||
timelineScroll.scrollToBottom();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
roomTimeline.loadLiveTimeline();
|
|
||||||
}, [roomTimeline]);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!roomTimeline.initialized) {
|
if (!roomTimeline.initialized) {
|
||||||
timelineScroll = new TimelineScroll(timelineSVRef.current);
|
timelineScroll = new TimelineScroll(timelineSVRef.current);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// when active timeline changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!roomTimeline.initialized) return undefined;
|
if (!roomTimeline.initialized) return undefined;
|
||||||
|
|
||||||
if (timeline.length > 0) {
|
if (timeline.length > 0) {
|
||||||
if (focusEventIndex === null) timelineScroll.scrollToBottom();
|
if (jumpToItemIndex === -1) {
|
||||||
else timelineScroll.scrollToIndex(focusEventIndex, 80);
|
timelineScroll.scrollToBottom();
|
||||||
focusEventIndex = null;
|
} else {
|
||||||
|
timelineScroll.scrollToIndex(jumpToItemIndex, 80);
|
||||||
|
}
|
||||||
|
if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()) {
|
||||||
|
if (readEventStore.getItem()?.getId() === roomTimeline.getReadUpToEventId()) {
|
||||||
|
requestAnimationFrame(() => roomTimeline.markAllAsRead());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jumpToItemIndex = -1;
|
||||||
}
|
}
|
||||||
autoPaginate();
|
autoPaginate();
|
||||||
|
|
||||||
timelineScroll.on('scroll', handleScroll);
|
timelineScroll.on('scroll', handleScroll);
|
||||||
viewEvent.on('scroll-to-live', handleScrollToLive);
|
roomTimeline.on(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive);
|
||||||
return () => {
|
return () => {
|
||||||
if (timelineSVRef.current === null) return;
|
if (timelineSVRef.current === null) return;
|
||||||
timelineScroll.removeListener('scroll', handleScroll);
|
timelineScroll.removeListener('scroll', handleScroll);
|
||||||
viewEvent.removeListener('scroll-to-live', handleScrollToLive);
|
roomTimeline.removeListener(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive);
|
||||||
};
|
};
|
||||||
}, [timelineInfo]);
|
}, [timelineInfo]);
|
||||||
|
|
||||||
|
// when paginating from server
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!roomTimeline.initialized) return;
|
if (!roomTimeline.initialized) return;
|
||||||
timelineScroll.tryRestoringScroll();
|
timelineScroll.tryRestoringScroll();
|
||||||
autoPaginate();
|
autoPaginate();
|
||||||
}, [paginateInfo]);
|
}, [paginateInfo]);
|
||||||
|
|
||||||
|
// when paginating locally
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!roomTimeline.initialized) return;
|
if (!roomTimeline.initialized) return;
|
||||||
timelineScroll.tryRestoringScroll();
|
timelineScroll.tryRestoringScroll();
|
||||||
|
@ -473,29 +542,16 @@ function RoomViewContent({
|
||||||
throttle._(() => timelineScroll?.calcScroll(), 400)(target);
|
throttle._(() => timelineScroll?.calcScroll(), 400)(target);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getReadEvent = () => {
|
|
||||||
const readEventId = roomTimeline.getReadUpToEventId();
|
|
||||||
if (readEventStore.getItem()?.getId() === readEventId) {
|
|
||||||
return readEventStore.getItem();
|
|
||||||
}
|
|
||||||
if (roomTimeline.hasEventInActiveTimeline(readEventId)) {
|
|
||||||
return readEventStore.setItem(
|
|
||||||
roomTimeline.findEventByIdInTimelineSet(readEventId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return readEventStore.setItem(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderTimeline = () => {
|
const renderTimeline = () => {
|
||||||
const tl = [];
|
const tl = [];
|
||||||
|
|
||||||
const readEvent = getReadEvent();
|
let itemCountIndex = 0;
|
||||||
let extraItemCount = 0;
|
jumpToItemIndex = -1;
|
||||||
focusEventIndex = null;
|
const readEvent = readEventStore.getItem();
|
||||||
|
|
||||||
if (roomTimeline.canPaginateBackward() || limit.from > 0) {
|
if (roomTimeline.canPaginateBackward() || limit.from > 0) {
|
||||||
tl.push(loadingMsgPlaceholders(1, PLACEHOLDER_COUNT));
|
tl.push(loadingMsgPlaceholders(1, PLACEHOLDER_COUNT));
|
||||||
extraItemCount += PLACEHOLDER_COUNT;
|
itemCountIndex += PLACEHOLDER_COUNT;
|
||||||
}
|
}
|
||||||
for (let i = limit.from; i < limit.getEndIndex(); i += 1) {
|
for (let i = limit.from; i < limit.getEndIndex(); i += 1) {
|
||||||
if (i >= timeline.length) break;
|
if (i >= timeline.length) break;
|
||||||
|
@ -505,30 +561,35 @@ function RoomViewContent({
|
||||||
if (i === 0 && !roomTimeline.canPaginateBackward()) {
|
if (i === 0 && !roomTimeline.canPaginateBackward()) {
|
||||||
if (mEvent.getType() === 'm.room.create') {
|
if (mEvent.getType() === 'm.room.create') {
|
||||||
tl.push(genRoomIntro(mEvent, roomTimeline));
|
tl.push(genRoomIntro(mEvent, roomTimeline));
|
||||||
|
itemCountIndex += 1;
|
||||||
// eslint-disable-next-line no-continue
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
tl.push(genRoomIntro(undefined, roomTimeline));
|
tl.push(genRoomIntro(undefined, roomTimeline));
|
||||||
extraItemCount += 1;
|
itemCountIndex += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unreadDivider = (readEvent
|
const unreadDivider = (readEvent
|
||||||
&& prevMEvent?.getTs() <= readEvent.getTs()
|
&& prevMEvent?.getTs() <= readEvent.getTs()
|
||||||
&& readEvent.getTs() < mEvent.getTs());
|
&& readEvent.getTs() < mEvent.getTs());
|
||||||
if (unreadDivider) {
|
if (unreadDivider) {
|
||||||
tl.push(<Divider key={`new-${readEvent.getId()}`} variant="positive" text="Unread messages" />);
|
tl.push(<Divider key={`new-${mEvent.getId()}`} variant="positive" text="New messages" />);
|
||||||
if (focusEventIndex === null) focusEventIndex = i + extraItemCount;
|
itemCountIndex += 1;
|
||||||
|
if (jumpToItemIndex === -1) jumpToItemIndex = itemCountIndex;
|
||||||
}
|
}
|
||||||
const dayDivider = prevMEvent && isNotInSameDay(mEvent.getDate(), prevMEvent.getDate());
|
const dayDivider = prevMEvent && isNotInSameDay(mEvent.getDate(), prevMEvent.getDate());
|
||||||
if (dayDivider) {
|
if (dayDivider) {
|
||||||
tl.push(<Divider key={`divider-${mEvent.getId()}`} text={`${dateFormat(mEvent.getDate(), 'mmmm dd, yyyy')}`} />);
|
tl.push(<Divider key={`divider-${mEvent.getId()}`} text={`${dateFormat(mEvent.getDate(), 'mmmm dd, yyyy')}`} />);
|
||||||
extraItemCount += 1;
|
itemCountIndex += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const focusId = timelineInfo.focusEventId;
|
const focusId = timelineInfo.focusEventId;
|
||||||
const isFocus = focusId === mEvent.getId() && focusId !== readEvent?.getId();
|
const isFocus = focusId === mEvent.getId();
|
||||||
if (isFocus) focusEventIndex = i + extraItemCount;
|
if (isFocus) jumpToItemIndex = itemCountIndex;
|
||||||
|
|
||||||
tl.push(renderEvent(roomTimeline, mEvent, prevMEvent, isFocus));
|
tl.push(renderEvent(roomTimeline, mEvent, prevMEvent, isFocus));
|
||||||
|
itemCountIndex += 1;
|
||||||
}
|
}
|
||||||
if (roomTimeline.canPaginateForward() || limit.getEndIndex() < timeline.length) {
|
if (roomTimeline.canPaginateForward() || limit.getEndIndex() < timeline.length) {
|
||||||
tl.push(loadingMsgPlaceholders(2, PLACEHOLDER_COUNT));
|
tl.push(loadingMsgPlaceholders(2, PLACEHOLDER_COUNT));
|
||||||
|
@ -554,7 +615,6 @@ RoomViewContent.defaultProps = {
|
||||||
RoomViewContent.propTypes = {
|
RoomViewContent.propTypes = {
|
||||||
eventId: PropTypes.string,
|
eventId: PropTypes.string,
|
||||||
roomTimeline: PropTypes.shape({}).isRequired,
|
roomTimeline: PropTypes.shape({}).isRequired,
|
||||||
viewEvent: PropTypes.shape({}).isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RoomViewContent;
|
export default RoomViewContent;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Button from '../../atoms/button/Button';
|
||||||
import IconButton from '../../atoms/button/IconButton';
|
import IconButton from '../../atoms/button/IconButton';
|
||||||
|
|
||||||
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
|
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
|
||||||
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
|
||||||
|
|
||||||
import { getUsersActionJsx } from './common';
|
import { getUsersActionJsx } from './common';
|
||||||
|
|
||||||
|
@ -20,28 +20,25 @@ function useJumpToEvent(roomTimeline) {
|
||||||
|
|
||||||
const jumpToEvent = () => {
|
const jumpToEvent = () => {
|
||||||
roomTimeline.loadEventTimeline(eventId);
|
roomTimeline.loadEventTimeline(eventId);
|
||||||
setEventId(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelJumpToEvent = () => {
|
const cancelJumpToEvent = (mEvent) => {
|
||||||
setEventId(null);
|
setEventId(null);
|
||||||
roomTimeline.markAsRead();
|
if (!mEvent) roomTimeline.markAllAsRead();
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: if user reaches the unread messages with other ways
|
|
||||||
// like by paginating, or loading timeline for that event by other ways ex: clicking on reply.
|
|
||||||
// then setEventId(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const readEventId = roomTimeline.getReadUpToEventId();
|
const readEventId = roomTimeline.getReadUpToEventId();
|
||||||
// we only show "Jump to unread" btn only if the event is not in live timeline.
|
// we only show "Jump to unread" btn only if the event is not in timeline.
|
||||||
// if event is in live timeline
|
// if event is in timeline
|
||||||
// we will automatically open the timeline from that event
|
// we will automatically open the timeline from that event position
|
||||||
if (!roomTimeline.hasEventInLiveTimeline(readEventId)) {
|
if (!readEventId.startsWith('~') && !roomTimeline.hasEventInTimeline(readEventId)) {
|
||||||
setEventId(readEventId);
|
setEventId(readEventId);
|
||||||
}
|
}
|
||||||
|
roomTimeline.on(cons.events.roomTimeline.MARKED_AS_READ, cancelJumpToEvent);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
roomTimeline.removeListener(cons.events.roomTimeline.MARKED_AS_READ, cancelJumpToEvent);
|
||||||
setEventId(null);
|
setEventId(null);
|
||||||
};
|
};
|
||||||
}, [roomTimeline]);
|
}, [roomTimeline]);
|
||||||
|
@ -69,28 +66,28 @@ function useTypingMembers(roomTimeline) {
|
||||||
return [typingMembers];
|
return [typingMembers];
|
||||||
}
|
}
|
||||||
|
|
||||||
function useScrollToBottom(roomId, viewEvent) {
|
function useScrollToBottom(roomTimeline) {
|
||||||
const [isAtBottom, setIsAtBottom] = useState(true);
|
const [isAtBottom, setIsAtBottom] = useState(true);
|
||||||
const handleAtBottom = (atBottom) => setIsAtBottom(atBottom);
|
const handleAtBottom = (atBottom) => setIsAtBottom(atBottom);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsAtBottom(true);
|
setIsAtBottom(true);
|
||||||
viewEvent.on('at-bottom', handleAtBottom);
|
roomTimeline.on(cons.events.roomTimeline.AT_BOTTOM, handleAtBottom);
|
||||||
return () => viewEvent.removeListener('at-bottom', handleAtBottom);
|
return () => roomTimeline.removeListener(cons.events.roomTimeline.AT_BOTTOM, handleAtBottom);
|
||||||
}, [roomId]);
|
}, [roomTimeline]);
|
||||||
|
|
||||||
return [isAtBottom, setIsAtBottom];
|
return [isAtBottom, setIsAtBottom];
|
||||||
}
|
}
|
||||||
|
|
||||||
function RoomViewFloating({
|
function RoomViewFloating({
|
||||||
roomId, roomTimeline, viewEvent,
|
roomId, roomTimeline,
|
||||||
}) {
|
}) {
|
||||||
const [isJumpToEvent, jumpToEvent, cancelJumpToEvent] = useJumpToEvent(roomTimeline, viewEvent);
|
const [isJumpToEvent, jumpToEvent, cancelJumpToEvent] = useJumpToEvent(roomTimeline);
|
||||||
const [typingMembers] = useTypingMembers(roomTimeline);
|
const [typingMembers] = useTypingMembers(roomTimeline);
|
||||||
const [isAtBottom, setIsAtBottom] = useScrollToBottom(roomId, viewEvent);
|
const [isAtBottom, setIsAtBottom] = useScrollToBottom(roomTimeline);
|
||||||
|
|
||||||
const handleScrollToBottom = () => {
|
const handleScrollToBottom = () => {
|
||||||
viewEvent.emit('scroll-to-live');
|
roomTimeline.emit(cons.events.roomTimeline.SCROLL_TO_LIVE);
|
||||||
setIsAtBottom(true);
|
setIsAtBottom(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,9 +101,9 @@ function RoomViewFloating({
|
||||||
onClick={cancelJumpToEvent}
|
onClick={cancelJumpToEvent}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="extra-small"
|
size="extra-small"
|
||||||
src={CrossIC}
|
src={TickMarkIC}
|
||||||
tooltipPlacement="bottom"
|
tooltipPlacement="bottom"
|
||||||
tooltip="Cancel"
|
tooltip="Mark as read"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={`room-view__typing${typingMembers.size > 0 ? ' room-view__typing--open' : ''}`}>
|
<div className={`room-view__typing${typingMembers.size > 0 ? ' room-view__typing--open' : ''}`}>
|
||||||
|
@ -126,7 +123,6 @@ function RoomViewFloating({
|
||||||
RoomViewFloating.propTypes = {
|
RoomViewFloating.propTypes = {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
roomTimeline: PropTypes.shape({}).isRequired,
|
roomTimeline: PropTypes.shape({}).isRequired,
|
||||||
viewEvent: PropTypes.shape({}).isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RoomViewFloating;
|
export default RoomViewFloating;
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import cons from './cons';
|
import cons from './cons';
|
||||||
|
|
||||||
|
function isNotifEvent(mEvent) {
|
||||||
|
const eType = mEvent.getType();
|
||||||
|
if (!cons.supportEventTypes.includes(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;
|
||||||
|
}
|
||||||
|
|
||||||
class Notifications extends EventEmitter {
|
class Notifications extends EventEmitter {
|
||||||
constructor(roomList) {
|
constructor(roomList) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.supportEvents = ['m.room.message', 'm.room.encrypted', 'm.sticker'];
|
|
||||||
this.matrixClient = roomList.matrixClient;
|
this.matrixClient = roomList.matrixClient;
|
||||||
this.roomList = roomList;
|
this.roomList = roomList;
|
||||||
|
|
||||||
|
@ -36,21 +46,14 @@ class Notifications extends EventEmitter {
|
||||||
const readUpToId = room.getEventReadUpTo(userId);
|
const readUpToId = room.getEventReadUpTo(userId);
|
||||||
const liveEvents = room.getLiveTimeline().getEvents();
|
const liveEvents = room.getLiveTimeline().getEvents();
|
||||||
|
|
||||||
if (liveEvents.length
|
if (liveEvents[liveEvents.length - 1]?.getSender() === userId) {
|
||||||
&& liveEvents[liveEvents.length - 1].sender
|
|
||||||
&& liveEvents[liveEvents.length - 1].sender.userId === userId
|
|
||||||
&& liveEvents[liveEvents.length - 1].getType() !== 'm.room.member') {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = liveEvents.length - 1; i >= 0; i -= 1) {
|
for (let i = liveEvents.length - 1; i >= 0; i -= 1) {
|
||||||
const event = liveEvents[i];
|
const event = liveEvents[i];
|
||||||
|
|
||||||
if (event.getId() === readUpToId) return false;
|
if (event.getId() === readUpToId) return false;
|
||||||
|
if (isNotifEvent(event)) return true;
|
||||||
if (this.supportEvents.includes(event.getType())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -150,7 +153,7 @@ class Notifications extends EventEmitter {
|
||||||
|
|
||||||
_listenEvents() {
|
_listenEvents() {
|
||||||
this.matrixClient.on('Room.timeline', (mEvent, room) => {
|
this.matrixClient.on('Room.timeline', (mEvent, room) => {
|
||||||
if (!this.supportEvents.includes(mEvent.getType())) return;
|
if (!isNotifEvent(mEvent)) return;
|
||||||
const liveEvents = room.getLiveTimeline().getEvents();
|
const liveEvents = room.getLiveTimeline().getEvents();
|
||||||
|
|
||||||
const lastTimelineEvent = liveEvents[liveEvents.length - 1];
|
const lastTimelineEvent = liveEvents[liveEvents.length - 1];
|
||||||
|
|
|
@ -48,6 +48,15 @@ function iterateLinkedTimelines(timeline, backwards, callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTimelineLinked(tm1, tm2) {
|
||||||
|
let tm = getFirstLinkedTimeline(tm1);
|
||||||
|
while (tm) {
|
||||||
|
if (tm === tm2) return true;
|
||||||
|
tm = tm.nextTimeline;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
class RoomTimeline extends EventEmitter {
|
class RoomTimeline extends EventEmitter {
|
||||||
constructor(roomId) {
|
constructor(roomId) {
|
||||||
super();
|
super();
|
||||||
|
@ -93,8 +102,8 @@ class RoomTimeline extends EventEmitter {
|
||||||
this.timeline = [];
|
this.timeline = [];
|
||||||
|
|
||||||
// TODO: don't clear these timeline cause there data can be used in other timeline
|
// TODO: don't clear these timeline cause there data can be used in other timeline
|
||||||
// this.reactionTimeline.clear();
|
this.reactionTimeline.clear();
|
||||||
// this.editedTimeline.clear();
|
this.editedTimeline.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
addToTimeline(mEvent) {
|
addToTimeline(mEvent) {
|
||||||
|
@ -197,22 +206,29 @@ class RoomTimeline extends EventEmitter {
|
||||||
return Promise.allSettled(decryptionPromises);
|
return Promise.allSettled(decryptionPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
markAsRead() {
|
markAllAsRead() {
|
||||||
const readEventId = this.getReadUpToEventId();
|
const readEventId = this.getReadUpToEventId();
|
||||||
if (this.timeline.length === 0) return;
|
if (this.timeline.length === 0) return;
|
||||||
const latestEvent = this.timeline[this.timeline.length - 1];
|
const latestEvent = this.timeline[this.timeline.length - 1];
|
||||||
if (readEventId === latestEvent.getId()) return;
|
if (readEventId === latestEvent.getId()) return;
|
||||||
this.matrixClient.sendReadReceipt(latestEvent);
|
this.matrixClient.sendReadReceipt(latestEvent);
|
||||||
|
this.emit(cons.events.roomTimeline.MARKED_AS_READ, latestEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasEventInLiveTimeline(eventId) {
|
markAsRead(eventId) {
|
||||||
const timelineSet = this.getUnfilteredTimelineSet();
|
if (this.hasEventInTimeline(eventId)) {
|
||||||
return timelineSet.getTimelineForEvent(eventId) === this.liveTimeline;
|
const mEvent = this.findEventById(eventId);
|
||||||
|
if (!mEvent) return;
|
||||||
|
this.matrixClient.sendReadReceipt(mEvent);
|
||||||
|
this.emit(cons.events.roomTimeline.MARKED_AS_READ, mEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasEventInActiveTimeline(eventId) {
|
hasEventInTimeline(eventId, timeline = this.activeTimeline) {
|
||||||
const timelineSet = this.getUnfilteredTimelineSet();
|
const timelineSet = this.getUnfilteredTimelineSet();
|
||||||
return timelineSet.getTimelineForEvent(eventId) === this.activeTimeline;
|
const eventTimeline = timelineSet.getTimelineForEvent(eventId);
|
||||||
|
if (!eventTimeline) return false;
|
||||||
|
return isTimelineLinked(eventTimeline, timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
getUnfilteredTimelineSet() {
|
getUnfilteredTimelineSet() {
|
||||||
|
@ -242,6 +258,22 @@ class RoomTimeline extends EventEmitter {
|
||||||
return [...new Set(readers)];
|
return [...new Set(readers)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnreadEventIndex(readUpToEventId) {
|
||||||
|
if (!this.hasEventInTimeline(readUpToEventId)) return -1;
|
||||||
|
|
||||||
|
const readUpToEvent = this.findEventByIdInTimelineSet(readUpToEventId);
|
||||||
|
if (!readUpToEvent) return -1;
|
||||||
|
const rTs = readUpToEvent.getTs();
|
||||||
|
|
||||||
|
const tLength = this.timeline.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < tLength; i += 1) {
|
||||||
|
const mEvent = this.timeline[i];
|
||||||
|
if (mEvent.getTs() > rTs) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
getReadUpToEventId() {
|
getReadUpToEventId() {
|
||||||
return this.room.getEventReadUpTo(this.matrixClient.getUserId());
|
return this.room.getEventReadUpTo(this.matrixClient.getUserId());
|
||||||
}
|
}
|
||||||
|
@ -261,7 +293,7 @@ class RoomTimeline extends EventEmitter {
|
||||||
deleteFromTimeline(eventId) {
|
deleteFromTimeline(eventId) {
|
||||||
const i = this.getEventIndex(eventId);
|
const i = this.getEventIndex(eventId);
|
||||||
if (i === -1) return undefined;
|
if (i === -1) return undefined;
|
||||||
return this.timeline.splice(i, 1);
|
return this.timeline.splice(i, 1)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenEvents() {
|
_listenEvents() {
|
||||||
|
@ -306,12 +338,12 @@ class RoomTimeline extends EventEmitter {
|
||||||
this.emit(cons.events.roomTimeline.EVENT, event);
|
this.emit(cons.events.roomTimeline.EVENT, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
this._listenRedaction = (event, room) => {
|
this._listenRedaction = (mEvent, room) => {
|
||||||
if (room.roomId !== this.roomId) return;
|
if (room.roomId !== this.roomId) return;
|
||||||
this.deleteFromTimeline(event.getId());
|
const rEvent = this.deleteFromTimeline(mEvent.event.redacts);
|
||||||
this.editedTimeline.delete(event.getId());
|
this.editedTimeline.delete(mEvent.event.redacts);
|
||||||
this.reactionTimeline.delete(event.getId());
|
this.reactionTimeline.delete(mEvent.event.redacts);
|
||||||
this.emit(cons.events.roomTimeline.EVENT);
|
this.emit(cons.events.roomTimeline.EVENT_REDACTED, rEvent, mEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
this._listenTypingEvent = (event, member) => {
|
this._listenTypingEvent = (event, member) => {
|
||||||
|
|
|
@ -92,6 +92,10 @@ const cons = {
|
||||||
PAGINATED: 'PAGINATED',
|
PAGINATED: 'PAGINATED',
|
||||||
TYPING_MEMBERS_UPDATED: 'TYPING_MEMBERS_UPDATED',
|
TYPING_MEMBERS_UPDATED: 'TYPING_MEMBERS_UPDATED',
|
||||||
LIVE_RECEIPT: 'LIVE_RECEIPT',
|
LIVE_RECEIPT: 'LIVE_RECEIPT',
|
||||||
|
MARKED_AS_READ: 'MARKED_AS_READ',
|
||||||
|
EVENT_REDACTED: 'EVENT_REDACTED',
|
||||||
|
AT_BOTTOM: 'AT_BOTTOM',
|
||||||
|
SCROLL_TO_LIVE: 'SCROLL_TO_LIVE',
|
||||||
},
|
},
|
||||||
roomsInput: {
|
roomsInput: {
|
||||||
MESSAGE_SENT: 'MESSAGE_SENT',
|
MESSAGE_SENT: 'MESSAGE_SENT',
|
||||||
|
|
Loading…
Reference in a new issue