Hidden Typing & Read Receipts (#2230)
Some checks are pending
Deploy to Netlify (dev) / Deploy to Netlify (push) Waiting to run

* add hide activity toggle

* stop sending/receiving typing status

* send private read receipt when setting toggle is activated

* prevent showing read-receipt when feature toggle in on
This commit is contained in:
Ajay Bura 2025-02-26 21:44:53 +11:00 committed by GitHub
parent 5c94471956
commit b7e5e0db3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 165 additions and 66 deletions

View file

@ -39,6 +39,8 @@ import { getMatrixToRoom } from '../../plugins/matrix-to';
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix'; import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
import { getViaServers } from '../../plugins/via-servers'; import { getViaServers } from '../../plugins/via-servers';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
type RoomNavItemMenuProps = { type RoomNavItemMenuProps = {
room: Room; room: Room;
@ -47,13 +49,14 @@ type RoomNavItemMenuProps = {
const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>( const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
({ room, requestClose }, ref) => { ({ room, requestClose }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomUnread(room.roomId, roomToUnreadAtom); const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
const powerLevels = usePowerLevels(room); const powerLevels = usePowerLevels(room);
const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(mx, room.roomId); markAsRead(mx, room.roomId, hideActivity);
requestClose(); requestClose();
}; };

View file

@ -20,6 +20,7 @@ export function Room() {
const mx = useMatrixClient(); const mx = useMatrixClient();
const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer'); const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer');
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
const powerLevels = usePowerLevels(room); const powerLevels = usePowerLevels(room);
const members = useRoomMembers(mx, room.roomId); const members = useRoomMembers(mx, room.roomId);
@ -29,10 +30,10 @@ export function Room() {
useCallback( useCallback(
(evt) => { (evt) => {
if (isKeyHotkey('escape', evt)) { if (isKeyHotkey('escape', evt)) {
markAsRead(mx, room.roomId); markAsRead(mx, room.roomId, hideActivity);
} }
}, },
[mx, room.roomId] [mx, room.roomId, hideActivity]
) )
); );

View file

@ -127,6 +127,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline'); const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline');
const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown'); const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const commands = useCommands(mx, room); const commands = useCommands(mx, room);
const emojiBtnRef = useRef<HTMLButtonElement>(null); const emojiBtnRef = useRef<HTMLButtonElement>(null);
const roomToParents = useAtomValue(roomToParentsAtom); const roomToParents = useAtomValue(roomToParentsAtom);
@ -382,7 +383,9 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
return; return;
} }
if (!hideActivity) {
sendTypingStatus(!isEmptyEditor(editor)); sendTypingStatus(!isEmptyEditor(editor));
}
const prevWordRange = getPrevWorldRange(editor); const prevWordRange = getPrevWorldRange(editor);
const query = prevWordRange const query = prevWordRange
@ -390,7 +393,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
: undefined; : undefined;
setAutocompleteQuery(query); setAutocompleteQuery(query);
}, },
[editor, sendTypingStatus] [editor, sendTypingStatus, hideActivity]
); );
const handleCloseAutocomplete = useCallback(() => { const handleCloseAutocomplete = useCallback(() => {

View file

@ -424,6 +424,7 @@ const getRoomUnreadInfo = (room: Room, scrollTo = false) => {
export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) { export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const [messageLayout] = useSetting(settingsAtom, 'messageLayout'); const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing'); const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
const [hideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents'); const [hideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents');
@ -589,7 +590,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
// Check if the document is in focus (user is actively viewing the app), // Check if the document is in focus (user is actively viewing the app),
// and either there are no unread messages or the latest message is from the current user. // and either there are no unread messages or the latest message is from the current user.
// If either condition is met, trigger the markAsRead function to send a read receipt. // If either condition is met, trigger the markAsRead function to send a read receipt.
requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!)); requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!, hideActivity));
} }
if (!document.hasFocus() && !unreadInfo) { if (!document.hasFocus() && !unreadInfo) {
@ -613,7 +614,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
setUnreadInfo(getRoomUnreadInfo(room)); setUnreadInfo(getRoomUnreadInfo(room));
} }
}, },
[mx, room, unreadInfo] [mx, room, unreadInfo, hideActivity]
) )
); );
@ -682,15 +683,15 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const tryAutoMarkAsRead = useCallback(() => { const tryAutoMarkAsRead = useCallback(() => {
const readUptoEventId = readUptoEventIdRef.current; const readUptoEventId = readUptoEventIdRef.current;
if (!readUptoEventId) { if (!readUptoEventId) {
requestAnimationFrame(() => markAsRead(mx, room.roomId)); requestAnimationFrame(() => markAsRead(mx, room.roomId, hideActivity));
return; return;
} }
const evtTimeline = getEventTimeline(room, readUptoEventId); const evtTimeline = getEventTimeline(room, readUptoEventId);
const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward); const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward);
if (latestTimeline === room.getLiveTimeline()) { if (latestTimeline === room.getLiveTimeline()) {
requestAnimationFrame(() => markAsRead(mx, room.roomId)); requestAnimationFrame(() => markAsRead(mx, room.roomId, hideActivity));
} }
}, [mx, room]); }, [mx, room, hideActivity]);
const debounceSetAtBottom = useDebounce( const debounceSetAtBottom = useDebounce(
useCallback((entry: IntersectionObserverEntry) => { useCallback((entry: IntersectionObserverEntry) => {
@ -872,7 +873,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
}; };
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(mx, room.roomId); markAsRead(mx, room.roomId, hideActivity);
}; };
const handleOpenReply: MouseEventHandler = useCallback( const handleOpenReply: MouseEventHandler = useCallback(
@ -1047,6 +1048,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
/> />
) )
} }
hideReadReceipts={hideActivity}
> >
{mEvent.isRedacted() ? ( {mEvent.isRedacted() ? (
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} /> <RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
@ -1119,6 +1121,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
/> />
) )
} }
hideReadReceipts={hideActivity}
> >
<EncryptedContent mEvent={mEvent}> <EncryptedContent mEvent={mEvent}>
{() => { {() => {
@ -1215,6 +1218,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
/> />
) )
} }
hideReadReceipts={hideActivity}
> >
{mEvent.isRedacted() ? ( {mEvent.isRedacted() ? (
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} /> <RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
@ -1256,6 +1260,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
highlight={highlighted} highlight={highlighted}
messageSpacing={messageSpacing} messageSpacing={messageSpacing}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()} canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
> >
<EventContent <EventContent
messageLayout={messageLayout} messageLayout={messageLayout}
@ -1291,6 +1296,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
highlight={highlighted} highlight={highlighted}
messageSpacing={messageSpacing} messageSpacing={messageSpacing}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()} canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
> >
<EventContent <EventContent
messageLayout={messageLayout} messageLayout={messageLayout}
@ -1327,6 +1333,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
highlight={highlighted} highlight={highlighted}
messageSpacing={messageSpacing} messageSpacing={messageSpacing}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()} canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
> >
<EventContent <EventContent
messageLayout={messageLayout} messageLayout={messageLayout}
@ -1363,6 +1370,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
highlight={highlighted} highlight={highlighted}
messageSpacing={messageSpacing} messageSpacing={messageSpacing}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()} canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
> >
<EventContent <EventContent
messageLayout={messageLayout} messageLayout={messageLayout}
@ -1401,6 +1409,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
highlight={highlighted} highlight={highlighted}
messageSpacing={messageSpacing} messageSpacing={messageSpacing}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()} canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
> >
<EventContent <EventContent
messageLayout={messageLayout} messageLayout={messageLayout}
@ -1444,6 +1453,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
highlight={highlighted} highlight={highlighted}
messageSpacing={messageSpacing} messageSpacing={messageSpacing}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()} canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
> >
<EventContent <EventContent
messageLayout={messageLayout} messageLayout={messageLayout}

