mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
fix(desktop): keep message actions hover-only (#844)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -91,7 +91,6 @@ type ChannelPaneProps = {
|
|||||||
threadMessages: MainTimelineEntry[];
|
threadMessages: MainTimelineEntry[];
|
||||||
threadPanelWidthPx: number;
|
threadPanelWidthPx: number;
|
||||||
threadTypingPubkeys: string[];
|
threadTypingPubkeys: string[];
|
||||||
threadReplyTargetId: string | null;
|
|
||||||
threadReplyTargetMessage: TimelineMessage | null;
|
threadReplyTargetMessage: TimelineMessage | null;
|
||||||
threadScrollTargetId: string | null;
|
threadScrollTargetId: string | null;
|
||||||
targetMessageId: string | null;
|
targetMessageId: string | null;
|
||||||
@@ -160,7 +159,6 @@ export const ChannelPane = React.memo(function ChannelPane({
|
|||||||
threadPanelWidthPx,
|
threadPanelWidthPx,
|
||||||
threadScrollTargetId,
|
threadScrollTargetId,
|
||||||
threadTypingPubkeys,
|
threadTypingPubkeys,
|
||||||
threadReplyTargetId,
|
|
||||||
threadReplyTargetMessage,
|
threadReplyTargetMessage,
|
||||||
typingPubkeys,
|
typingPubkeys,
|
||||||
}: ChannelPaneProps) {
|
}: ChannelPaneProps) {
|
||||||
@@ -307,7 +305,6 @@ export const ChannelPane = React.memo(function ChannelPane({
|
|||||||
) : null}
|
) : null}
|
||||||
<MessageTimeline
|
<MessageTimeline
|
||||||
channelId={activeChannel?.id}
|
channelId={activeChannel?.id}
|
||||||
activeReplyTargetId={openThreadHeadId}
|
|
||||||
scrollContainerRef={timelineScrollRef}
|
scrollContainerRef={timelineScrollRef}
|
||||||
currentPubkey={currentPubkey}
|
currentPubkey={currentPubkey}
|
||||||
fetchOlder={fetchOlder}
|
fetchOlder={fetchOlder}
|
||||||
@@ -456,7 +453,6 @@ export const ChannelPane = React.memo(function ChannelPane({
|
|||||||
onToggleReaction={onToggleReaction}
|
onToggleReaction={onToggleReaction}
|
||||||
onUnfollowThread={onUnfollowThread}
|
onUnfollowThread={onUnfollowThread}
|
||||||
profiles={profiles}
|
profiles={profiles}
|
||||||
replyTargetId={threadReplyTargetId}
|
|
||||||
replyTargetMessage={threadReplyTargetMessage}
|
replyTargetMessage={threadReplyTargetMessage}
|
||||||
scrollTargetId={threadScrollTargetId}
|
scrollTargetId={threadScrollTargetId}
|
||||||
canResetWidth={canResetThreadPanelWidth}
|
canResetWidth={canResetThreadPanelWidth}
|
||||||
|
|||||||
@@ -551,7 +551,6 @@ export function ChannelScreen({
|
|||||||
threadMessages={threadMessages}
|
threadMessages={threadMessages}
|
||||||
threadPanelWidthPx={threadPanelWidthPx}
|
threadPanelWidthPx={threadPanelWidthPx}
|
||||||
threadTypingPubkeys={threadTypingPubkeys}
|
threadTypingPubkeys={threadTypingPubkeys}
|
||||||
threadReplyTargetId={threadReplyTargetId}
|
|
||||||
threadReplyTargetMessage={threadReplyTargetMessage}
|
threadReplyTargetMessage={threadReplyTargetMessage}
|
||||||
threadScrollTargetId={threadScrollTargetId}
|
threadScrollTargetId={threadScrollTargetId}
|
||||||
isJoining={joinChannelMutation.isPending}
|
isJoining={joinChannelMutation.isPending}
|
||||||
|
|||||||
@@ -324,7 +324,6 @@ export function InboxDetailPane({
|
|||||||
<div className="mx-6 my-3 border-t border-border/60" />
|
<div className="mx-6 my-3 border-t border-border/60" />
|
||||||
) : null}
|
) : null}
|
||||||
<InboxMessageRow
|
<InboxMessageRow
|
||||||
activeReplyTargetId={replyTargetId}
|
|
||||||
canReply={canReply}
|
canReply={canReply}
|
||||||
channelId={item.item.channelId}
|
channelId={item.item.channelId}
|
||||||
isFocusHighlightVisible={isFocusHighlightVisible}
|
isFocusHighlightVisible={isFocusHighlightVisible}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ function toTimelineMessage(message: InboxDisplayMessage): TimelineMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type InboxMessageRowProps = {
|
type InboxMessageRowProps = {
|
||||||
activeReplyTargetId: string | null;
|
|
||||||
canReply: boolean;
|
canReply: boolean;
|
||||||
/** Channel UUID for "Copy link" — passed straight through to MessageActionBar. */
|
/** Channel UUID for "Copy link" — passed straight through to MessageActionBar. */
|
||||||
channelId?: string | null;
|
channelId?: string | null;
|
||||||
@@ -42,7 +41,6 @@ type InboxMessageRowProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function InboxMessageRow({
|
export function InboxMessageRow({
|
||||||
activeReplyTargetId,
|
|
||||||
canReply,
|
canReply,
|
||||||
channelId = null,
|
channelId = null,
|
||||||
isFocusHighlightVisible,
|
isFocusHighlightVisible,
|
||||||
@@ -89,7 +87,6 @@ export function InboxMessageRow({
|
|||||||
{canReply || canToggleReactions ? (
|
{canReply || canToggleReactions ? (
|
||||||
<div className="absolute right-2 top-1 z-10">
|
<div className="absolute right-2 top-1 z-10">
|
||||||
<MessageActionBar
|
<MessageActionBar
|
||||||
activeReplyTargetId={activeReplyTargetId}
|
|
||||||
channelId={channelId}
|
channelId={channelId}
|
||||||
message={timelineMessage}
|
message={timelineMessage}
|
||||||
onReactionSelect={
|
onReactionSelect={
|
||||||
|
|||||||
@@ -257,7 +257,6 @@ function MoreActionsMenu({
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export function MessageActionBar({
|
export function MessageActionBar({
|
||||||
activeReplyTargetId = null,
|
|
||||||
channelId,
|
channelId,
|
||||||
message,
|
message,
|
||||||
onDelete,
|
onDelete,
|
||||||
@@ -272,7 +271,6 @@ export function MessageActionBar({
|
|||||||
reactionPending = false,
|
reactionPending = false,
|
||||||
isFollowingThread,
|
isFollowingThread,
|
||||||
}: {
|
}: {
|
||||||
activeReplyTargetId?: string | null;
|
|
||||||
/** Channel UUID — required for the "Copy link" action; when omitted the
|
/** Channel UUID — required for the "Copy link" action; when omitted the
|
||||||
* action is hidden (callers like the home inbox that lack the context). */
|
* action is hidden (callers like the home inbox that lack the context). */
|
||||||
channelId?: string | null;
|
channelId?: string | null;
|
||||||
@@ -306,7 +304,6 @@ export function MessageActionBar({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isReplyingToMessage = activeReplyTargetId === message.id;
|
|
||||||
const selectedReactionCount = reactions.filter(
|
const selectedReactionCount = reactions.filter(
|
||||||
(reaction) => reaction.reactedByCurrentUser,
|
(reaction) => reaction.reactedByCurrentUser,
|
||||||
).length;
|
).length;
|
||||||
@@ -318,7 +315,7 @@ export function MessageActionBar({
|
|||||||
"opacity-100 sm:pointer-events-none sm:opacity-0",
|
"opacity-100 sm:pointer-events-none sm:opacity-0",
|
||||||
"sm:group-hover/message:pointer-events-auto sm:group-hover/message:opacity-100",
|
"sm:group-hover/message:pointer-events-auto sm:group-hover/message:opacity-100",
|
||||||
"sm:group-focus-within/message:pointer-events-auto sm:group-focus-within/message:opacity-100",
|
"sm:group-focus-within/message:pointer-events-auto sm:group-focus-within/message:opacity-100",
|
||||||
isReplyingToMessage || isReactionPickerOpen || isDropdownOpen
|
isReactionPickerOpen || isDropdownOpen
|
||||||
? "sm:pointer-events-auto sm:opacity-100"
|
? "sm:pointer-events-auto sm:opacity-100"
|
||||||
: "",
|
: "",
|
||||||
)}
|
)}
|
||||||
@@ -390,7 +387,7 @@ export function MessageActionBar({
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
aria-label={isReplyingToMessage ? "Cancel reply" : "Reply"}
|
aria-label="Reply"
|
||||||
className="h-6 w-6 rounded-full p-0"
|
className="h-6 w-6 rounded-full p-0"
|
||||||
data-testid={`reply-message-${message.id}`}
|
data-testid={`reply-message-${message.id}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -398,14 +395,12 @@ export function MessageActionBar({
|
|||||||
}}
|
}}
|
||||||
size="sm"
|
size="sm"
|
||||||
type="button"
|
type="button"
|
||||||
variant={isReplyingToMessage ? "secondary" : "ghost"}
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<CornerUpLeft className="h-3 w-3" />
|
<CornerUpLeft className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>Reply</TooltipContent>
|
||||||
{isReplyingToMessage ? "Cancel reply" : "Reply"}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ const NESTED_REPLY_OFFSET_PX = 28;
|
|||||||
|
|
||||||
export const MessageRow = React.memo(
|
export const MessageRow = React.memo(
|
||||||
function MessageRow({
|
function MessageRow({
|
||||||
activeReplyTargetId = null,
|
|
||||||
channelId = null,
|
channelId = null,
|
||||||
highlighted = false,
|
highlighted = false,
|
||||||
isFollowingThread,
|
isFollowingThread,
|
||||||
@@ -44,7 +43,6 @@ export const MessageRow = React.memo(
|
|||||||
profiles,
|
profiles,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
}: {
|
}: {
|
||||||
activeReplyTargetId?: string | null;
|
|
||||||
channelId?: string | null;
|
channelId?: string | null;
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
isFollowingThread?: boolean;
|
isFollowingThread?: boolean;
|
||||||
@@ -212,7 +210,6 @@ export const MessageRow = React.memo(
|
|||||||
const actionBarNode = (
|
const actionBarNode = (
|
||||||
<div className="absolute right-2 top-1 z-10">
|
<div className="absolute right-2 top-1 z-10">
|
||||||
<MessageActionBar
|
<MessageActionBar
|
||||||
activeReplyTargetId={activeReplyTargetId}
|
|
||||||
channelId={channelId}
|
channelId={channelId}
|
||||||
isFollowingThread={isFollowingThread}
|
isFollowingThread={isFollowingThread}
|
||||||
message={message}
|
message={message}
|
||||||
@@ -451,7 +448,6 @@ export const MessageRow = React.memo(
|
|||||||
prev.message.role === next.message.role &&
|
prev.message.role === next.message.role &&
|
||||||
prev.message.personaDisplayName === next.message.personaDisplayName &&
|
prev.message.personaDisplayName === next.message.personaDisplayName &&
|
||||||
prev.highlighted === next.highlighted &&
|
prev.highlighted === next.highlighted &&
|
||||||
prev.activeReplyTargetId === next.activeReplyTargetId &&
|
|
||||||
prev.isFollowingThread === next.isFollowingThread &&
|
prev.isFollowingThread === next.isFollowingThread &&
|
||||||
prev.layoutVariant === next.layoutVariant &&
|
prev.layoutVariant === next.layoutVariant &&
|
||||||
prev.profiles === next.profiles &&
|
prev.profiles === next.profiles &&
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ type MessageThreadPanelProps = {
|
|||||||
remove: boolean,
|
remove: boolean,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
profiles?: UserProfileLookup;
|
profiles?: UserProfileLookup;
|
||||||
replyTargetId: string | null;
|
|
||||||
replyTargetMessage: TimelineMessage | null;
|
replyTargetMessage: TimelineMessage | null;
|
||||||
scrollTargetId: string | null;
|
scrollTargetId: string | null;
|
||||||
threadHead: TimelineMessage | null;
|
threadHead: TimelineMessage | null;
|
||||||
@@ -116,7 +115,6 @@ export function MessageThreadPanel({
|
|||||||
onToggleReaction,
|
onToggleReaction,
|
||||||
onUnfollowThread,
|
onUnfollowThread,
|
||||||
profiles,
|
profiles,
|
||||||
replyTargetId,
|
|
||||||
replyTargetMessage,
|
replyTargetMessage,
|
||||||
scrollTargetId,
|
scrollTargetId,
|
||||||
threadHead,
|
threadHead,
|
||||||
@@ -275,7 +273,6 @@ export function MessageThreadPanel({
|
|||||||
<div className="px-3 pb-1 pt-0" data-testid="message-thread-head">
|
<div className="px-3 pb-1 pt-0" data-testid="message-thread-head">
|
||||||
<div className="rounded-2xl">
|
<div className="rounded-2xl">
|
||||||
<MessageRow
|
<MessageRow
|
||||||
activeReplyTargetId={replyTargetId}
|
|
||||||
channelId={channelId}
|
channelId={channelId}
|
||||||
isFollowingThread={isFollowingThread}
|
isFollowingThread={isFollowingThread}
|
||||||
layoutVariant="thread-reply"
|
layoutVariant="thread-reply"
|
||||||
@@ -316,7 +313,6 @@ export function MessageThreadPanel({
|
|||||||
key={entry.message.id}
|
key={entry.message.id}
|
||||||
>
|
>
|
||||||
<MessageRow
|
<MessageRow
|
||||||
activeReplyTargetId={replyTargetId}
|
|
||||||
channelId={channelId}
|
channelId={channelId}
|
||||||
layoutVariant="thread-reply"
|
layoutVariant="thread-reply"
|
||||||
message={entry.message}
|
message={entry.message}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type MessageTimelineProps = {
|
|||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
emptyTitle?: string;
|
emptyTitle?: string;
|
||||||
emptyDescription?: string;
|
emptyDescription?: string;
|
||||||
activeReplyTargetId?: string | null;
|
|
||||||
currentPubkey?: string;
|
currentPubkey?: string;
|
||||||
fetchOlder?: () => Promise<void>;
|
fetchOlder?: () => Promise<void>;
|
||||||
hasOlderMessages?: boolean;
|
hasOlderMessages?: boolean;
|
||||||
@@ -58,7 +57,6 @@ export const MessageTimeline = React.memo(function MessageTimeline({
|
|||||||
isLoading = false,
|
isLoading = false,
|
||||||
emptyTitle = "No messages yet",
|
emptyTitle = "No messages yet",
|
||||||
emptyDescription = "Send the first message to start the thread.",
|
emptyDescription = "Send the first message to start the thread.",
|
||||||
activeReplyTargetId = null,
|
|
||||||
currentPubkey,
|
currentPubkey,
|
||||||
fetchOlder,
|
fetchOlder,
|
||||||
hasOlderMessages = true,
|
hasOlderMessages = true,
|
||||||
@@ -192,7 +190,6 @@ export const MessageTimeline = React.memo(function MessageTimeline({
|
|||||||
|
|
||||||
{!isLoading && messages.length > 0 ? (
|
{!isLoading && messages.length > 0 ? (
|
||||||
<TimelineMessageList
|
<TimelineMessageList
|
||||||
activeReplyTargetId={activeReplyTargetId}
|
|
||||||
channelId={channelId}
|
channelId={channelId}
|
||||||
currentPubkey={currentPubkey}
|
currentPubkey={currentPubkey}
|
||||||
followThreadById={followThreadById}
|
followThreadById={followThreadById}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { MessageThreadSummaryRow } from "./MessageThreadSummaryRow";
|
|||||||
import { SystemMessageRow } from "./SystemMessageRow";
|
import { SystemMessageRow } from "./SystemMessageRow";
|
||||||
|
|
||||||
type TimelineMessageListProps = {
|
type TimelineMessageListProps = {
|
||||||
activeReplyTargetId?: string | null;
|
|
||||||
channelId?: string | null;
|
channelId?: string | null;
|
||||||
currentPubkey?: string;
|
currentPubkey?: string;
|
||||||
followThreadById?: (rootId: string) => void;
|
followThreadById?: (rootId: string) => void;
|
||||||
@@ -45,7 +44,6 @@ type TimelineMessageListProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const TimelineMessageList = React.memo(function TimelineMessageList({
|
export const TimelineMessageList = React.memo(function TimelineMessageList({
|
||||||
activeReplyTargetId = null,
|
|
||||||
channelId,
|
channelId,
|
||||||
currentPubkey,
|
currentPubkey,
|
||||||
followThreadById,
|
followThreadById,
|
||||||
@@ -116,7 +114,6 @@ export const TimelineMessageList = React.memo(function TimelineMessageList({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MessageRow
|
<MessageRow
|
||||||
activeReplyTargetId={activeReplyTargetId}
|
|
||||||
channelId={channelId}
|
channelId={channelId}
|
||||||
highlighted={false}
|
highlighted={false}
|
||||||
isFollowingThread={
|
isFollowingThread={
|
||||||
@@ -165,7 +162,6 @@ export const TimelineMessageList = React.memo(function TimelineMessageList({
|
|||||||
currentDayGroup?.elements.push(
|
currentDayGroup?.elements.push(
|
||||||
<div key={message.id} className="flex flex-col gap-1">
|
<div key={message.id} className="flex flex-col gap-1">
|
||||||
<MessageRow
|
<MessageRow
|
||||||
activeReplyTargetId={activeReplyTargetId}
|
|
||||||
channelId={channelId}
|
channelId={channelId}
|
||||||
highlighted={message.id === highlightedMessageId || isSearchActive}
|
highlighted={message.id === highlightedMessageId || isSearchActive}
|
||||||
message={message}
|
message={message}
|
||||||
|
|||||||
Reference in New Issue
Block a user