Added unread symbol for Spaces, DMs and Home (#82)
authorAjay Bura <ajbura@gmail.com>
Sun, 12 Sep 2021 15:14:13 +0000 (20:44 +0530)
committerAjay Bura <ajbura@gmail.com>
Sun, 12 Sep 2021 15:14:13 +0000 (20:44 +0530)
14 files changed:
src/app/atoms/badge/NotificationBadge.scss
src/app/molecules/room-selector/RoomSelector.jsx
src/app/molecules/sidebar-avatar/SidebarAvatar.jsx
src/app/molecules/sidebar-avatar/SidebarAvatar.scss
src/app/organisms/navigation/Directs.jsx
src/app/organisms/navigation/DrawerBreadcrumb.jsx
src/app/organisms/navigation/DrawerBreadcrumb.scss
src/app/organisms/navigation/Home.jsx
src/app/organisms/navigation/Selector.jsx
src/app/organisms/navigation/SideBar.jsx
src/client/state/Notifications.js
src/client/state/RoomList.js
src/client/state/cons.js
src/index.scss

index c876c37ac97c897ebac3c2625990bf61272f8e04..c672b114a200c94612a8282e992c4925a190bbe1 100644 (file)
@@ -12,7 +12,7 @@
   }
 
   &--alert {
-    background-color: var(--bg-danger);
+    background-color: var(--bg-positive);
     & .text { color: white }
   }
 
