Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

activity: bug fixes #3559

Merged
merged 17 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/tlon-web/src/chat/ChatInput/ChatInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as Popover from '@radix-ui/react-popover';
import { Editor } from '@tiptap/react';
import { getKey } from '@tloncorp/shared/dist/urbit/activity';
import {
CacheId,
Cite,
Expand Down Expand Up @@ -70,6 +71,7 @@ import {
} from '@/state/chat';
import { useGroupFlag } from '@/state/groups';
import { useFileStore, useUploader } from '@/state/storage';
import { useUnreadsStore } from '@/state/unreads';

interface ChatInputProps {
whom: string;
Expand Down Expand Up @@ -392,7 +394,7 @@ export default function ChatInput({
setDraft(inlinesToJSON(['']));
setTimeout(() => {
// TODO: chesterton's fence, but why execute a read here?
useChatStore.getState().read(whom);
useUnreadsStore.getState().read(getKey(whom));
clearAttachments();
}, 0);
},
Expand Down
71 changes: 39 additions & 32 deletions apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/* eslint-disable react/no-unused-prop-types */
import { Editor } from '@tiptap/react';
import { ActivitySummary } from '@tloncorp/shared/dist/urbit/activity';
import {
ActivitySummary,
getKey,
getThreadKey,
} from '@tloncorp/shared/dist/urbit/activity';
import {
Post,
Story,
Expand All @@ -10,6 +14,7 @@ import {
import { daToUnix } from '@urbit/api';
import { formatUd, unixToDa } from '@urbit/aura';
import { BigInteger } from 'big-integer';
import bigInt from 'big-integer';
import cn from 'classnames';
import { format, formatDistanceToNow, formatRelative, isToday } from 'date-fns';
import debounce from 'lodash/debounce';
Expand Down Expand Up @@ -55,6 +60,7 @@ import {
useMessageToggler,
useTrackedMessageStatus,
} from '@/state/chat';
import { Unread, useUnread, useUnreadsStore } from '@/state/unreads';

import ReactionDetails from '../ChatReactions/ReactionDetails';
import {
Expand Down Expand Up @@ -83,12 +89,11 @@ export interface ChatMessageProps {
}

function getUnreadDisplay(
unread: ActivitySummary | undefined,
unread: Unread | undefined,
id: string,
thread: ActivitySummary | undefined
thread: Unread | undefined
): 'none' | 'top' | 'thread' | 'top-with-thread' {
const mainChat = unread?.unread;
const isTop = mainChat?.id === id;
const isTop = unread?.lastUnread?.id === id;

// if this message is the oldest unread in the main chat,
// and has an unread thread, show the divider and thread indicator
Expand All @@ -98,7 +103,7 @@ function getUnreadDisplay(

// if we have a thread, only mark it as explicitly unread
// if it's not nested under main chat unreads
if (thread) {
if (thread && thread.status !== 'read') {
return 'thread';
}

Expand Down Expand Up @@ -169,25 +174,24 @@ const ChatMessage = React.memo<
const isMobile = useIsMobile();
const isThreadOnMobile = isThread && isMobile;
const isDMOrMultiDM = useIsDmOrMultiDm(whom);
const unreadId = !whomIsFlag(whom)
? seal.id
: `${essay.author}/${formatUd(unixToDa(essay.sent))}`;
const keys = useChatKeys();
const isChannel = whomIsFlag(whom);
const unreadId = !isChannel ? seal.id : formatUd(bigInt(seal.id));
const chatInfo = useChatInfo(whom);
const threadInfo = useChatInfo(`${whom}/${unreadId}`);
const unread = chatInfo?.unread;
const unreadsKey = getKey(whom);
const unread = useUnread(unreadsKey);
const threadUr = useUnread(getThreadKey(whom, unreadId));
const unreadDisplay = useMemo(
() =>
getUnreadDisplay(
unread?.unread,
unreadId,
threadInfo?.unread?.unread
unread,
!isChannel ? unreadId : `${essay.author}/${unreadId}`,
threadUr
),
[unread, threadInfo, seal.id, whom, essay]
[unread, threadUr, unreadId]
);
const topUnread =
unreadDisplay === 'top' || unreadDisplay === 'top-with-thread';
const threadNotify = threadInfo?.unread?.unread?.notify;
const threadNotify = threadUr?.notify;
const threadUnread =
unreadDisplay === 'thread' ||
unreadDisplay === 'top-with-thread' ||
Expand All @@ -212,24 +216,28 @@ const ChatMessage = React.memo<
return;
}

const { unread: brief, seen } = unread;
const unseen = unread.status === 'unread';
/* the first fire of this function
which we don't to do anything with. */
if (!inView && !seen) {
if (!inView && unseen) {
return;
}

const { seen: markSeen, delayedRead } = useChatStore.getState();

const { seen: markSeen, delayedRead } = useUnreadsStore.getState();
/* once the unseen marker comes into view we need to mark it
as seen and start a timer to mark it read so it goes away.
we ensure that the brief matches and hasn't changed before
doing so. we don't want to accidentally clear unreads when
the state has changed
*/
if (inView && unreadDisplay === 'top' && !seen) {
markSeen(whom);
delayedRead(whom, () => {
if (
inView &&
(unreadDisplay === 'top' ||
unreadDisplay === 'top-with-thread') &&
unseen
) {
markSeen(unreadsKey);
delayedRead(unreadsKey, () => {
if (isDMOrMultiDM) {
markDmRead();
} else {
Expand All @@ -241,7 +249,7 @@ const ChatMessage = React.memo<
[
unreadDisplay,
unread,
whom,
unreadsKey,
isDMOrMultiDM,
markReadChannel,
markDmRead,
Expand Down Expand Up @@ -435,11 +443,7 @@ const ChatMessage = React.memo<
{...handlers}
>
{unread && topUnread ? (
<DateDivider
date={unix}
unreadCount={unread.unread.unread?.count || 0}
ref={viewRef}
/>
<DateDivider date={unix} unreadCount={unread.count} ref={viewRef} />
) : null}
{newDay && unreadDisplay === 'none' ? (
<DateDivider date={unix} />
Expand Down Expand Up @@ -559,16 +563,19 @@ const ChatMessage = React.memo<

<span
className={cn(
threadUnread &&
threadUr?.status !== 'unread' &&
'mr-2',
threadUnread
? threadNotify
? 'text-blue'
: 'text-gray-400'
: ''
: 'mr-2'
)}
>
{replyCount} {replyCount > 1 ? 'replies' : 'reply'}{' '}
</span>
{threadUnread ? (
{threadUnread && threadUr?.status === 'unread' ? (
<UnreadIndicator
count={0}
notify={threadNotify}
Expand Down
22 changes: 12 additions & 10 deletions apps/tlon-web/src/chat/ChatThread/ChatThread.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getThreadKey } from '@tloncorp/shared/dist/urbit/activity';
import { ReplyTuple } from '@tloncorp/shared/dist/urbit/channel';
import { formatUd, unixToDa } from '@urbit/aura';
import bigInt from 'big-integer';
Expand Down Expand Up @@ -43,9 +44,10 @@ import {
useRouteGroup,
useVessel,
} from '@/state/groups/groups';
import { useUnread, useUnreadsStore } from '@/state/unreads';

import ChatScrollerPlaceholder from '../ChatScroller/ChatScrollerPlaceholder';
import { chatStoreLogger, useChatInfo, useChatStore } from '../useChatStore';
import { chatStoreLogger, useChatStore } from '../useChatStore';

export default function ChatThread() {
const { name, chShip, ship, chName, idTime } = useParams<{
Expand Down Expand Up @@ -76,14 +78,13 @@ export default function ChatThread() {
const dropZoneId = `chat-thread-input-dropzone-${idTime}`;
const { isDragging, isOver } = useDragAndDrop(dropZoneId);
const { post: note, isLoading } = usePost(nest, idTime!);
const id = note
? `${note.essay.author}/${formatUd(unixToDa(note.essay.sent))}`
: '';
const time = formatUd(bigInt(idTime!));
const id = note ? `${note.essay.author}/${time}` : '';
const msgKey = {
id,
time: formatUd(bigInt(idTime!)),
};
const chatUnreadsKey = `${flag}/${id}`;
const chatUnreadsKey = getThreadKey(flag, time);
const { markRead } = useMarkChannelRead(nest, msgKey);
const replies = note?.seal.replies || null;
const idTimeIsNumber = !Number.isNaN(Number(idTime));
Expand Down Expand Up @@ -128,7 +129,7 @@ export default function ChatThread() {
_.intersection(perms.writers, vessel.sects).length !== 0;
const { compatible, text } = useChannelCompatibility(`chat/${flag}`);
const { paddingBottom } = useBottomPadding();
const readTimeout = useChatInfo(chatUnreadsKey).unread?.readTimeout;
const readTimeout = useUnread(chatUnreadsKey)?.readTimeout;
const clearOnNavRef = useRef({
readTimeout,
chatUnreadsKey,
Expand All @@ -153,10 +154,11 @@ export default function ChatThread() {
);

const onAtBottom = useCallback(() => {
const { bottom, delayedRead } = useChatStore.getState();
const { bottom } = useChatStore.getState();
const { delayedRead } = useUnreadsStore.getState();
bottom(true);
delayedRead(flag, markRead);
}, [flag, markRead]);
delayedRead(chatUnreadsKey, markRead);
}, [chatUnreadsKey, markRead]);

const onEscape = useCallback(
(e: KeyboardEvent) => {
Expand All @@ -182,7 +184,7 @@ export default function ChatThread() {
const curr = clearOnNavRef.current;
if (curr.readTimeout !== undefined && curr.readTimeout !== 0) {
chatStoreLogger.log('unmount read from thread');
useChatStore.getState().read(curr.chatUnreadsKey);
useUnreadsStore.getState().read(curr.chatUnreadsKey);
curr.markRead();
}
},
Expand Down
30 changes: 14 additions & 16 deletions apps/tlon-web/src/chat/ChatWindow.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getKey } from '@tloncorp/shared/dist/urbit/activity';
import bigInt from 'big-integer';
import React, {
ReactElement,
Expand All @@ -16,10 +17,11 @@ import ArrowS16Icon from '@/components/icons/ArrowS16Icon';
import { useChannelCompatibility, useMarkChannelRead } from '@/logic/channel';
import { log } from '@/logic/utils';
import { useInfinitePosts } from '@/state/channel/channel';
import { useUnread, useUnreadsStore } from '@/state/unreads';

import ChatScrollerPlaceholder from './ChatScroller/ChatScrollerPlaceholder';
import UnreadAlerts from './UnreadAlerts';
import { useChatInfo, useChatStore } from './useChatStore';
import { useChatStore } from './useChatStore';

interface ChatWindowProps {
whom: string;
Expand Down Expand Up @@ -58,12 +60,12 @@ const ChatWindow = React.memo(function ChatWindowRaw({
} = useInfinitePosts(nest, scrollToId);
const { markRead } = useMarkChannelRead(nest);
const scrollerRef = useRef<VirtuosoHandle>(null);
const whomRef = useRef(whom);
const fetchingNewest =
isFetching && (!isFetchingNextPage || !isFetchingPreviousPage);
const [showUnreadBanner, setShowUnreadBanner] = useState(false);
const readTimeout = useChatInfo(whom).unread?.readTimeout;
const clearOnNavRef = useRef({ readTimeout, nest, whom, markRead });
const unreadsKey = getKey(whom);
const readTimeout = useUnread(unreadsKey)?.readTimeout;
const clearOnNavRef = useRef({ readTimeout, nest, unreadsKey, markRead });
const { compatible } = useChannelCompatibility(nest);
const navigate = useNavigate();
const latestMessageIndex = messages.length - 1;
Expand Down Expand Up @@ -125,14 +127,15 @@ const ChatWindow = React.memo(function ChatWindowRaw({
}, [whom]);

const onAtBottom = useCallback(() => {
const { bottom, delayedRead } = useChatStore.getState();
const { bottom } = useChatStore.getState();
const { delayedRead } = useUnreadsStore.getState();
bottom(true);
delayedRead(whom, () => markRead());
delayedRead(unreadsKey, () => markRead());
if (hasPreviousPage && !isFetching) {
log('fetching previous page');
fetchPreviousPage();
}
}, [whom, markRead, fetchPreviousPage, hasPreviousPage, isFetching]);
}, [unreadsKey, markRead, fetchPreviousPage, hasPreviousPage, isFetching]);

const onAtTop = useCallback(() => {
if (hasNextPage && !isFetching) {
Expand All @@ -147,34 +150,29 @@ const ChatWindow = React.memo(function ChatWindowRaw({
* we saw the unread marker
*/
useEffect(() => {
if (whomRef.current === whom) {
return;
}

let timeout = 0;
setShowUnreadBanner(false);
if (!fetchingNewest) {
timeout = setTimeout(() => {
whomRef.current = whom;
setShowUnreadBanner(true);
}, 250) as unknown as number;
}

return () => {
clearTimeout(timeout);
};
}, [whom, fetchingNewest]);
}, [fetchingNewest]);

// read the messages once navigated away
useEffect(() => {
clearOnNavRef.current = { readTimeout, nest, whom, markRead };
}, [readTimeout, nest, whom, markRead]);
clearOnNavRef.current = { readTimeout, nest, unreadsKey, markRead };
}, [readTimeout, nest, unreadsKey, markRead]);

useEffect(
() => () => {
const curr = clearOnNavRef.current;
if (curr.readTimeout !== undefined && curr.readTimeout !== 0) {
useChatStore.getState().read(curr.whom);
useUnreadsStore.getState().read(curr.unreadsKey);
curr.markRead();
}
},
Expand Down
Loading
Loading