View file

@ -13,12 +13,14 @@ import { RoomTimeline } from './RoomTimeline';
import { RoomViewTyping } from './RoomViewTyping'; import { RoomViewTyping } from './RoomViewTyping';
import { RoomTombstone } from './RoomTombstone'; import { RoomTombstone } from './RoomTombstone';
import { RoomInput } from './RoomInput'; import { RoomInput } from './RoomInput';
import { RoomViewFollowing } from './RoomViewFollowing'; import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing';
import { Page } from '../../components/page'; import { Page } from '../../components/page';
import { RoomViewHeader } from './RoomViewHeader'; import { RoomViewHeader } from './RoomViewHeader';
import { useKeyDown } from '../../hooks/useKeyDown'; import { useKeyDown } from '../../hooks/useKeyDown';
import { editableActiveElement } from '../../utils/dom'; import { editableActiveElement } from '../../utils/dom';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { settingsAtom } from '../../state/settings';
import { useSetting } from '../../state/hooks/settings';
const FN_KEYS_REGEX = /^F\d+$/; const FN_KEYS_REGEX = /^F\d+$/;
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => { const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
@ -57,6 +59,8 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
const roomInputRef = useRef<HTMLDivElement>(null); const roomInputRef = useRef<HTMLDivElement>(null);
const roomViewRef = useRef<HTMLDivElement>(null); const roomViewRef = useRef<HTMLDivElement>(null);
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const { roomId } = room; const { roomId } = room;
const editor = useEditor(); const editor = useEditor();
@ -133,7 +137,7 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
</> </>
)} )}
</div> </div>
<RoomViewFollowing room={room} /> {hideActivity ? <RoomViewFollowingPlaceholder /> : <RoomViewFollowing room={room} />}
</Box> </Box>
</Page> </Page>
); );

