feat: Badge documents in sidebar that have been newly shared with you
This commit is contained in:
@@ -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) {
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
{notification.viewedAt ? null : <Unread />}
|
||||
{notification.viewedAt ? null : <UnreadBadge style={{ right: 20 }} />}
|
||||
</Container>
|
||||
</StyledLink>
|
||||
);
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
<SidebarLink
|
||||
depth={0}
|
||||
to={{
|
||||
pathname: document.url,
|
||||
pathname: document.path,
|
||||
state: { starred: true },
|
||||
}}
|
||||
expanded={hasChildDocuments && !isDragging ? expanded : undefined}
|
||||
@@ -107,6 +108,12 @@ function SharedWithMeLink({ userMembership }: Props) {
|
||||
icon={icon}
|
||||
label={label}
|
||||
exact={false}
|
||||
unreadBadge={
|
||||
document.unreadNotifications.filter(
|
||||
(notification) =>
|
||||
notification.event === NotificationEventType.AddUserToDocument
|
||||
).length > 0
|
||||
}
|
||||
showActions={menuOpen}
|
||||
menu={
|
||||
document && !isDragging ? (
|
||||
|
||||
@@ -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<NavLinkProps, "to"> & {
|
||||
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<HTMLAnchorElement>
|
||||
@@ -141,6 +144,7 @@ function SidebarLink(
|
||||
{icon && <IconWrapper>{icon}</IconWrapper>}
|
||||
{emoji && <EmojiIcon emoji={emoji} />}
|
||||
<Label>{label}</Label>
|
||||
{unreadBadge && <UnreadBadge />}
|
||||
</Content>
|
||||
</Link>
|
||||
{menu && <Actions showActions={showActions}>{menu}</Actions>}
|
||||
|
||||
12
app/components/UnreadBadge.tsx
Normal file
12
app/components/UnreadBadge.tsx
Normal file
@@ -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;
|
||||
`;
|
||||
@@ -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({
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -34,6 +34,7 @@ export default class ViewsStore extends Store<View> {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
view.touch();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user