--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
+<g>
+ <rect x="7" y="12" fill="#010101" width="10" height="2"/>
+ <g>
+ <circle fill="#010101" cx="19" cy="6" r="4"/>
+ </g>
+ <path fill="#010101" d="M13.3,8H7v2h7.5C14,9.4,13.6,8.7,13.3,8z"/>
+ <path fill="#010101" d="M19,12v5.6l-2.4-1.3L16.1,16h-0.5H5V6h8c0-0.7,0.1-1.4,0.3-2H4.8C3.8,4,3,4.9,3,6v10c0,1.1,0.8,2,1.8,2
+ h10.8l5.4,3v-9.3C20.4,11.9,19.7,12,19,12z"/>
+</g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
+<g>
+ <path fill="#010101" d="M19,6v11.6l-2.4-1.3L16.1,16h-0.5H5V6H19 M19.2,4H4.8C3.8,4,3,4.9,3,6v10c0,1.1,0.8,2,1.8,2h10.8l5.4,3V6
+ C21,4.9,20.2,4,19.2,4L19.2,4z"/>
+ <rect x="7" y="8" fill="#010101" width="10" height="2"/>
+ <rect x="7" y="12" fill="#010101" width="10" height="2"/>
+</g>
+</svg>
import initMatrix from '../../../client/initMatrix';
import { openInviteUser } from '../../../client/action/navigation';
import * as roomActions from '../../../client/action/room';
+import { markAsRead } from '../../../client/action/notifications';
import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
import RoomNotification from '../room-notification/RoomNotification';
const canInvite = room?.canInvite(mx.getUserId());
const handleMarkAsRead = () => {
+ markAsRead(roomId);
afterOptionSelect();
- if (!room) return;
- const events = room.getLiveTimeline().getEvents();
- mx.sendReadReceipt(events[events.length - 1]);
};
const handleInviteClick = () => {
roomInfo.roomTimeline?.removeInternalListeners();
if (mx.getRoom(rId)) {
setRoomInfo({
- roomTimeline: new RoomTimeline(rId, initMatrix.notifications),
+ roomTimeline: new RoomTimeline(rId),
eventId: eId ?? null,
});
} else {
import navigation from '../../../client/state/navigation';
import { openProfileViewer } from '../../../client/action/navigation';
import { diffMinutes, isInSameDay, Throttle } from '../../../util/common';
+import { markAsRead } from '../../../client/action/notifications';
import Divider from '../../atoms/divider/Divider';
import ScrollView from '../../atoms/scroll/ScrollView';
);
roomTimeline.emit(cons.events.roomTimeline.AT_BOTTOM, isAtBottom);
if (isAtBottom && readUptoEvtStore.getItem()) {
- requestAnimationFrame(() => roomTimeline.markAllAsRead());
+ requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
}
});
autoPaginate();
const timelineScroll = timelineScrollRef.current;
const limit = eventLimitRef.current;
if (readUptoEvtStore.getItem()) {
- requestAnimationFrame(() => roomTimeline.markAllAsRead());
+ requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
}
if (roomTimeline.isServingLiveTimeline()) {
limit.setFrom(roomTimeline.timeline.length - limit.maxEvents);
const limit = eventLimitRef.current;
const trySendReadReceipt = (event) => {
if (myUserId === event.getSender()) {
- requestAnimationFrame(() => roomTimeline.markAllAsRead());
+ requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
return;
}
const readUpToEvent = readUptoEvtStore.getItem();
if (isUnread === false) {
if (document.visibilityState === 'visible' && timelineScroll.bottom < 16) {
- requestAnimationFrame(() => roomTimeline.markAllAsRead());
+ requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
} else {
readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
}
const { timeline } = roomTimeline;
const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToId;
if (unreadMsgIsLast) {
- requestAnimationFrame(() => roomTimeline.markAllAsRead());
+ requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
}
};
if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()) {
const readUpToId = roomTimeline.getReadUpToEventId();
if (readUptoEvtStore.getItem()?.getId() === readUpToId || readUpToId === null) {
- requestAnimationFrame(() => roomTimeline.markAllAsRead());
+ requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
}
}
jumpToItemIndex = -1;
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
+import { markAsRead } from '../../../client/action/notifications';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
import IconButton from '../../atoms/button/IconButton';
+import MessageUnreadIC from '../../../../public/res/ic/outlined/message-unread.svg';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
};
const cancelJumpToEvent = () => {
- roomTimeline.markAllAsRead();
+ markAsRead(roomTimeline.roomId);
setEventId(null);
};
setEventId(readEventId);
}
+ const { notifications } = initMatrix;
const handleMarkAsRead = () => setEventId(null);
- roomTimeline.on(cons.events.roomTimeline.MARKED_AS_READ, handleMarkAsRead);
+ notifications.on(cons.events.notifications.FULL_READ, handleMarkAsRead);
return () => {
- roomTimeline.removeListener(cons.events.roomTimeline.MARKED_AS_READ, handleMarkAsRead);
+ notifications.removeListener(cons.events.notifications.FULL_READ, handleMarkAsRead);
setEventId(null);
};
}, [roomTimeline]);
return (
<>
<div className={`room-view__unread ${isJumpToEvent ? 'room-view__unread--open' : ''}`}>
- <Button onClick={jumpToEvent} variant="primary">
- <Text variant="b2">Jump to unread</Text>
+ <Button iconSrc={MessageUnreadIC} onClick={jumpToEvent} variant="primary">
+ <Text variant="b3" weight="medium">Jump to unread messages</Text>
+ </Button>
+ <Button iconSrc={TickMarkIC} onClick={cancelJumpToEvent} variant="primary">
+ <Text variant="b3" weight="bold">Mark as read</Text>
</Button>
- <IconButton
- onClick={cancelJumpToEvent}
- variant="primary"
- size="extra-small"
- src={TickMarkIC}
- tooltipPlacement="bottom"
- tooltip="Mark as read"
- />
</div>
<div className={`room-view__typing${typingMembers.size > 0 ? ' room-view__typing--open' : ''}`}>
<div className="bouncing-loader"><div /></div>
&__unread {
position: absolute;
- top: var(--sp-extra-tight);
- @include dir.prop(right, var(--sp-extra-tight), unset);
- @include dir.prop(left, unset, var(--sp-extra-tight));
+ top: 0;
+ @include dir.prop(left, var(--sp-normal), unset);
+ @include dir.prop(right, unset, var(--sp-normal));
z-index: 999;
display: none;
+ width: calc(100% - var(--sp-extra-loose));
background-color: var(--bg-surface);
- border-radius: var(--bo-radius);
+ border-radius: 0 0 var(--bo-radius) var(--bo-radius);
box-shadow: var(--bs-primary-border);
overflow: hidden;
&--open {
display: flex;
}
-
- & .ic-btn {
- padding: 6px var(--sp-extra-tight);
- border-radius: 0;
- }
& .btn-primary {
- @extend .cp-fx__item-one;
- @include dir.side(margin, 0, 1px);
+ justify-content: flex-start;
border-radius: 0;
- padding: 0 var(--sp-tight);
- &:focus {
- background-color: var(--bg-primary-hover);
+ padding: 2px var(--sp-tight);
+ & .ic-raw {
+ width: 16px;
+ height: 16px;
}
}
+ & .btn-primary:first-child {
+ @extend .cp-fx__item-one;
+ padding: var(--sp-ultra-tight) var(--sp-extra-tight);
+ }
}
}
\ No newline at end of file
--- /dev/null
+import initMatrix from '../initMatrix';
+
+// eslint-disable-next-line import/prefer-default-export
+export async function markAsRead(roomId) {
+ const mx = initMatrix.matrixClient;
+ const room = mx.getRoom(roomId);
+ if (!room) return;
+ initMatrix.notifications.deleteNoti(roomId);
+
+ const timeline = room.getLiveTimeline().getEvents();
+ const readEventId = room.getEventReadUpTo(mx.getUserId());
+
+ const getLatestValidEvent = () => {
+ for (let i = timeline.length - 1; i >= 0; i -= 1) {
+ const latestEvent = timeline[i];
+ if (latestEvent.getId() === readEventId) return null;
+ if (!latestEvent.isSending()) return latestEvent;
+ }
+ return null;
+ };
+ if (timeline.length === 0) return;
+ const latestEvent = getLatestValidEvent();
+ if (latestEvent === null) return;
+
+ await mx.sendReadReceipt(latestEvent);
+}
import { openSearch, toggleRoomSettings } from '../action/navigation';
import navigation from '../state/navigation';
+import { markAsRead } from '../action/notifications';
function listenKeyboard(event) {
// Ctrl/Cmd +
return;
}
- // esc - close room settings panel
- if (event.keyCode === 27 && navigation.isRoomSettings) {
- toggleRoomSettings();
+ // esc
+ if (event.keyCode === 27) {
+ if (navigation.isRoomSettings) {
+ toggleRoomSettings();
+ return;
+ }
+ if (navigation.selectedRoomId) {
+ markAsRead(navigation.selectedRoomId);
+ return;
+ }
}
+ // Don't allow these keys to type/focus message field
if ((event.keyCode !== 8 && event.keyCode < 48)
|| (event.keyCode >= 91 && event.keyCode <= 93)
|| (event.keyCode >= 112 && event.keyCode <= 183)) {
}
class RoomTimeline extends EventEmitter {
- constructor(roomId, notifications) {
+ constructor(roomId) {
super();
// These are local timelines
this.timeline = [];
this.matrixClient = initMatrix.matrixClient;
this.roomId = roomId;
this.room = this.matrixClient.getRoom(roomId);
- this.notifications = notifications;
this.liveTimeline = this.room.getLiveTimeline();
this.activeTimeline = this.liveTimeline;
return Promise.allSettled(decryptionPromises);
}
- markAllAsRead() {
- const readEventId = this.getReadUpToEventId();
- const getLatestValidEvent = () => {
- for (let i = this.timeline.length - 1; i >= 0; i -= 1) {
- const latestEvent = this.timeline[i];
- if (latestEvent.getId() === readEventId) return null;
- if (!latestEvent.isSending()) return latestEvent;
- }
- return null;
- };
- this.notifications.deleteNoti(this.roomId);
- if (this.timeline.length === 0) return;
- const latestEvent = getLatestValidEvent();
- if (latestEvent === null) return;
- if (readEventId === latestEvent.getId()) return;
- this.matrixClient.sendReadReceipt(latestEvent);
- this.emit(cons.events.roomTimeline.MARKED_AS_READ, latestEvent);
- }
-
hasEventInTimeline(eventId, timeline = this.activeTimeline) {
const timelineSet = this.getUnfilteredTimelineSet();
const eventTimeline = timelineSet.getTimelineForEvent(eventId);
PAGINATED: 'PAGINATED',
TYPING_MEMBERS_UPDATED: 'TYPING_MEMBERS_UPDATED',
LIVE_RECEIPT: 'LIVE_RECEIPT',
- MARKED_AS_READ: 'MARKED_AS_READ',
EVENT_REDACTED: 'EVENT_REDACTED',
AT_BOTTOM: 'AT_BOTTOM',
SCROLL_TO_LIVE: 'SCROLL_TO_LIVE',