diff --git a/app/actions/definitions/collections.tsx b/app/actions/definitions/collections.tsx
index d0f4af5890..ee97fd7b33 100644
--- a/app/actions/definitions/collections.tsx
+++ b/app/actions/definitions/collections.tsx
@@ -8,8 +8,10 @@ import {
SearchIcon,
ShapesIcon,
StarredIcon,
+ SubscribeIcon,
TrashIcon,
UnstarredIcon,
+ UnsubscribeIcon,
} from "outline-icons";
import * as React from "react";
import { toast } from "sonner";
@@ -205,6 +207,66 @@ export const unstarCollection = createAction({
},
});
+export const subscribeCollection = createAction({
+ name: ({ t }) => t("Subscribe"),
+ analyticsName: "Subscribe to collection",
+ section: ActiveCollectionSection,
+ icon: ,
+ visible: ({ activeCollectionId, stores }) => {
+ if (!activeCollectionId) {
+ return false;
+ }
+
+ const collection = stores.collections.get(activeCollectionId);
+
+ return (
+ !collection?.isSubscribed &&
+ stores.policies.abilities(activeCollectionId).subscribe
+ );
+ },
+ perform: async ({ activeCollectionId, stores, t }) => {
+ if (!activeCollectionId) {
+ return;
+ }
+
+ const collection = stores.collections.get(activeCollectionId);
+
+ await collection?.subscribe();
+
+ toast.success(t("Subscribed to document notifications"));
+ },
+});
+
+export const unsubscribeCollection = createAction({
+ name: ({ t }) => t("Unsubscribe"),
+ analyticsName: "Unsubscribe from collection",
+ section: ActiveCollectionSection,
+ icon: ,
+ visible: ({ activeCollectionId, stores }) => {
+ if (!activeCollectionId) {
+ return false;
+ }
+
+ const collection = stores.collections.get(activeCollectionId);
+
+ return (
+ !!collection?.isSubscribed &&
+ stores.policies.abilities(activeCollectionId).unsubscribe
+ );
+ },
+ perform: async ({ activeCollectionId, currentUserId, stores, t }) => {
+ if (!activeCollectionId || !currentUserId) {
+ return;
+ }
+
+ const collection = stores.collections.get(activeCollectionId);
+
+ await collection?.unsubscribe();
+
+ toast.success(t("Unsubscribed from document notifications"));
+ },
+});
+
export const archiveCollection = createAction({
name: ({ t }) => `${t("Archive")}…`,
analyticsName: "Archive collection",
@@ -331,5 +393,7 @@ export const rootCollectionActions = [
createCollection,
starCollection,
unstarCollection,
+ subscribeCollection,
+ unsubscribeCollection,
deleteCollection,
];
diff --git a/app/actions/definitions/documents.tsx b/app/actions/definitions/documents.tsx
index 7f58ff2e67..d7e2950f2a 100644
--- a/app/actions/definitions/documents.tsx
+++ b/app/actions/definitions/documents.tsx
@@ -333,6 +333,7 @@ export const subscribeDocument = createAction({
const document = stores.documents.get(activeDocumentId);
return (
+ !document?.collection?.isSubscribed &&
!document?.isSubscribed &&
stores.policies.abilities(activeDocumentId).subscribe
);
@@ -361,8 +362,9 @@ export const unsubscribeDocument = createAction({
const document = stores.documents.get(activeDocumentId);
return (
- !!document?.isSubscribed &&
- stores.policies.abilities(activeDocumentId).unsubscribe
+ !!document?.collection?.isSubscribed ||
+ (!!document?.isSubscribed &&
+ stores.policies.abilities(activeDocumentId).unsubscribe)
);
},
perform: async ({ activeDocumentId, stores, currentUserId, t }) => {
diff --git a/app/components/ContextMenu/Template.tsx b/app/components/ContextMenu/Template.tsx
index cb4bf7c191..9dafb79759 100644
--- a/app/components/ContextMenu/Template.tsx
+++ b/app/components/ContextMenu/Template.tsx
@@ -20,6 +20,7 @@ import {
MenuHeading,
MenuItem as TMenuItem,
} from "~/types";
+import Tooltip from "../Tooltip";
import Header from "./Header";
import MenuItem, { MenuAnchor } from "./MenuItem";
import MouseSafeArea from "./MouseSafeArea";
@@ -167,7 +168,7 @@ function Template({ items, actions, context, showIcons, ...menu }: Props) {
}
if (item.type === "button") {
- return (
+ const menuItem = (