From 05b3c65b0fbbe359ad9402a71689421b6a4ef926 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sat, 14 Feb 2026 20:00:38 -0800 Subject: [PATCH] feat: notifications (#1947) * feat: notifications * feat: watchers * improvements * handle page move for watchers * make watchers non-blocking * more --- .../public/locales/en-US/translation.json | 18 +- .../components/layouts/global/app-header.tsx | 2 + .../comment/components/comment-dialog.tsx | 2 + .../comment/components/comment-editor.tsx | 36 ++- .../comment/components/comment.module.css | 5 +- .../components/mention/mention-list.tsx | 97 +++++-- .../components/mention/mention-suggestion.ts | 27 +- .../components/mention/mention.module.css | 8 +- .../editor/components/mention/mention.type.ts | 1 + .../components/notification-item.tsx | 148 +++++++++++ .../components/notification-list.tsx | 115 ++++++++ .../components/notification-popover.tsx | 142 ++++++++++ .../hooks/use-notification-socket.ts | 23 ++ .../notification/notification.module.css | 13 + .../notification/notification.utils.ts | 75 ++++++ .../queries/notification-query.ts | 59 +++++ .../services/notification-service.ts | 31 +++ .../notification/types/notification.types.ts | 39 +++ .../page/tree/components/space-tree.tsx | 27 +- .../search/components/search-control.tsx | 4 +- .../features/search/queries/search-query.ts | 7 +- .../src/features/user/user-provider.tsx | 2 + apps/client/src/theme.ts | 2 +- .../src/collaboration/collaboration.module.ts | 3 +- .../extensions/persistence.extension.ts | 21 ++ .../processors/history.processor.ts | 9 + .../src/collaboration/server/collab-main.ts | 3 +- apps/server/src/common/helpers/constants.ts | 4 + .../src/common/helpers/prosemirror/utils.ts | 24 ++ .../src/common/logger/internal-log-filter.ts | 36 ++- .../server/src/core/comment/comment.module.ts | 1 - .../src/core/comment/comment.service.ts | 90 ++++++- apps/server/src/core/core.module.ts | 4 + apps/server/src/core/group/group.module.ts | 1 + .../core/group/services/group-user.service.ts | 19 +- .../src/core/group/services/group.service.ts | 44 ++-- .../core/notification/dto/notification.dto.ts | 13 + .../notification/notification.constants.ts | 9 + .../notification/notification.controller.ts | 56 ++++ .../core/notification/notification.module.ts | 20 ++ .../notification/notification.processor.ts | 101 +++++++ .../core/notification/notification.service.ts | 80 ++++++ .../services/comment.notification.ts | 219 +++++++++++++++ .../services/page.notification.ts | 132 ++++++++++ apps/server/src/core/page/page.module.ts | 3 +- .../src/core/page/services/page.service.ts | 37 ++- .../space/services/space-member.service.ts | 31 ++- apps/server/src/core/space/space.module.ts | 1 + .../src/core/watcher/dto/watcher.dto.ts | 7 + .../src/core/watcher/watcher.controller.ts | 99 +++++++ .../server/src/core/watcher/watcher.module.ts | 11 + .../src/core/watcher/watcher.service.ts | 99 +++++++ .../workspace/services/workspace.service.ts | 6 + apps/server/src/database/database.module.ts | 6 + .../20260213T085259-notifications.ts | 53 ++++ .../migrations/20260213T085320-watchers.ts | 57 ++++ .../20260213T085337-backfill-watchers.ts | 29 ++ .../database/repos/group/group-user.repo.ts | 27 +- .../src/database/repos/group/group.repo.ts | 11 +- .../repos/notification/notification.repo.ts | 167 ++++++++++++ .../database/repos/space/space-member.repo.ts | 43 ++- .../database/repos/watcher/watcher.repo.ts | 249 ++++++++++++++++++ apps/server/src/database/types/db.d.ts | 30 +++ .../server/src/database/types/db.interface.ts | 4 + .../server/src/database/types/entity.types.ts | 12 + .../mail/interfaces/mail.message.ts | 1 + .../mail/processors/email.processor.ts | 14 +- .../queue/constants/queue.constants.ts | 6 + .../queue/constants/queue.interface.ts | 48 +++- .../queue/processors/backlinks.processor.ts | 135 ---------- .../processors/general-queue.processor.ts | 87 ++++++ .../src/integrations/queue/queue.module.ts | 7 +- .../queue/tasks/backlinks.task.ts | 80 ++++++ .../emails/comment-created-email.tsx | 43 +++ .../emails/comment-mention-email.tsx | 43 +++ .../emails/comment-resolved-email.tsx | 43 +++ .../emails/page-mention-email.tsx | 39 +++ apps/server/src/main.ts | 5 +- apps/server/src/ws/ws.gateway.ts | 3 +- apps/server/src/ws/ws.module.ts | 1 + 80 files changed, 3071 insertions(+), 238 deletions(-) create mode 100644 apps/client/src/features/notification/components/notification-item.tsx create mode 100644 apps/client/src/features/notification/components/notification-list.tsx create mode 100644 apps/client/src/features/notification/components/notification-popover.tsx create mode 100644 apps/client/src/features/notification/hooks/use-notification-socket.ts create mode 100644 apps/client/src/features/notification/notification.module.css create mode 100644 apps/client/src/features/notification/notification.utils.ts create mode 100644 apps/client/src/features/notification/queries/notification-query.ts create mode 100644 apps/client/src/features/notification/services/notification-service.ts create mode 100644 apps/client/src/features/notification/types/notification.types.ts create mode 100644 apps/server/src/core/notification/dto/notification.dto.ts create mode 100644 apps/server/src/core/notification/notification.constants.ts create mode 100644 apps/server/src/core/notification/notification.controller.ts create mode 100644 apps/server/src/core/notification/notification.module.ts create mode 100644 apps/server/src/core/notification/notification.processor.ts create mode 100644 apps/server/src/core/notification/notification.service.ts create mode 100644 apps/server/src/core/notification/services/comment.notification.ts create mode 100644 apps/server/src/core/notification/services/page.notification.ts create mode 100644 apps/server/src/core/watcher/dto/watcher.dto.ts create mode 100644 apps/server/src/core/watcher/watcher.controller.ts create mode 100644 apps/server/src/core/watcher/watcher.module.ts create mode 100644 apps/server/src/core/watcher/watcher.service.ts create mode 100644 apps/server/src/database/migrations/20260213T085259-notifications.ts create mode 100644 apps/server/src/database/migrations/20260213T085320-watchers.ts create mode 100644 apps/server/src/database/migrations/20260213T085337-backfill-watchers.ts create mode 100644 apps/server/src/database/repos/notification/notification.repo.ts create mode 100644 apps/server/src/database/repos/watcher/watcher.repo.ts delete mode 100644 apps/server/src/integrations/queue/processors/backlinks.processor.ts create mode 100644 apps/server/src/integrations/queue/processors/general-queue.processor.ts create mode 100644 apps/server/src/integrations/queue/tasks/backlinks.task.ts create mode 100644 apps/server/src/integrations/transactional/emails/comment-created-email.tsx create mode 100644 apps/server/src/integrations/transactional/emails/comment-mention-email.tsx create mode 100644 apps/server/src/integrations/transactional/emails/comment-resolved-email.tsx create mode 100644 apps/server/src/integrations/transactional/emails/page-mention-email.tsx diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index fa040002..aec23caa 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -590,5 +590,21 @@ "No answer available": "No answer available", "Background color": "Background color", "Highlight color": "Highlight color", - "Remove color": "Remove color" + "Remove color": "Remove color", + "Notifications": "Notifications", + "No notifications": "No notifications", + "No unread notifications": "No unread notifications", + "All notifications": "All notifications", + "Unread only": "Unread only", + "Mark all as read": "Mark all as read", + "Mark as read": "Mark as read", + "More options": "More options", + "mentioned you in a comment": "mentioned you in a comment", + "commented on a page": "commented on a page", + "resolved a comment": "resolved a comment", + "mentioned you on a page": "mentioned you on a page", + "Today": "Today", + "Yesterday": "Yesterday", + "This week": "This week", + "Older": "Older" } diff --git a/apps/client/src/components/layouts/global/app-header.tsx b/apps/client/src/components/layouts/global/app-header.tsx index eb1ca74f..58b76b71 100644 --- a/apps/client/src/components/layouts/global/app-header.tsx +++ b/apps/client/src/components/layouts/global/app-header.tsx @@ -22,6 +22,7 @@ import { searchSpotlight, shareSearchSpotlight, } from "@/features/search/constants.ts"; +import { NotificationPopover } from "@/features/notification/components/notification-popover.tsx"; const links = [{ link: APP_ROUTE.HOME, label: "Home" }]; @@ -97,6 +98,7 @@ export function AppHeader() { + {isCloud() && isTrial && trialDaysLeft !== 0 && ( { + if (document.querySelector("#mention")) return; handleDialogClose(); }); const createCommentMutation = useCreateCommentMutation(); @@ -105,6 +106,7 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) { position={{ bottom: 500, right: 50 }} withCloseButton withBorder + data-comment-dialog > diff --git a/apps/client/src/features/comment/components/comment-editor.tsx b/apps/client/src/features/comment/components/comment-editor.tsx index efb0c586..067391f4 100644 --- a/apps/client/src/features/comment/components/comment-editor.tsx +++ b/apps/client/src/features/comment/components/comment-editor.tsx @@ -1,14 +1,15 @@ -import { EditorContent, useEditor } from "@tiptap/react"; +import { EditorContent, ReactNodeViewRenderer, useEditor } from "@tiptap/react"; import { Placeholder } from "@tiptap/extension-placeholder"; -import { Underline } from "@tiptap/extension-underline"; -import { Link } from "@tiptap/extension-link"; import { StarterKit } from "@tiptap/starter-kit"; +import { Mention, LinkExtension } from "@docmost/editor-ext"; import classes from "./comment.module.css"; import { useFocusWithin } from "@mantine/hooks"; import clsx from "clsx"; import { forwardRef, useEffect, useImperativeHandle } from "react"; import { useTranslation } from "react-i18next"; import EmojiCommand from "@/features/editor/extensions/emoji-command"; +import mentionRenderItems from "@/features/editor/components/mention/mention-suggestion"; +import MentionView from "@/features/editor/components/mention/mention-view"; interface CommentEditorProps { defaultContent?: any; @@ -39,13 +40,29 @@ const CommentEditor = forwardRef( StarterKit.configure({ gapcursor: false, dropcursor: false, + link: false, }), Placeholder.configure({ placeholder: placeholder || t("Reply..."), }), - Underline, - Link, + LinkExtension, EmojiCommand, + Mention.configure({ + suggestion: { + allowSpaces: true, + items: () => [], + // @ts-ignore + render: mentionRenderItems, + }, + HTMLAttributes: { + class: "mention", + }, + }).extend({ + addNodeView() { + this.editor.isInitialized = true; + return ReactNodeViewRenderer(MentionView); + }, + }), ], editorProps: { handleDOMEvents: { @@ -60,7 +77,8 @@ const CommentEditor = forwardRef( ].includes(event.key) ) { const emojiCommand = document.querySelector("#emoji-command"); - if (emojiCommand) { + const mentionPopup = document.querySelector("#mention"); + if (emojiCommand || mentionPopup) { return true; } } @@ -108,7 +126,11 @@ const CommentEditor = forwardRef( })); return ( -
+