mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-13 18:50:29 +03:00
Hidden Typing & Read Receipts (#2230)
* 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:
parent
5c94471956
commit
b7e5e0db3e
21 changed files with 165 additions and 66 deletions
|
|
@ -39,6 +39,8 @@ import { getMatrixToRoom } from '../../plugins/matrix-to';
|
|||
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
|
||||
import { getViaServers } from '../../plugins/via-servers';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
|
||||
type RoomNavItemMenuProps = {
|
||||
room: Room;
|
||||
|
|
@ -47,13 +49,14 @@ type RoomNavItemMenuProps = {
|
|||
const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
|
||||
({ room, requestClose }, ref) => {
|
||||
const mx = useMatrixClient();
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
||||
const powerLevels = usePowerLevels(room);
|
||||
const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
|
||||
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
|
||||
|
||||
const handleMarkAsRead = () => {
|
||||
markAsRead(mx, room.roomId);
|
||||
markAsRead(mx, room.roomId, hideActivity);
|
||||
requestClose();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export function Room() {
|
|||
const mx = useMatrixClient();
|
||||
|
||||
const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer');
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
const screenSize = useScreenSizeContext();
|
||||
const powerLevels = usePowerLevels(room);
|
||||
const members = useRoomMembers(mx, room.roomId);
|
||||
|
|
@ -29,10 +30,10 @@ export function Room() {
|
|||
useCallback(
|
||||
(evt) => {
|
||||
if (isKeyHotkey('escape', evt)) {
|
||||
markAsRead(mx, room.roomId);
|
||||
markAsRead(mx, room.roomId, hideActivity);
|
||||
}
|
||||
},
|
||||
[mx, room.roomId]
|
||||
[mx, room.roomId, hideActivity]
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||
const useAuthentication = useMediaAuthentication();
|
||||
const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
||||
const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
const commands = useCommands(mx, room);
|
||||
const emojiBtnRef = useRef<HTMLButtonElement>(null);
|
||||
const roomToParents = useAtomValue(roomToParentsAtom);
|
||||
|
|
@ -382,7 +383,9 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||
return;
|
||||
}
|
||||
|
||||
sendTypingStatus(!isEmptyEditor(editor));
|
||||
if (!hideActivity) {
|
||||
sendTypingStatus(!isEmptyEditor(editor));
|
||||
}
|
||||
|
||||
const prevWordRange = getPrevWorldRange(editor);
|
||||
const query = prevWordRange
|
||||
|
|
@ -390,7 +393,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||
: undefined;
|
||||
setAutocompleteQuery(query);
|
||||
},
|
||||
[editor, sendTypingStatus]
|
||||
[editor, sendTypingStatus, hideActivity]
|
||||
);
|
||||
|
||||
const handleCloseAutocomplete = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -424,6 +424,7 @@ const getRoomUnreadInfo = (room: Room, scrollTo = false) => {
|
|||
export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
|
||||
const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
|
||||
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),
|
||||
// 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.
|
||||
requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!));
|
||||
requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!, hideActivity));
|
||||
}
|
||||
|
||||
if (!document.hasFocus() && !unreadInfo) {
|
||||
|
|
@ -613,7 +614,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
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 readUptoEventId = readUptoEventIdRef.current;
|
||||
if (!readUptoEventId) {
|
||||
requestAnimationFrame(() => markAsRead(mx, room.roomId));
|
||||
requestAnimationFrame(() => markAsRead(mx, room.roomId, hideActivity));
|
||||
return;
|
||||
}
|
||||
const evtTimeline = getEventTimeline(room, readUptoEventId);
|
||||
const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward);
|
||||
if (latestTimeline === room.getLiveTimeline()) {
|
||||
requestAnimationFrame(() => markAsRead(mx, room.roomId));
|
||||
requestAnimationFrame(() => markAsRead(mx, room.roomId, hideActivity));
|
||||
}
|
||||
}, [mx, room]);
|
||||
}, [mx, room, hideActivity]);
|
||||
|
||||
const debounceSetAtBottom = useDebounce(
|
||||
useCallback((entry: IntersectionObserverEntry) => {
|
||||
|
|
@ -872,7 +873,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
};
|
||||
|
||||
const handleMarkAsRead = () => {
|
||||
markAsRead(mx, room.roomId);
|
||||
markAsRead(mx, room.roomId, hideActivity);
|
||||
};
|
||||
|
||||
const handleOpenReply: MouseEventHandler = useCallback(
|
||||
|
|
@ -1047,6 +1048,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
/>
|
||||
)
|
||||
}
|
||||
hideReadReceipts={hideActivity}
|
||||
>
|
||||
{mEvent.isRedacted() ? (
|
||||
<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}>
|
||||
{() => {
|
||||
|
|
@ -1215,6 +1218,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
/>
|
||||
)
|
||||
}
|
||||
hideReadReceipts={hideActivity}
|
||||
>
|
||||
{mEvent.isRedacted() ? (
|
||||
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
||||
|
|
@ -1256,6 +1260,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
|
|
@ -1291,6 +1296,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
|
|
@ -1327,6 +1333,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
|
|
@ -1363,6 +1370,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
|
|
@ -1401,6 +1409,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
|
|
@ -1444,6 +1453,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ import { RoomTimeline } from './RoomTimeline';
|
|||
import { RoomViewTyping } from './RoomViewTyping';
|
||||
import { RoomTombstone } from './RoomTombstone';
|
||||
import { RoomInput } from './RoomInput';
|
||||
import { RoomViewFollowing } from './RoomViewFollowing';
|
||||
import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing';
|
||||
import { Page } from '../../components/page';
|
||||
import { RoomViewHeader } from './RoomViewHeader';
|
||||
import { useKeyDown } from '../../hooks/useKeyDown';
|
||||
import { editableActiveElement } from '../../utils/dom';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
|
||||
const FN_KEYS_REGEX = /^F\d+$/;
|
||||
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
||||
|
|
@ -57,6 +59,8 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
|||
const roomInputRef = useRef<HTMLDivElement>(null);
|
||||
const roomViewRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
|
||||
const { roomId } = room;
|
||||
const editor = useEditor();
|
||||
|
||||
|
|
@ -133,7 +137,7 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
|||
</>
|
||||
)}
|
||||
</div>
|
||||
<RoomViewFollowing room={room} />
|
||||
{hideActivity ? <RoomViewFollowingPlaceholder /> : <RoomViewFollowing room={room} />}
|
||||
</Box>
|
||||
</Page>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import { style } from '@vanilla-extract/css';
|
||||
import { recipe } from '@vanilla-extract/recipes';
|
||||
import { DefaultReset, color, config, toRem } from 'folds';
|
||||
|
||||
export const RoomViewFollowingPlaceholder = style([
|
||||
DefaultReset,
|
||||
{
|
||||
height: toRem(28),
|
||||
},
|
||||
]);
|
||||
|
||||
export const RoomViewFollowing = recipe({
|
||||
base: [
|
||||
DefaultReset,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ import { useRoomEventReaders } from '../../hooks/useRoomEventReaders';
|
|||
import { EventReaders } from '../../components/event-readers';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
|
||||
export function RoomViewFollowingPlaceholder() {
|
||||
return <div className={css.RoomViewFollowingPlaceholder} />;
|
||||
}
|
||||
|
||||
export type RoomViewFollowingProps = {
|
||||
room: Room;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { RoomTopicViewer } from '../../components/room-topic-viewer';
|
|||
import { StateEvent } from '../../../types/matrix/room';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useRoom } from '../../hooks/useRoom';
|
||||
import { useSetSetting } from '../../state/hooks/settings';
|
||||
import { useSetSetting, useSetting } from '../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
import { useSpaceOptionally } from '../../hooks/useSpace';
|
||||
import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils';
|
||||
|
|
@ -64,13 +64,14 @@ type RoomMenuProps = {
|
|||
};
|
||||
const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose }, ref) => {
|
||||
const mx = useMatrixClient();
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
|
||||
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
|
||||
|
||||
const handleMarkAsRead = () => {
|
||||
markAsRead(mx, room.roomId);
|
||||
markAsRead(mx, room.roomId, hideActivity);
|
||||
requestClose();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -671,6 +671,7 @@ export type MessageProps = {
|
|||
onReactionToggle: (targetEventId: string, key: string, shortcode?: string) => void;
|
||||
reply?: ReactNode;
|
||||
reactions?: ReactNode;
|
||||
hideReadReceipts?: boolean;
|
||||
};
|
||||
export const Message = as<'div', MessageProps>(
|
||||
(
|
||||
|
|
@ -695,6 +696,7 @@ export const Message = as<'div', MessageProps>(
|
|||
onEditId,
|
||||
reply,
|
||||
reactions,
|
||||
hideReadReceipts,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
|
|
@ -992,11 +994,13 @@ export const Message = as<'div', MessageProps>(
|
|||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MessageReadReceiptItem
|
||||
room={room}
|
||||
eventId={mEvent.getId() ?? ''}
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
{!hideReadReceipts && (
|
||||
<MessageReadReceiptItem
|
||||
room={room}
|
||||
eventId={mEvent.getId() ?? ''}
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
)}
|
||||
<MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
<MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
{canPinEvent && (
|
||||
|
|
@ -1071,9 +1075,23 @@ export type EventProps = {
|
|||
highlight: boolean;
|
||||
canDelete?: boolean;
|
||||
messageSpacing: MessageSpacing;
|
||||
hideReadReceipts?: boolean;
|
||||
};
|
||||
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 [hover, setHover] = useState(false);
|
||||
const { hoverProps } = useHover({ onHoverChange: setHover });
|
||||
|
|
@ -1138,11 +1156,13 @@ export const Event = as<'div', EventProps>(
|
|||
>
|
||||
<Menu {...props} ref={ref}>
|
||||
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
|
||||
<MessageReadReceiptItem
|
||||
room={room}
|
||||
eventId={mEvent.getId() ?? ''}
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
{!hideReadReceipts && (
|
||||
<MessageReadReceiptItem
|
||||
room={room}
|
||||
eventId={mEvent.getId() ?? ''}
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
)}
|
||||
<MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
<MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@ function Appearance() {
|
|||
function Editor() {
|
||||
const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
||||
const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
||||
const [hideActivity, setHideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
|
|
@ -363,6 +364,13 @@ function Editor() {
|
|||
after={<Switch variant="Primary" value={isMarkdown} onChange={setIsMarkdown} />}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
@ -555,7 +563,13 @@ function Messages() {
|
|||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
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 className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue