feat(P5C.T2): per-conv 'mentions only' toggle + notification gate
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
muteDurationToIso,
|
||||
setConversationArchived,
|
||||
setConversationMentionsOnly,
|
||||
setConversationMutedUntil,
|
||||
} from '@chat-app/shared/chat';
|
||||
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
||||
@@ -8,12 +9,13 @@ import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { ArchiveIcon, BellIcon, BellOffIcon, MoreVerticalIcon } from './icons';
|
||||
import { ArchiveIcon, AtIcon, BellIcon, BellOffIcon, MoreVerticalIcon } from './icons';
|
||||
|
||||
interface Props {
|
||||
conversationId: string;
|
||||
archived: boolean;
|
||||
mutedUntil: string | null;
|
||||
mentionsOnly: boolean;
|
||||
}
|
||||
|
||||
interface MuteOption {
|
||||
@@ -50,7 +52,7 @@ interface MenuPos {
|
||||
// escape the sidebar's `overflow-y-auto` clipping context. Position is
|
||||
// computed from the trigger's bounding rect — menu anchors right-aligned
|
||||
// under the trigger so it doesn't push off-screen on narrow windows.
|
||||
export function ConversationRowMenu({ conversationId, archived, mutedUntil }: Props) {
|
||||
export function ConversationRowMenu({ conversationId, archived, mutedUntil, mentionsOnly }: Props) {
|
||||
const { t } = useTranslation(['app']);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [submenuOpen, setSubmenuOpen] = useState<'mute' | null>(null);
|
||||
@@ -145,6 +147,21 @@ export function ConversationRowMenu({ conversationId, archived, mutedUntil }: Pr
|
||||
[conversationId],
|
||||
);
|
||||
|
||||
const handleMentionsOnly = useCallback(
|
||||
async (next: boolean) => {
|
||||
setOpen(false);
|
||||
try {
|
||||
await setConversationMentionsOnly(supabase, {
|
||||
conversationId,
|
||||
mentionsOnly: next,
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
console.warn('mentions-only toggle failed', err);
|
||||
}
|
||||
},
|
||||
[conversationId],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
@@ -196,6 +213,16 @@ export function ConversationRowMenu({ conversationId, archived, mutedUntil }: Pr
|
||||
}}
|
||||
hasSubmenu={!isMuted}
|
||||
/>
|
||||
<MenuItem
|
||||
icon={<AtIcon className="h-4 w-4" />}
|
||||
label={
|
||||
(mentionsOnly ? '✓ ' : '') +
|
||||
t('app:chats.mentions_only', {
|
||||
defaultValue: 'Nur bei @Mentions benachrichtigen',
|
||||
})
|
||||
}
|
||||
onClick={() => void handleMentionsOnly(!mentionsOnly)}
|
||||
/>
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
|
||||
@@ -225,7 +225,12 @@ export function ConversationsProvider({ children }: { children: ReactNode }) {
|
||||
(c) => c.id === row.conversation_id,
|
||||
);
|
||||
const muted = isConversationMuted(convForMute?.mutedUntil ?? null);
|
||||
if (presenceRef.current !== 'dnd' && !muted) {
|
||||
// "Mentions only" silences non-mention messages here. Mentions
|
||||
// still fire via the independent useMentionNotifications
|
||||
// subscription on message_mentions, so this branch doesn't
|
||||
// lose the @-alerts.
|
||||
const mentionsOnly = convForMute?.mentionsOnly ?? false;
|
||||
if (presenceRef.current !== 'dnd' && !muted && !mentionsOnly) {
|
||||
playNotificationTone();
|
||||
const conv = conversationsRef.current.find(
|
||||
(c) => c.id === row.conversation_id,
|
||||
|
||||
@@ -425,6 +425,7 @@ function ConversationRow({
|
||||
conversationId={item.id}
|
||||
archived={item.archived}
|
||||
mutedUntil={item.mutedUntil}
|
||||
mentionsOnly={item.mentionsOnly}
|
||||
/>
|
||||
</NavLink>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user