View file

@ -1,6 +1,14 @@
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes'; import { recipe } from '@vanilla-extract/recipes';
import { DefaultReset, color, config, toRem } from 'folds'; import { DefaultReset, color, config, toRem } from 'folds';
export const RoomViewFollowingPlaceholder = style([
DefaultReset,
{
height: toRem(28),
},
]);
export const RoomViewFollowing = recipe({ export const RoomViewFollowing = recipe({
base: [ base: [
DefaultReset, DefaultReset,

View file

@ -24,6 +24,10 @@ import { useRoomEventReaders } from '../../hooks/useRoomEventReaders';
import { EventReaders } from '../../components/event-readers'; import { EventReaders } from '../../components/event-readers';
import { stopPropagation } from '../../utils/keyboard'; import { stopPropagation } from '../../utils/keyboard';
export function RoomViewFollowingPlaceholder() {
return <div className={css.RoomViewFollowingPlaceholder} />;
}
export type RoomViewFollowingProps = { export type RoomViewFollowingProps = {
room: Room; room: Room;
}; };

View file

@ -33,7 +33,7 @@ import { RoomTopicViewer } from '../../components/room-topic-viewer';
import { StateEvent } from '../../../types/matrix/room'; import { StateEvent } from '../../../types/matrix/room';
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoom } from '../../hooks/useRoom'; import { useRoom } from '../../hooks/useRoom';
import { useSetSetting } from '../../state/hooks/settings'; import { useSetSetting, useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings'; import { settingsAtom } from '../../state/settings';
import { useSpaceOptionally } from '../../hooks/useSpace'; import { useSpaceOptionally } from '../../hooks/useSpace';
import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils'; import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils';
@ -64,13 +64,14 @@ type RoomMenuProps = {
}; };
const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose }, ref) => { const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomUnread(room.roomId, roomToUnreadAtom); const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
const powerLevels = usePowerLevelsContext(); const powerLevels = usePowerLevelsContext();
const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(mx, room.roomId); markAsRead(mx, room.roomId, hideActivity);
requestClose(); requestClose();
}; };

View file

@ -671,6 +671,7 @@ export type MessageProps = {
onReactionToggle: (targetEventId: string, key: string, shortcode?: string) => void; onReactionToggle: (targetEventId: string, key: string, shortcode?: string) => void;
reply?: ReactNode; reply?: ReactNode;
reactions?: ReactNode; reactions?: ReactNode;
hideReadReceipts?: boolean;
}; };
export const Message = as<'div', MessageProps>( export const Message = as<'div', MessageProps>(
( (
@ -695,6 +696,7 @@ export const Message = as<'div', MessageProps>(
onEditId, onEditId,
reply, reply,
reactions, reactions,
hideReadReceipts,
children, children,
...props ...props
}, },
@ -992,11 +994,13 @@ export const Message = as<'div', MessageProps>(
</Text> </Text>
</MenuItem> </MenuItem>
)} )}
{!hideReadReceipts && (
<MessageReadReceiptItem <MessageReadReceiptItem
room={room} room={room}
eventId={mEvent.getId() ?? ''} eventId={mEvent.getId() ?? ''}
onClose={closeMenu} onClose={closeMenu}
/> />
)}
<MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} /> <MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} />
<MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} /> <MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
{canPinEvent && ( {canPinEvent && (
@ -1071,9 +1075,23 @@ export type EventProps = {
highlight: boolean; highlight: boolean;
canDelete?: boolean; canDelete?: boolean;
messageSpacing: MessageSpacing; messageSpacing: MessageSpacing;
hideReadReceipts?: boolean;
}; };
export const Event = as<'div', EventProps>( export const Event = as<'div', EventProps>(
({ className, room, mEvent, highlight, canDelete, messageSpacing, children, ...props }, ref) => { (
{
className,
room,
mEvent,
highlight,
canDelete,
messageSpacing,
hideReadReceipts,
children,
...props
},
ref
) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const [hover, setHover] = useState(false); const [hover, setHover] = useState(false);
const { hoverProps } = useHover({ onHoverChange: setHover }); const { hoverProps } = useHover({ onHoverChange: setHover });
@ -1138,11 +1156,13 @@ export const Event = as<'div', EventProps>(
> >
<Menu {...props} ref={ref}> <Menu {...props} ref={ref}>
<Box direction="Column" gap="100" className={css.MessageMenuGroup}> <Box direction="Column" gap="100" className={css.MessageMenuGroup}>
{!hideReadReceipts && (
<MessageReadReceiptItem <MessageReadReceiptItem
room={room} room={room}
eventId={mEvent.getId() ?? ''} eventId={mEvent.getId() ?? ''}
onClose={closeMenu} onClose={closeMenu}
/> />
)}
<MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} /> <MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} />
<MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} /> <MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
</Box> </Box>

View file

@ -344,6 +344,7 @@ function Appearance() {
function Editor() { function Editor() {
const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline'); const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown'); const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
const [hideActivity, setHideActivity] = useSetting(settingsAtom, 'hideActivity');
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
@ -363,6 +364,13 @@ function Editor() {
after={<Switch variant="Primary" value={isMarkdown} onChange={setIsMarkdown} />} after={<Switch variant="Primary" value={isMarkdown} onChange={setIsMarkdown} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile
title="Hide Typing & Read Receipts"
description="Turn off both typing status and read receipts to keep your activity private."
after={<Switch variant="Primary" value={hideActivity} onChange={setHideActivity} />}
/>
</SequenceCard>
</Box> </Box>
); );
} }
@ -555,7 +563,13 @@ function Messages() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Disable Media Auto Load" title="Disable Media Auto Load"
after={<Switch variant="Primary" value={!mediaAutoLoad} onChange={(v) => setMediaAutoLoad(!v)} />} after={
<Switch
variant="Primary"
value={!mediaAutoLoad}
onChange={(v) => setMediaAutoLoad(!v)}
/>
}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">

View file

@ -45,18 +45,21 @@ import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCatego
import { useRoomsUnread } from '../../../state/hooks/unread'; import { useRoomsUnread } from '../../../state/hooks/unread';
import { markAsRead } from '../../../../client/action/notifications'; import { markAsRead } from '../../../../client/action/notifications';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
type DirectMenuProps = { type DirectMenuProps = {
requestClose: () => void; requestClose: () => void;
}; };
const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => { const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const orphanRooms = useDirectRooms(); const orphanRooms = useDirectRooms();
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
if (!unread) return; if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(mx, rId)); orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
requestClose(); requestClose();
}; };

View file

@ -48,18 +48,21 @@ import { useRoomsUnread } from '../../../state/hooks/unread';
import { markAsRead } from '../../../../client/action/notifications'; import { markAsRead } from '../../../../client/action/notifications';
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories'; import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
type HomeMenuProps = { type HomeMenuProps = {
requestClose: () => void; requestClose: () => void;
}; };
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => { const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
const orphanRooms = useHomeRooms(); const orphanRooms = useHomeRooms();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const mx = useMatrixClient(); const mx = useMatrixClient();
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
if (!unread) return; if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(mx, rId)); orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
requestClose(); requestClose();
}; };