index 7e7f277c3d6e9513cfd25c0036d512ebd20415a1..47201a625500175cc07f202ea13f8b6dacd60048 100644 (file)
@@ -84,7 +84,10 @@ RoomSelector.propTypes = {
   iconSrc: PropTypes.string,
   isSelected: PropTypes.bool,
   isUnread: PropTypes.bool.isRequired,
-  notificationCount: PropTypes.number.isRequired,
+  notificationCount: PropTypes.oneOfType([
+    PropTypes.string,
+    PropTypes.number,
+  ]).isRequired,
   isAlert: PropTypes.bool.isRequired,
   options: PropTypes.node,
   onClick: PropTypes.func.isRequired,
index 8d57e9bd41b5e02dbe560cb5ec1fd15d15cb1b33..882c00c948376414d3a5635e1d41e4c6603cb55d 100644 (file)
@@ -2,29 +2,22 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import './SidebarAvatar.scss';
 
-import Tippy from '@tippyjs/react';
 import Avatar from '../../atoms/avatar/Avatar';
 import Text from '../../atoms/text/Text';
+import Tooltip from '../../atoms/tooltip/Tooltip';
 import NotificationBadge from '../../atoms/badge/NotificationBadge';
 import { blurOnBubbling } from '../../atoms/button/script';
 
 const SidebarAvatar = React.forwardRef(({
   tooltip, text, bgColor, imageSrc,
-  iconSrc, active, onClick, notifyCount,
+  iconSrc, active, onClick, isUnread, notificationCount, isAlert,
 }, ref) => {
   let activeClass = '';
   if (active) activeClass = ' sidebar-avatar--active';
   return (
-    <Tippy
+    <Tooltip
       content={<Text variant="b1">{tooltip}</Text>}
-      className="sidebar-avatar-tippy"
-      touch="hold"
-      arrow={false}
       placement="right"
-      maxWidth={200}
-      delay={[0, 0]}
-      duration={[100, 0]}
-      offset={[0, 0]}
     >
       <button
         ref={ref}
@@ -40,9 +33,14 @@ const SidebarAvatar = React.forwardRef(({
           iconSrc={iconSrc}
           size="normal"
         />
-        { notifyCount !== null && <NotificationBadge alert content={notifyCount} /> }
+        { isUnread && (
+          <NotificationBadge
+            alert={isAlert}
+            content={notificationCount !== 0 ? notificationCount : null}
+          />
+        )}
       </button>
-    </Tippy>
+    </Tooltip>
   );
 });
 SidebarAvatar.defaultProps = {
@@ -52,7 +50,9 @@ SidebarAvatar.defaultProps = {
   imageSrc: null,
   active: false,
   onClick: null,
-  notifyCount: null,
+  isUnread: false,
+  notificationCount: 0,
+  isAlert: false,
 };
 
 SidebarAvatar.propTypes = {
@@ -63,10 +63,12 @@ SidebarAvatar.propTypes = {
   iconSrc: PropTypes.string,
   active: PropTypes.bool,
   onClick: PropTypes.func,
-  notifyCount: PropTypes.oneOfType([
+  isUnread: PropTypes.bool,
+  notificationCount: PropTypes.oneOfType([
     PropTypes.string,
     PropTypes.number,
   ]),
+  isAlert: PropTypes.bool,
 };
 
 export default SidebarAvatar;
index 61917358a2f8764aab2862c7780602064a275814..3f445dfd4becc1af0cd0c1260883e86569948ec9 100644 (file)
@@ -1,28 +1,18 @@
-
-.sidebar-avatar-tippy {
-  padding: var(--sp-extra-tight) var(--sp-normal);
-  background-color: var(--bg-tooltip);
-  border-radius: var(--bo-radius);
-  box-shadow: var(--bs-popup);
-
-  .text {
-    color: var(--tc-tooltip);
-  }
-}
-
 .sidebar-avatar {
   position: relative;
   display: flex;
   justify-content: center;
   align-items: center;
-  width: 100%;
   cursor: pointer;
   
   & .notification-badge {
     position: absolute;
-    right: var(--sp-extra-tight);
-    top: calc(-1 * var(--sp-ultra-tight));
+    right: 0;
+    top: 0;
     box-shadow: 0 0 0 2px var(--bg-surface-low);
+    transform: translate(20%, -20%);
+
+    margin: 0 !important;
   }
   &:focus {
     outline: none;
@@ -37,7 +27,7 @@
     content: "";
     display: block;
     position: absolute;
-    left: 0;
+    left: -11px;
     top: 50%;
     transform: translateY(-50%);
 
@@ -48,7 +38,8 @@
     transition: height 200ms linear;
 
     [dir=rtl] & {
-      right: 0;
+      left: unset;
+      right: -11px;
       border-radius: 4px 0 0 4px;
     }
   }
index a907980bc561a01af0de956f43d1a38df27b825b..639f4cd4edace5daea185e70d7c56a32b383430e 100644 (file)
@@ -12,7 +12,7 @@ import { AtoZ } from './common';
 
 const drawerPostie = new Postie();
 function Directs() {
-  const { roomList } = initMatrix;
+  const { roomList, notifications } = initMatrix;
   const directIds = [...roomList.directs].sort(AtoZ);
 
   const [, forceUpdate] = useState({});
@@ -26,10 +26,11 @@ function Directs() {
     drawerPostie.post('selector-change', addresses, selectedRoomId);
   }
 
-  function unreadChanged(roomId) {
-    if (!drawerPostie.hasTopic('unread-change')) return;
-    if (!drawerPostie.hasSubscriber('unread-change', roomId)) return;
-    drawerPostie.post('unread-change', roomId);
+  function notiChanged(roomId, total, prevTotal) {
+    if (total === prevTotal) return;
+    if (drawerPostie.hasTopicAndSubscriber('unread-change', roomId)) {
+      drawerPostie.post('unread-change', roomId);
+    }
   }
 
   function roomListUpdated() {
@@ -47,13 +48,11 @@ function Directs() {
   useEffect(() => {
     roomList.on(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
     navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
-    roomList.on(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
-    roomList.on(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
+    notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
     return () => {
       roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
       navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
-      roomList.removeListener(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
-      roomList.removeListener(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
+      notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
     };
   }, []);
 
index e7843626b7fb0b0a5e29d32b90170b9602f6b089..7eaae4ef38300f83743a986feda48e9fb6b1de4f 100644 (file)
@@ -1,4 +1,4 @@
-import React, { useEffect, useRef } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
 import PropTypes from 'prop-types';
 import './DrawerBreadcrumb.scss';
 
@@ -6,51 +6,108 @@ import initMatrix from '../../../client/initMatrix';
 import cons from '../../../client/state/cons';
 import { selectSpace } from '../../../client/action/navigation';
 import navigation from '../../../client/state/navigation';
+import { abbreviateNumber } from '../../../util/common';
 
 import Text from '../../atoms/text/Text';
 import RawIcon from '../../atoms/system-icons/RawIcon';
 import Button from '../../atoms/button/Button';
 import ScrollView from '../../atoms/scroll/ScrollView';
+import NotificationBadge from '../../atoms/badge/NotificationBadge';
 
 import ChevronRightIC from '../../../../public/res/ic/outlined/chevron-right.svg';
 
 function DrawerBreadcrumb({ spaceId }) {
+  const [, forceUpdate] = useState({});
   const scrollRef = useRef(null);
+  const { roomList, notifications } = initMatrix;
   const mx = initMatrix.matrixClient;
   const spacePath = navigation.selectedSpacePath;
 
+  function onNotiChanged(roomId, total, prevTotal) {
+    if (total === prevTotal) return;
+    if (navigation.selectedSpacePath.includes(roomId)) {
+      forceUpdate({});
+    }
+    if (navigation.selectedSpacePath[0] === cons.tabs.HOME) {
+      if (!roomList.isOrphan(roomId)) return;
+      if (roomList.directs.has(roomId)) return;
+      forceUpdate({});
+    }
+  }
+
   useEffect(() => {
     requestAnimationFrame(() => {
       if (scrollRef?.current === null) return;
       scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;
     });
+    notifications.on(cons.events.notifications.NOTI_CHANGED, onNotiChanged);
+    return () => {
+      notifications.removeListener(cons.events.notifications.NOTI_CHANGED, onNotiChanged);
+    };
   }, [spaceId]);
 
   if (spacePath.length === 1) return null;
 
+  function getHomeNotiExcept(childId) {
+    const orphans = roomList.getOrphans();
+    const childIndex = orphans.indexOf(childId);
+    if (childId !== -1) orphans.splice(childIndex, 1);
+
+    let noti = null;
+
+    orphans.forEach((roomId) => {
+      if (!notifications.hasNoti(roomId)) return;
+      if (noti === null) noti = { total: 0, highlight: 0 };
+      const childNoti = notifications.getNoti(roomId);
+      noti.total += childNoti.total;
+      noti.highlight += childNoti.highlight;
+    });
+
+    return noti;
+  }
+
+  function getNotiExcept(roomId, childId) {
+    if (!notifications.hasNoti(roomId)) return null;
+
+    const noti = notifications.getNoti(roomId);
+    if (!notifications.hasNoti(childId)) return noti;
+    if (noti.from === null) return noti;
+    if (noti.from.has(childId) && noti.from.size === 1) return null;
+
+    const childNoti = notifications.getNoti(childId);
+
+    return {
+      total: noti.total - childNoti.total,
+      highlight: noti.highlight - childNoti.highlight,
+    };
+  }
+
   return (
     <div className="breadcrumb__wrapper">
       <ScrollView ref={scrollRef} horizontal vertical={false} invisible>
         <div className="breadcrumb">
           {
             spacePath.map((id, index) => {
-              if (index === 0) {
-                return (
-                  <Button key={id} onClick={() => selectSpace(id)}>
-                    <Text variant="b2">{id === cons.tabs.HOME ? 'Home' : mx.getRoom(id).name}</Text>
-                  </Button>
-                );
-              }
+              const noti = (id !== cons.tabs.HOME && index < spacePath.length)
+                ? getNotiExcept(id, (index === spacePath.length - 1) ? null : spacePath[index + 1])
+                : getHomeNotiExcept((index === spacePath.length - 1) ? null : spacePath[index + 1]);
+
               return (
                 <React.Fragment
                   key={id}
                 >
-                  <RawIcon size="extra-small" src={ChevronRightIC} />
+                  { index !== 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
                   <Button
                     className={index === spacePath.length - 1 ? 'breadcrumb__btn--selected' : ''}
                     onClick={() => selectSpace(id)}
                   >
-                    <Text variant="b2">{ mx.getRoom(id).name }</Text>
+                    <Text variant="b2">{id === cons.tabs.HOME ? 'Home' : mx.getRoom(id).name}</Text>
+                    { noti !== null && (
+                      <NotificationBadge
+                        alert={noti.highlight !== 0}
+                        content={noti.total > 0 ? abbreviateNumber(noti.total) : null}
+                      />
+                    )}
                   </Button>
                 </React.Fragment>
               );
index 80262a9d0e2dd148f55ac7bc599e7dab96be78fb..60cd47f152587ef657ebc806ab4fe0b5433641dc 100644 (file)
       overflow: hidden;
       text-overflow: ellipsis;
     }
+
+    & .notification-badge {
+      margin-left: var(--sp-extra-tight);
+      [dir=rtl] & {
+        margin-left: 0;
+        margin-right: var(--sp-extra-tight);
+      }
+    }
   }
 
   &__btn--selected {
index 120ceb7fe7934e19489b320f488ce4902b286a70..2c505f7239a920ab87e69822ed4868d7509886db 100644 (file)
@@ -15,7 +15,7 @@ import { AtoZ } from './common';
 const drawerPostie = new Postie();
 function Home({ spaceId }) {
   const [, forceUpdate] = useState({});
-  const { roomList } = initMatrix;
+  const { roomList, notifications } = initMatrix;
   let spaceIds = [];
   let roomIds = [];
   let directIds = [];
@@ -40,10 +40,11 @@ function Home({ spaceId }) {
     if (addresses.length === 0) return;
     drawerPostie.post('selector-change', addresses, selectedRoomId);
   }
-  function unreadChanged(roomId) {
-    if (!drawerPostie.hasTopic('unread-change')) return;
-    if (!drawerPostie.hasSubscriber('unread-change', roomId)) return;
-    drawerPostie.post('unread-change', roomId);
+  function notiChanged(roomId, total, prevTotal) {
+    if (total === prevTotal) return;
+    if (drawerPostie.hasTopicAndSubscriber('unread-change', roomId)) {
+      drawerPostie.post('unread-change', roomId);
+    }
   }
 
   function roomListUpdated() {
@@ -61,13 +62,11 @@ function Home({ spaceId }) {
   useEffect(() => {
     roomList.on(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
     navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
-    roomList.on(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
-    roomList.on(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
+    notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
     return () => {
       roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, roomListUpdated);
       navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
-      roomList.removeListener(cons.events.roomList.MY_RECEIPT_ARRIVED, unreadChanged);
-      roomList.removeListener(cons.events.roomList.EVENT_ARRIVED, unreadChanged);
+      notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
     };
   }, []);
 
index 1a47a57e14061497c05e2ea2d52b9d90a835fa70..7ec7c0b0ecf7538ea7b01005f4c3d836211d7e9d 100644 (file)
@@ -3,11 +3,10 @@ import React, { useState, useEffect } from 'react';
 import PropTypes from 'prop-types';
 
 import initMatrix from '../../../client/initMatrix';
-import { doesRoomHaveUnread } from '../../../util/matrixUtil';
 import navigation from '../../../client/state/navigation';
 import { openRoomOptions } from '../../../client/action/navigation';
 import { createSpaceShortcut, deleteSpaceShortcut } from '../../../client/action/room';
-import { getEventCords } from '../../../util/common';
+import { getEventCords, abbreviateNumber } from '../../../util/common';
 
 import IconButton from '../../atoms/button/IconButton';
 import RoomSelector from '../../molecules/room-selector/RoomSelector';
@@ -24,6 +23,7 @@ function Selector({
   roomId, isDM, drawerPostie, onClick,
 }) {
   const mx = initMatrix.matrixClient;
+  const noti = initMatrix.notifications;
   const room = mx.getRoom(roomId);
   let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
   if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
@@ -54,9 +54,9 @@ function Selector({
         name={room.name}
         roomId={roomId}
         iconSrc={room.getJoinRule() === 'invite' ? SpaceLockIC : SpaceIC}
-        isUnread={doesRoomHaveUnread(room)}
-        notificationCount={room.getUnreadNotificationCount('total') || 0}
-        isAlert={room.getUnreadNotificationCount('highlight') !== 0}
+        isUnread={noti.hasNoti(roomId)}
+        notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))}
+        isAlert={noti.getHighlightNoti(roomId) !== 0}
         onClick={onClick}
         options={(
           <IconButton
@@ -85,9 +85,9 @@ function Selector({
       // eslint-disable-next-line no-nested-ternary
       iconSrc={isDM ? null : room.getJoinRule() === 'invite' ? HashLockIC : HashIC}
       isSelected={isSelected}
-      isUnread={doesRoomHaveUnread(room)}
-      notificationCount={room.getUnreadNotificationCount('total') || 0}
-      isAlert={room.getUnreadNotificationCount('highlight') !== 0}
+      isUnread={noti.hasNoti(roomId)}
+      notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))}
+      isAlert={noti.getHighlightNoti(roomId) !== 0}
       onClick={onClick}
       options={(
         <IconButton
index b95636f99705f97ab24271919226525065c2e5c4..cd6de37e58d4e798ebcd6844edb9c40c23ff4685 100644 (file)
@@ -9,6 +9,7 @@ import {
   selectTab, openInviteList, openPublicRooms, openSettings,
 } from '../../../client/action/navigation';
 import navigation from '../../../client/state/navigation';
+import { abbreviateNumber } from '../../../util/common';
 
 import ScrollView from '../../atoms/scroll/ScrollView';
 import SidebarAvatar from '../../molecules/sidebar-avatar/SidebarAvatar';
@@ -55,7 +56,7 @@ function ProfileAvatarMenu() {
 }
 
 function SideBar() {
-  const { roomList } = initMatrix;
+  const { roomList, notifications } = initMatrix;
   const mx = initMatrix.matrixClient;
   const totalInviteCount = () => roomList.inviteRooms.size
     + roomList.inviteSpaces.size
@@ -74,33 +75,83 @@ function SideBar() {
   function onSpaceShortcutUpdated() {
     forceUpdate({});
   }
+  function onNotificationChanged(roomId, total, prevTotal) {
+    if (total === prevTotal) return;
+    forceUpdate({});
+  }
 
   useEffect(() => {
     navigation.on(cons.events.navigation.TAB_SELECTED, onTabSelected);
     roomList.on(cons.events.roomList.SPACE_SHORTCUT_UPDATED, onSpaceShortcutUpdated);
-    initMatrix.roomList.on(
-      cons.events.roomList.INVITELIST_UPDATED,
-      onInviteListChange,
-    );
+    roomList.on(cons.events.roomList.INVITELIST_UPDATED, onInviteListChange);
+    notifications.on(cons.events.notifications.NOTI_CHANGED, onNotificationChanged);
 
     return () => {
       navigation.removeListener(cons.events.navigation.TAB_SELECTED, onTabSelected);
       roomList.removeListener(cons.events.roomList.SPACE_SHORTCUT_UPDATED, onSpaceShortcutUpdated);
-      initMatrix.roomList.removeListener(
-        cons.events.roomList.INVITELIST_UPDATED,
-        onInviteListChange,
-      );
+      roomList.removeListener(cons.events.roomList.INVITELIST_UPDATED, onInviteListChange);
+      notifications.removeListener(cons.events.notifications.NOTI_CHANGED, onNotificationChanged);
     };
   }, []);
 
+  function getHomeNoti() {
+    const orphans = roomList.getOrphans();
+    let noti = null;
+
+    orphans.forEach((roomId) => {
+      if (!notifications.hasNoti(roomId)) return;
+      if (noti === null) noti = { total: 0, highlight: 0 };
+      const childNoti = notifications.getNoti(roomId);
+      noti.total += childNoti.total;
+      noti.highlight += childNoti.highlight;
+    });
+
+    return noti;
+  }
+  function getDMsNoti() {
+    if (roomList.directs.size === 0) return null;
+    let noti = null;
+
+    [...roomList.directs].forEach((roomId) => {
+      if (!notifications.hasNoti(roomId)) return;
+      if (noti === null) noti = { total: 0, highlight: 0 };
+      const childNoti = notifications.getNoti(roomId);
+      noti.total += childNoti.total;
+      noti.highlight += childNoti.highlight;
+    });
+
+    return noti;
+  }
+
+  // TODO: bellow operations are heavy.
+  // refactor this component into more smaller components.
+  const dmsNoti = getDMsNoti();
+  const homeNoti = getHomeNoti();
+
   return (
     <div className="sidebar">
       <div className="sidebar__scrollable">
         <ScrollView invisible>
           <div className="scrollable-content">
             <div className="featured-container">
-              <SidebarAvatar active={selectedTab === cons.tabs.HOME} onClick={() => selectTab(cons.tabs.HOME)} tooltip="Home" iconSrc={HomeIC} />
-              <SidebarAvatar active={selectedTab === cons.tabs.DIRECTS} onClick={() => selectTab(cons.tabs.DIRECTS)} tooltip="People" iconSrc={UserIC} />
+              <SidebarAvatar
+                active={selectedTab === cons.tabs.HOME}
+                onClick={() => selectTab(cons.tabs.HOME)}
+                tooltip="Home"
+                iconSrc={HomeIC}
+                isUnread={homeNoti !== null}
+                notificationCount={homeNoti !== null ? abbreviateNumber(homeNoti.total) : 0}
+                isAlert={homeNoti?.highlight > 0}
+              />
+              <SidebarAvatar
+                active={selectedTab === cons.tabs.DIRECTS}
+                onClick={() => selectTab(cons.tabs.DIRECTS)}
+                tooltip="People"
+                iconSrc={UserIC}
+                isUnread={dmsNoti !== null}
+                notificationCount={dmsNoti !== null ? abbreviateNumber(dmsNoti.total) : 0}
+                isAlert={dmsNoti?.highlight > 0}
+              />
               <SidebarAvatar onClick={() => openPublicRooms()} tooltip="Public rooms" iconSrc={HashSearchIC} />
             </div>
             <div className="sidebar-divider" />
@@ -117,6 +168,9 @@ function SideBar() {
                       bgColor={colorMXID(room.roomId)}
                       imageSrc={room.getAvatarUrl(initMatrix.matrixClient.baseUrl, 42, 42, 'crop') || null}
                       text={room.name.slice(0, 1)}
+                      isUnread={notifications.hasNoti(sRoomId)}
+                      notificationCount={abbreviateNumber(notifications.getTotalNoti(sRoomId))}
+                      isAlert={notifications.getHighlightNoti(sRoomId) !== 0}
                       onClick={() => selectTab(shortcut)}
                     />
                   );
@@ -131,7 +185,9 @@ function SideBar() {
         <div className="sticky-container">
           { totalInvites !== 0 && (
             <SidebarAvatar
-              notifyCount={totalInvites}
+              isUnread
+              notificationCount={totalInvites}
+              isAlert
               onClick={() => openInviteList()}
               tooltip="Invites"
               iconSrc={InviteIC}
index f3b56aceaf75e7d6631e5ac402463824faf4e990..f5ecce2b51c6c15b1dc690812bef828ba899c4e2 100644 (file)
@@ -58,11 +58,27 @@ class Notifications extends EventEmitter {
     return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null };
   }
 
+  getTotalNoti(roomId) {
+    const { total } = this.getNoti(roomId);
+    return total;
+  }
+
+  getHighlightNoti(roomId) {
+    const { highlight } = this.getNoti(roomId);
+    return highlight;
+  }
+
+  getFromNoti(roomId) {
+    const { from } = this.getNoti(roomId);
+    return from;
+  }
+
   hasNoti(roomId) {
     return this.roomIdToNoti.has(roomId);
   }
 
   _setNoti(roomId, total, highlight, childId) {
+    const prevTotal = this.roomIdToNoti.get(roomId)?.total ?? null;
     const noti = this.getNoti(roomId);
 
     noti.total += total;
@@ -73,7 +89,7 @@ class Notifications extends EventEmitter {
     }
 
     this.roomIdToNoti.set(roomId, noti);
-    this.emit(cons.events.notification.NOTI_CHANGED, roomId);
+    this.emit(cons.events.notifications.NOTI_CHANGED, roomId, noti.total, prevTotal);
 
     const parentIds = this.roomList.roomIdToParents.get(roomId);
     if (typeof parentIds === 'undefined') return;
@@ -84,6 +100,7 @@ class Notifications extends EventEmitter {
     if (this.roomIdToNoti.has(roomId) === false) return;
 
     const noti = this.getNoti(roomId);
+    const prevTotal = noti.total;
     noti.total -= total;
     noti.highlight -= highlight;
     if (childId && noti.from !== null) {
@@ -91,10 +108,11 @@ class Notifications extends EventEmitter {
     }
     if (noti.from === null || noti.from.size === 0) {
       this.roomIdToNoti.delete(roomId);
-      this.emit(cons.events.notification.FULL_READ, roomId);
+      this.emit(cons.events.notifications.FULL_READ, roomId);
+      this.emit(cons.events.notifications.NOTI_CHANGED, roomId, null, prevTotal);
     } else {
       this.roomIdToNoti.set(roomId, noti);
-      this.emit(cons.events.notification.NOTI_CHANGED, roomId);
+      this.emit(cons.events.notifications.NOTI_CHANGED, roomId, noti.total, prevTotal);
     }
 
     const parentIds = this.roomList.roomIdToParents.get(roomId);
@@ -120,8 +138,6 @@ class Notifications extends EventEmitter {
 
     this.matrixClient.on('Room.receipt', (mEvent, room) => {
       if (mEvent.getType() === 'm.receipt') {
-        if (typeof mEvent.event.room_id === 'string') return;
-
         const content = mEvent.getContent();
         const readedEventId = Object.keys(content)[0];
         const readerUserId = Object.keys(content[readedEventId]['m.read'])[0];
index c6b6f86bdf9b5fa919ad28d7a5ebd1f565b569c9..b746a46816baea29154d2697f4df90423f12b9f2 100644 (file)
@@ -41,6 +41,15 @@ class RoomList extends EventEmitter {
     this.matrixClient.setAccountData(cons['in.cinny.spaces'], spaceContent);
   }
 
+  isOrphan(roomId) {
+    return !this.roomIdToParents.has(roomId);
+  }
+
+  getOrphans() {
+    const rooms = [...this.spaces].concat([...this.rooms]);
+    return rooms.filter((roomId) => !this.roomIdToParents.has(roomId));
+  }
+
   getSpaceChildren(roomId) {
     const space = this.matrixClient.getRoom(roomId);
     const mSpaceChild = space?.currentState.getStateEvents('m.space.child');
@@ -254,15 +263,6 @@ class RoomList extends EventEmitter {
     this.matrixClient.on('Room.name', () => {
       this.emit(cons.events.roomList.ROOMLIST_UPDATED);
     });
-    this.matrixClient.on('Room.receipt', (event, room) => {
-      if (event.getType() === 'm.receipt') {
-        const content = event.getContent();
-        const userReadEventId = Object.keys(content)[0];
-        const eventReaderUserId = Object.keys(content[userReadEventId]['m.read'])[0];
-        if (eventReaderUserId !== this.matrixClient.getUserId()) return;
-        this.emit(cons.events.roomList.MY_RECEIPT_ARRIVED, room.roomId);
-      }
-    });
 
     this.matrixClient.on('RoomState.events', (mEvent) => {
       if (mEvent.getType() === 'm.space.child') {
@@ -387,16 +387,6 @@ class RoomList extends EventEmitter {
       }
       this.emit(cons.events.roomList.ROOMLIST_UPDATED);
     });
-
-    this.matrixClient.on('Room.timeline', (event, room) => {
-      const supportEvents = ['m.room.message', 'm.room.encrypted', 'm.sticker'];
-      if (!supportEvents.includes(event.getType())) return;
-
-      const lastTimelineEvent = room.timeline[room.timeline.length - 1];
-      if (lastTimelineEvent.getId() !== event.getId()) return;
-      if (event.getSender() === this.matrixClient.getUserId()) return;
-      this.emit(cons.events.roomList.EVENT_ARRIVED, room.roomId);
-    });
   }
 }
 export default RoomList;
index 6211348b37e3fea043a63055b624f58c4fa6c91c..fee81b5ede7cb8342f5efdaadc8a18cccd238387 100644 (file)
@@ -67,11 +67,9 @@ const cons = {
       ROOM_JOINED: 'ROOM_JOINED',
       ROOM_LEAVED: 'ROOM_LEAVED',
       ROOM_CREATED: 'ROOM_CREATED',
-      MY_RECEIPT_ARRIVED: 'MY_RECEIPT_ARRIVED',
-      EVENT_ARRIVED: 'EVENT_ARRIVED',
       SPACE_SHORTCUT_UPDATED: 'SPACE_SHORTCUT_UPDATED',
     },
-    notification: {
+    notifications: {
       NOTI_CHANGED: 'NOTI_CHANGED',
       FULL_READ: 'FULL_READ',
     },
index 552e9b083e86eb7ed8dddac667d03a1e5b363941..77261e54644321b631fdc6494068489c16eabefc 100644 (file)
   --bg-surface-low: hsl(64, 6%, 10%);
   --bg-surface-low-transparent: hsla(64, 6%, 14%, 0);
 
+  --bg-badge: #c4c1ab;
+
   
   /* text color | --tc-[background type]-[priority]: value */
   --tc-surface-high: rgb(255, 251, 222, 94%);