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();
}
}