From abaa56c8f1d9162744eb72fc8e829e970050862d Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Fri, 2 Feb 2024 23:09:18 -0500 Subject: [PATCH] feat: Badge documents in sidebar that have been newly shared with you --- .../Notifications/NotificationListItem.tsx | 13 ++----- .../Sidebar/components/SharedWithMeLink.tsx | 9 ++++- .../Sidebar/components/SidebarLink.tsx | 4 +++ app/components/UnreadBadge.tsx | 12 +++++++ app/models/Document.ts | 35 ++++++++++++++++++- app/models/Notification.ts | 3 +- app/stores/ViewsStore.ts | 1 + 7 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 app/components/UnreadBadge.tsx diff --git a/app/components/Notifications/NotificationListItem.tsx b/app/components/Notifications/NotificationListItem.tsx index 58bedd34b..2e3b86b14 100644 --- a/app/components/Notifications/NotificationListItem.tsx +++ b/app/components/Notifications/NotificationListItem.tsx @@ -14,6 +14,7 @@ import { AvatarSize } from "../Avatar/Avatar"; import Flex from "../Flex"; import Text from "../Text"; import Time from "../Time"; +import { UnreadBadge } from "../UnreadBadge"; type Props = { notification: Notification; @@ -65,7 +66,7 @@ function NotificationListItem({ notification, onNavigate }: Props) { /> )} - {notification.viewedAt ? null : } + {notification.viewedAt ? null : } ); @@ -100,14 +101,4 @@ const Container = styled(Flex)<{ $unread: boolean }>` } `; -const Unread = styled.div` - width: 8px; - height: 8px; - background: ${s("accent")}; - border-radius: 8px; - align-self: center; - position: absolute; - right: 20px; -`; - export default observer(NotificationListItem); diff --git a/app/components/Sidebar/components/SharedWithMeLink.tsx b/app/components/Sidebar/components/SharedWithMeLink.tsx index 1181037cf..9460bed4a 100644 --- a/app/components/Sidebar/components/SharedWithMeLink.tsx +++ b/app/components/Sidebar/components/SharedWithMeLink.tsx @@ -2,6 +2,7 @@ import fractionalIndex from "fractional-index"; import { observer } from "mobx-react"; import * as React from "react"; import styled from "styled-components"; +import { NotificationEventType } from "@shared/types"; import UserMembership from "~/models/UserMembership"; import Fade from "~/components/Fade"; import useBoolean from "~/hooks/useBoolean"; @@ -99,7 +100,7 @@ function SharedWithMeLink({ userMembership }: Props) { + notification.event === NotificationEventType.AddUserToDocument + ).length > 0 + } showActions={menuOpen} menu={ document && !isDragging ? ( diff --git a/app/components/Sidebar/components/SidebarLink.tsx b/app/components/Sidebar/components/SidebarLink.tsx index cbf2cd1a5..ab716bacf 100644 --- a/app/components/Sidebar/components/SidebarLink.tsx +++ b/app/components/Sidebar/components/SidebarLink.tsx @@ -7,6 +7,7 @@ import { NavigationNode } from "@shared/types"; import EventBoundary from "~/components/EventBoundary"; import EmojiIcon from "~/components/Icons/EmojiIcon"; import NudeButton from "~/components/NudeButton"; +import { UnreadBadge } from "~/components/UnreadBadge"; import useUnmount from "~/hooks/useUnmount"; import { undraggableOnDesktop } from "~/styles"; import Disclosure from "./Disclosure"; @@ -29,6 +30,7 @@ type Props = Omit & { emoji?: string | null; label?: React.ReactNode; menu?: React.ReactNode; + unreadBadge?: boolean; showActions?: boolean; disabled?: boolean; active?: boolean; @@ -64,6 +66,7 @@ function SidebarLink( expanded, onDisclosureClick, disabled, + unreadBadge, ...rest }: Props, ref: React.RefObject @@ -141,6 +144,7 @@ function SidebarLink( {icon && {icon}} {emoji && } + {unreadBadge && } {menu && {menu}} diff --git a/app/components/UnreadBadge.tsx b/app/components/UnreadBadge.tsx new file mode 100644 index 000000000..5cf1ba881 --- /dev/null +++ b/app/components/UnreadBadge.tsx @@ -0,0 +1,12 @@ +import styled from "styled-components"; +import { s } from "@shared/styles"; + +export const UnreadBadge = styled.div` + width: 8px; + height: 8px; + background: ${s("accent")}; + border-radius: 8px; + align-self: center; + position: absolute; + right: 4px; +`; diff --git a/app/models/Document.ts b/app/models/Document.ts index fa224ee97..4dea9c234 100644 --- a/app/models/Document.ts +++ b/app/models/Document.ts @@ -2,7 +2,7 @@ import { addDays, differenceInDays } from "date-fns"; import i18n, { t } from "i18next"; import floor from "lodash/floor"; import { action, autorun, computed, observable, set } from "mobx"; -import { ExportContentType } from "@shared/types"; +import { ExportContentType, NotificationEventType } from "@shared/types"; import type { JSONObject, NavigationNode } from "@shared/types"; import Storage from "@shared/utils/Storage"; import { isRTL } from "@shared/utils/rtl"; @@ -13,6 +13,7 @@ import type { Properties } from "~/types"; import { client } from "~/utils/ApiClient"; import { settingsPath } from "~/utils/routeHelpers"; import Collection from "./Collection"; +import Notification from "./Notification"; import View from "./View"; import ParanoidModel from "./base/ParanoidModel"; import Field from "./decorators/Field"; @@ -160,6 +161,24 @@ export default class Document extends ParanoidModel { @observable isCollectionDeleted: boolean; + /** + * Returns the notifications associated with this document. + */ + @computed + get notifications(): Notification[] { + return this.store.rootStore.notifications.filter( + (notification: Notification) => notification.documentId === this.id + ); + } + + /** + * Returns the unread notifications associated with this document. + */ + @computed + get unreadNotifications(): Notification[] { + return this.notifications.filter((notification) => !notification.viewedAt); + } + /** * Returns the direction of the document text, either "rtl" or "ltr" */ @@ -391,6 +410,20 @@ export default class Document extends ParanoidModel { return; } + // Mark associated unread notifications as read when the document is viewed + this.store.rootStore.notifications + .filter( + (notification: Notification) => + !notification.viewedAt && + notification.documentId === this.id && + [ + NotificationEventType.AddUserToDocument, + NotificationEventType.UpdateDocument, + NotificationEventType.PublishDocument, + ].includes(notification.event) + ) + .forEach((notification) => notification.markAsRead()); + this.lastViewedAt = new Date().toString(); return this.store.rootStore.views.create({ diff --git a/app/models/Notification.ts b/app/models/Notification.ts index 54438017a..a2773c8ed 100644 --- a/app/models/Notification.ts +++ b/app/models/Notification.ts @@ -1,5 +1,5 @@ import { TFunction } from "i18next"; -import { action, observable } from "mobx"; +import { action, computed, observable } from "mobx"; import { NotificationEventType } from "@shared/types"; import { collectionPath, @@ -154,6 +154,7 @@ class Notification extends Model { * * @returns The router path. */ + @computed get path() { switch (this.event) { case NotificationEventType.PublishDocument: diff --git a/app/stores/ViewsStore.ts b/app/stores/ViewsStore.ts index 9f6effa59..6da6c1cb6 100644 --- a/app/stores/ViewsStore.ts +++ b/app/stores/ViewsStore.ts @@ -34,6 +34,7 @@ export default class ViewsStore extends Store { if (!view) { return; } + view.touch(); } }