Skip to content
Merged
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
38 changes: 36 additions & 2 deletions resources/js/components/chat/conversation-thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,38 @@ export function ConversationThread({
}, [messages, isLoaded, user.id, mutedUntil, isVisible]);

const pinnedMessages = thread?.pinned ?? noMessages;
const hasPinnedMessages = pinnedMessages.length > 0;
const [hasPinClearance, setHasPinClearance] = useState(hasPinnedMessages);

if (hasPinnedMessages && !hasPinClearance) {
setHasPinClearance(true);
}

const releasePinClearance = useCallback(() => {
const element = scroller.current;
const content = list.current;

if (
hasPinnedMessages ||
!hasPinClearance ||
element === null ||
content === null
) {
return;
}

// Near the oldest message, removing the inset would clamp the reverse scroller and move the reading position.
const roomAbove =
element.scrollHeight - element.clientHeight + element.scrollTop;

if (
element.scrollTop === 0 ||
roomAbove >= parseFloat(getComputedStyle(content).paddingTop)
) {
setHasPinClearance(false);
}
}, [hasPinnedMessages, hasPinClearance]);

const canPin = isOrg && thread?.role === 'maintainer' && isOpen;
// Polls hand over a new list whenever any reaction changes, so the bubbles only hear about the pins themselves.
const pinnedKey = pinnedMessages.map((message) => message.id).join();
Expand Down Expand Up @@ -507,6 +539,8 @@ export function ConversationThread({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [contentKey, thread?.isLoaded]);

useLayoutEffect(releasePinClearance, [releasePinClearance]);

const releaseScrollAnchor = useCallback(() => {
// Let deliberate input own the viewport until the fold settles, including delayed automatic scroll events.
anchor.current = null;
Expand Down Expand Up @@ -585,6 +619,7 @@ export function ConversationThread({
}

updateScrollPosition();
releasePinClearance();

// A correction during the reply fold is not an intentional scroll, even while it is close to the bottom.
if (
Expand Down Expand Up @@ -723,8 +758,7 @@ export function ConversationThread({
? 'px-3 pt-3 text-sm'
: 'px-4 pt-4 text-[15px]',
// The pinned banner lies over the top, so the oldest message can still scroll clear of it.
pinnedMessages.length > 0 &&
(isCompact ? 'pt-12' : 'pt-13'),
hasPinClearance && (isCompact ? 'pt-12' : 'pt-13'),
)}
>
{thread.isLoadingOlder && (
Expand Down
Loading