View file

@ -182,6 +182,7 @@ type RoomNotificationsGroupProps = {
notifications: INotification[]; notifications: INotification[];
mediaAutoLoad?: boolean; mediaAutoLoad?: boolean;
urlPreview?: boolean; urlPreview?: boolean;
hideActivity: boolean;
onOpen: (roomId: string, eventId: string) => void; onOpen: (roomId: string, eventId: string) => void;
}; };
function RoomNotificationsGroupComp({ function RoomNotificationsGroupComp({
@ -189,6 +190,7 @@ function RoomNotificationsGroupComp({
notifications, notifications,
mediaAutoLoad, mediaAutoLoad,
urlPreview, urlPreview,
hideActivity,
onOpen, onOpen,
}: RoomNotificationsGroupProps) { }: RoomNotificationsGroupProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
@ -362,7 +364,7 @@ function RoomNotificationsGroupComp({
onOpen(room.roomId, eventId); onOpen(room.roomId, eventId);
}; };
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(mx, room.roomId); markAsRead(mx, room.roomId, hideActivity);
}; };
return ( return (
@ -496,6 +498,7 @@ const DEFAULT_REFRESH_MS = 7000;
export function Notifications() { export function Notifications() {
const mx = useMatrixClient(); const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
const [urlPreview] = useSetting(settingsAtom, 'urlPreview'); const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
@ -656,6 +659,7 @@ export function Notifications() {
notifications={group.notifications} notifications={group.notifications}
mediaAutoLoad={mediaAutoLoad} mediaAutoLoad={mediaAutoLoad}
urlPreview={urlPreview} urlPreview={urlPreview}
hideActivity={hideActivity}
onOpen={navigateRoom} onOpen={navigateRoom}
/> />
</VirtualTile> </VirtualTile>

View file

@ -23,18 +23,21 @@ import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath';
import { useDirectRooms } from '../direct/useDirectRooms'; import { useDirectRooms } from '../direct/useDirectRooms';
import { markAsRead } from '../../../../client/action/notifications'; import { markAsRead } from '../../../../client/action/notifications';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { settingsAtom } from '../../../state/settings';
import { useSetting } from '../../../state/hooks/settings';
type DirectMenuProps = { type DirectMenuProps = {
requestClose: () => void; requestClose: () => void;
}; };
const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => { const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => {
const orphanRooms = useDirectRooms(); const orphanRooms = useDirectRooms();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const mx = useMatrixClient(); const mx = useMatrixClient();
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
if (!unread) return; if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(mx, rId)); orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
requestClose(); requestClose();
}; };

View file

@ -24,18 +24,21 @@ import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath';
import { useHomeRooms } from '../home/useHomeRooms'; import { useHomeRooms } from '../home/useHomeRooms';
import { markAsRead } from '../../../../client/action/notifications'; import { markAsRead } from '../../../../client/action/notifications';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
type HomeMenuProps = { type HomeMenuProps = {
requestClose: () => void; requestClose: () => void;
}; };
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => { const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
const orphanRooms = useHomeRooms(); const orphanRooms = useHomeRooms();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const mx = useMatrixClient(); const mx = useMatrixClient();
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
if (!unread) return; if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(mx, rId)); orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
requestClose(); requestClose();
}; };

View file

@ -88,6 +88,8 @@ import { getMatrixToRoom } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers'; import { getViaServers } from '../../../plugins/via-servers';
import { getRoomAvatarUrl } from '../../../utils/room'; import { getRoomAvatarUrl } from '../../../utils/room';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
type SpaceMenuProps = { type SpaceMenuProps = {
room: Room; room: Room;
@ -97,6 +99,7 @@ type SpaceMenuProps = {
const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>( const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
({ room, requestClose, onUnpin }, ref) => { ({ room, requestClose, onUnpin }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const roomToParents = useAtomValue(roomToParentsAtom); const roomToParents = useAtomValue(roomToParentsAtom);
const powerLevels = usePowerLevels(room); const powerLevels = usePowerLevels(room);
const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
@ -110,7 +113,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
const unread = useRoomsUnread(allChild, roomToUnreadAtom); const unread = useRoomsUnread(allChild, roomToUnreadAtom);
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
allChild.forEach((childRoomId) => markAsRead(mx, childRoomId)); allChild.forEach((childRoomId) => markAsRead(mx, childRoomId, hideActivity));
requestClose(); requestClose();
}; };

View file

@ -69,6 +69,8 @@ import { StateEvent } from '../../../../types/matrix/room';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoom } from '../../../plugins/matrix-to'; import { getMatrixToRoom } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers'; import { getViaServers } from '../../../plugins/via-servers';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
type SpaceMenuProps = { type SpaceMenuProps = {
room: Room; room: Room;
@ -76,6 +78,7 @@ type SpaceMenuProps = {
}; };
const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClose }, ref) => { const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClose }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const roomToParents = useAtomValue(roomToParentsAtom); const roomToParents = useAtomValue(roomToParentsAtom);
const powerLevels = usePowerLevels(room); const powerLevels = usePowerLevels(room);
const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
@ -89,7 +92,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClo
const unread = useRoomsUnread(allChild, roomToUnreadAtom); const unread = useRoomsUnread(allChild, roomToUnreadAtom);
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
allChild.forEach((childRoomId) => markAsRead(mx, childRoomId)); allChild.forEach((childRoomId) => markAsRead(mx, childRoomId, hideActivity));
requestClose(); requestClose();
}; };

View file

@ -228,7 +228,6 @@ export const useBindRoomToUnreadAtom = (
useEffect(() => { useEffect(() => {
const handleReceipt = (mEvent: MatrixEvent, room: Room) => { const handleReceipt = (mEvent: MatrixEvent, room: Room) => {
if (mEvent.getType() === 'm.receipt') {
const myUserId = mx.getUserId(); const myUserId = mx.getUserId();
if (!myUserId) return; if (!myUserId) return;
if (room.isSpaceRoom()) return; if (room.isSpaceRoom()) return;
@ -242,7 +241,6 @@ export const useBindRoomToUnreadAtom = (
if (isMyReceipt) { if (isMyReceipt) {
setUnreadAtom({ type: 'DELETE', roomId: room.roomId }); setUnreadAtom({ type: 'DELETE', roomId: room.roomId });
} }
}
}; };
mx.on(RoomEvent.Receipt, handleReceipt); mx.on(RoomEvent.Receipt, handleReceipt);
return () => { return () => {

View file

@ -17,6 +17,7 @@ export interface Settings {
editorToolbar: boolean; editorToolbar: boolean;
twitterEmoji: boolean; twitterEmoji: boolean;
pageZoom: number; pageZoom: number;
hideActivity: boolean;
isPeopleDrawer: boolean; isPeopleDrawer: boolean;
memberSortFilterIndex: number; memberSortFilterIndex: number;
@ -45,6 +46,7 @@ const defaultSettings: Settings = {
editorToolbar: false, editorToolbar: false,
twitterEmoji: false, twitterEmoji: false,
pageZoom: 100, pageZoom: 100,
hideActivity: false,
isPeopleDrawer: true, isPeopleDrawer: true,
memberSortFilterIndex: 0, memberSortFilterIndex: 0,

View file

@ -2,6 +2,8 @@ import produce from 'immer';
import { atom, useSetAtom } from 'jotai'; import { atom, useSetAtom } from 'jotai';
import { MatrixClient, RoomMemberEvent, RoomMemberEventHandlerMap } from 'matrix-js-sdk'; import { MatrixClient, RoomMemberEvent, RoomMemberEventHandlerMap } from 'matrix-js-sdk';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useSetting } from './hooks/settings';
import { settingsAtom } from './settings';
export const TYPING_TIMEOUT_MS = 5000; // 5 seconds export const TYPING_TIMEOUT_MS = 5000; // 5 seconds
@ -127,12 +129,16 @@ export const useBindRoomIdToTypingMembersAtom = (
typingMembersAtom: typeof roomIdToTypingMembersAtom typingMembersAtom: typeof roomIdToTypingMembersAtom
) => { ) => {
const setTypingMembers = useSetAtom(typingMembersAtom); const setTypingMembers = useSetAtom(typingMembersAtom);
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
useEffect(() => { useEffect(() => {
const handleTypingEvent: RoomMemberEventHandlerMap[RoomMemberEvent.Typing] = ( const handleTypingEvent: RoomMemberEventHandlerMap[RoomMemberEvent.Typing] = (
event, event,
member member
) => { ) => {
if (hideActivity) {
return;
}
setTypingMembers({ setTypingMembers({
type: member.typing ? 'PUT' : 'DELETE', type: member.typing ? 'PUT' : 'DELETE',
roomId: member.roomId, roomId: member.roomId,
@ -145,5 +151,5 @@ export const useBindRoomIdToTypingMembersAtom = (
return () => { return () => {
mx.removeListener(RoomMemberEvent.Typing, handleTypingEvent); mx.removeListener(RoomMemberEvent.Typing, handleTypingEvent);
}; };
}, [mx, setTypingMembers]); }, [mx, setTypingMembers, hideActivity]);
}; };

View file

@ -1,6 +1,6 @@
import { MatrixClient } from "matrix-js-sdk"; import { MatrixClient, ReceiptType } from 'matrix-js-sdk';
export async function markAsRead(mx: MatrixClient, roomId: string) { export async function markAsRead(mx: MatrixClient, roomId: string, privateReceipt: boolean) {
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
if (!room) return; if (!room) return;
@ -19,5 +19,8 @@ export async function markAsRead(mx: MatrixClient, roomId: string) {
const latestEvent = getLatestValidEvent(); const latestEvent = getLatestValidEvent();
if (latestEvent === null) return; if (latestEvent === null) return;
await mx.sendReadReceipt(latestEvent); await mx.sendReadReceipt(
latestEvent,
privateReceipt ? ReceiptType.ReadPrivate : ReceiptType.Read
);
} }