mirror of
https://github.com/docmost/docmost.git
synced 2026-06-11 02:36:56 +08:00
feat: page update notifications (#2074)
* feat: watchers notification and email preferences * fix: email copy * digests * clean up * fix * clean up * move backlinks queue-up to history processor * fix * fix keys * feat: group notifications * filter * adjust email digest window
This commit is contained in:
@@ -3,6 +3,8 @@ import {
|
||||
IconArrowRight,
|
||||
IconArrowsHorizontal,
|
||||
IconDots,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
IconFileExport,
|
||||
IconHistory,
|
||||
IconLink,
|
||||
@@ -40,6 +42,11 @@ import { PageStateSegmentedControl } from "@/features/user/components/page-state
|
||||
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
|
||||
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
|
||||
import { PageShareModal } from "@/ee/page-permission";
|
||||
import {
|
||||
useWatchStatusQuery,
|
||||
useWatchPageMutation,
|
||||
useUnwatchPageMutation,
|
||||
} from "@/features/page/queries/watcher-query";
|
||||
|
||||
interface PageHeaderMenuProps {
|
||||
readOnly?: boolean;
|
||||
@@ -123,6 +130,9 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
|
||||
] = useDisclosure(false);
|
||||
const [pageEditor] = useAtom(pageEditorAtom);
|
||||
const pageUpdatedAt = useTimeAgo(page?.updatedAt);
|
||||
const { data: watchStatus } = useWatchStatusQuery(page?.id);
|
||||
const watchPage = useWatchPageMutation();
|
||||
const unwatchPage = useUnwatchPageMutation();
|
||||
|
||||
const handleCopyLink = () => {
|
||||
const pageUrl =
|
||||
@@ -185,6 +195,23 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
|
||||
>
|
||||
{t("Copy as Markdown")}
|
||||
</Menu.Item>
|
||||
|
||||
{watchStatus?.watching ? (
|
||||
<Menu.Item
|
||||
leftSection={<IconEyeOff size={16} />}
|
||||
onClick={() => unwatchPage.mutate(page.id)}
|
||||
>
|
||||
{t("Stop watching")}
|
||||
</Menu.Item>
|
||||
) : (
|
||||
<Menu.Item
|
||||
leftSection={<IconEye size={16} />}
|
||||
onClick={() => watchPage.mutate(page.id)}
|
||||
>
|
||||
{t("Watch page")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
<Menu.Item leftSection={<IconArrowsHorizontal size={16} />}>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
watchPage,
|
||||
unwatchPage,
|
||||
getWatchStatus,
|
||||
} from "@/features/page/services/watcher-service";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const WATCHER_KEY = "watcher";
|
||||
|
||||
export function useWatchStatusQuery(pageId: string) {
|
||||
return useQuery({
|
||||
queryKey: [WATCHER_KEY, pageId],
|
||||
queryFn: () => getWatchStatus(pageId),
|
||||
enabled: !!pageId,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useWatchPageMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
return useMutation({
|
||||
mutationFn: (pageId: string) => watchPage(pageId),
|
||||
onSuccess: (_data, pageId) => {
|
||||
queryClient.setQueryData([WATCHER_KEY, pageId], { watching: true });
|
||||
notifications.show({ message: t("You are now watching this page") });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnwatchPageMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
return useMutation({
|
||||
mutationFn: (pageId: string) => unwatchPage(pageId),
|
||||
onSuccess: (_data, pageId) => {
|
||||
queryClient.setQueryData([WATCHER_KEY, pageId], { watching: false });
|
||||
notifications.show({ message: t("You are no longer watching this page") });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import api from "@/lib/api-client";
|
||||
|
||||
export async function watchPage(pageId: string): Promise<{ watching: boolean }> {
|
||||
const req = await api.post<{ watching: boolean }>("/pages/watch", { pageId });
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function unwatchPage(pageId: string): Promise<{ watching: boolean }> {
|
||||
const req = await api.post<{ watching: boolean }>("/pages/unwatch", { pageId });
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function getWatchStatus(pageId: string): Promise<{ watching: boolean }> {
|
||||
const req = await api.post<{ watching: boolean }>("/pages/watch-status", { pageId });
|
||||
return req.data;
|
||||
}
|
||||
Reference in New Issue
Block a user