Improve jump to unread button (#396)
authorAjay Bura <32841439+ajbura@users.noreply.github.com>
Fri, 18 Mar 2022 03:39:14 +0000 (09:09 +0530)
committerGitHub <noreply@github.com>
Fri, 18 Mar 2022 03:39:14 +0000 (09:09 +0530)
* Improve jump to unread button

Signed-off-by: Ajay Bura <ajbura@gmail.com>
* Remove unused cod

Signed-off-by: Ajay Bura <ajbura@gmail.com>
* Fix mark as read not hidding jump to unread btn

Signed-off-by: Ajay Bura <ajbura@gmail.com>
* Add notification mark as read action

Signed-off-by: Ajay Bura <ajbura@gmail.com>
* Add esc as hotkey to mark room as read

Signed-off-by: Ajay Bura <ajbura@gmail.com>
* Add message icons

Signed-off-by: Ajay Bura <ajbura@gmail.com>
* Change jump to unread icon

Signed-off-by: Ajay Bura <ajbura@gmail.com>
public/res/ic/outlined/message-unread.svg [new file with mode: 0644]
public/res/ic/outlined/message.svg [new file with mode: 0644]
src/app/molecules/room-options/RoomOptions.jsx
src/app/organisms/room/Room.jsx
src/app/organisms/room/RoomViewContent.jsx
src/app/organisms/room/RoomViewFloating.jsx
src/app/organisms/room/RoomViewFloating.scss
src/client/action/notifications.js [new file with mode: 0644]
src/client/event/hotkeys.js
src/client/state/RoomTimeline.js
src/client/state/cons.js

diff --git a/public/res/ic/outlined/message-unread.svg b/public/res/ic/outlined/message-unread.svg
new file mode 100644 (file)
index 0000000..fc5e9ff
--- /dev/null
@@ -0,0 +1,15 @@
+<?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>
diff --git a/public/res/ic/outlined/message.svg b/public/res/ic/outlined/message.svg
new file mode 100644 (file)
index 0000000..d36e9a3
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
index 734f93bd28a2dfce04cdf99a8cafacd342b95e4c..85b2b5a548ce94d430d492fc927ff463e94658eb 100644 (file)
@@ -6,6 +6,7 @@ import { twemojify } from '../../../util/twemojify';
 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';
@@ -20,10 +21,8 @@ function RoomOptions({ roomId, afterOptionSelect }) {
   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 = () => {
index 015fb689d7000343a573ca6451bef8dce19dc7da..7c199beae37e5e9bfe38566dff4a2ba3d353f0c5 100644 (file)
@@ -26,7 +26,7 @@ function Room() {
       roomInfo.roomTimeline?.removeInternalListeners();
       if (mx.getRoom(rId)) {
         setRoomInfo({
-          roomTimeline: new RoomTimeline(rId, initMatrix.notifications),
+          roomTimeline: new RoomTimeline(rId),
           eventId: eId ?? null,
         });
       } else {
index e3f2aba4cc06c413e8522fd8899b6c5ce219b59d..dd77e9891b2309c4600567ec8ebf4e5b587d7c9c 100644 (file)
@@ -14,6 +14,7 @@ import cons from '../../../client/state/cons';
 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';
@@ -253,7 +254,7 @@ function useHandleScroll(
       );
       roomTimeline.emit(cons.events.roomTimeline.AT_BOTTOM, isAtBottom);
       if (isAtBottom && readUptoEvtStore.getItem()) {
-        requestAnimationFrame(() => roomTimeline.markAllAsRead());
+        requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
       }
     });
     autoPaginate();
@@ -263,7 +264,7 @@ function useHandleScroll(
     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);
@@ -286,7 +287,7 @@ function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, event
     const limit = eventLimitRef.current;
     const trySendReadReceipt = (event) => {
       if (myUserId === event.getSender()) {
-        requestAnimationFrame(() => roomTimeline.markAllAsRead());
+        requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
         return;
       }
       const readUpToEvent = readUptoEvtStore.getItem();
@@ -295,7 +296,7 @@ function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, event
 
       if (isUnread === false) {
         if (document.visibilityState === 'visible' && timelineScroll.bottom < 16) {
-          requestAnimationFrame(() => roomTimeline.markAllAsRead());
+          requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
         } else {
           readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
         }
@@ -305,7 +306,7 @@ function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, event
       const { timeline } = roomTimeline;
       const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToId;
       if (unreadMsgIsLast) {
-        requestAnimationFrame(() => roomTimeline.markAllAsRead());
+        requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
       }
     };
 
@@ -399,7 +400,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
       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;
index 1bec52d210971ee09562a27ed28fa03977efaaaf..e65e854fcf8aa1292bc108ee58164b48f242e09f 100644 (file)
@@ -5,11 +5,13 @@ import './RoomViewFloating.scss';
 
 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';
 
@@ -23,7 +25,7 @@ function useJumpToEvent(roomTimeline) {
   };
 
   const cancelJumpToEvent = () => {
-    roomTimeline.markAllAsRead();
+    markAsRead(roomTimeline.roomId);
     setEventId(null);
   };
 
@@ -36,11 +38,12 @@ function useJumpToEvent(roomTimeline) {
       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]);
@@ -96,17 +99,12 @@ function RoomViewFloating({
   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>
index 13de15ca2423b505a18e65d6e8dda5396b0e26ef..263025e1258563be3f3a5565fb9e3aea8e78c817 100644 (file)
 
   &__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
diff --git a/src/client/action/notifications.js b/src/client/action/notifications.js
new file mode 100644 (file)
index 0000000..a869632
--- /dev/null
@@ -0,0 +1,26 @@
+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);
+}
index 41b6fc8f5e7ca448f26c8d72c0cb0281fc830da5..11f72bea63886b2b5fbaa2350d2035210f562372 100644 (file)
@@ -1,5 +1,6 @@
 import { openSearch, toggleRoomSettings } from '../action/navigation';
 import navigation from '../state/navigation';
+import { markAsRead } from '../action/notifications';
 
 function listenKeyboard(event) {
   // Ctrl/Cmd +
@@ -18,11 +19,19 @@ function listenKeyboard(event) {
       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)) {
index bccb197d88e386acd27cfd3874eeaf0abd483442..57d91c1415975752d0a84e6315d86e1a43b9699c 100644 (file)
@@ -77,7 +77,7 @@ function isTimelineLinked(tm1, tm2) {
 }
 
 class RoomTimeline extends EventEmitter {
-  constructor(roomId, notifications) {
+  constructor(roomId) {
     super();
     // These are local timelines
     this.timeline = [];
@@ -88,7 +88,6 @@ class RoomTimeline extends EventEmitter {
     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;
@@ -228,25 +227,6 @@ class RoomTimeline extends EventEmitter {
     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);
index 862bf5cf7cd892ce91819ff72b471eae0e174141..f7ffc8f9c06c841b9f987b3fa77214e02897d2da 100644 (file)
@@ -116,7 +116,6 @@ const cons = {
       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',