v0.7.7 bugs fixes

This commit is contained in:
2026-03-11 19:11:18 -04:00
parent 03a8983b7d
commit 08243be745
9 changed files with 86 additions and 45 deletions

View File

@@ -108,8 +108,9 @@ export default function Message({ message: msg, prevMessage, currentUser, onRepl
setShowEmojiPicker(p => !p);
};
// Long press for mobile action menu
// Long press for mobile action menu (DMs only)
const handleTouchStart = () => {
if (!isDirect) return;
longPressTimer.current = setTimeout(() => setShowOptionsMenu(true), 500);
};
const handleTouchEnd = () => {
@@ -165,13 +166,13 @@ export default function Message({ message: msg, prevMessage, currentUser, onRepl
{!isOwn && !prevSameUser && (
<div
ref={avatarRef}
style={{ position: 'relative', cursor: 'pointer', borderRadius: '50%', transition: 'box-shadow 0.15s', flexShrink: 0 }}
style={{ position: 'relative', cursor: 'pointer', transition: 'box-shadow 0.15s', flexShrink: 0, borderRadius: '50%', display: 'inline-flex' }}
onClick={() => setShowProfile(p => !p)}
onMouseEnter={e => e.currentTarget.style.boxShadow = '0 0 0 2px var(--primary)'}
onMouseLeave={e => e.currentTarget.style.boxShadow = 'none'}
>
<Avatar user={msgUser} size="sm" className="msg-avatar" />
{onlineUserIds.has(msg.user_id) && (
{!!(onlineUserIds instanceof Set ? onlineUserIds.has(Number(msg.user_id)) : false) && (
<span style={{
position: 'absolute', bottom: 0, right: 0,
width: 9, height: 9, borderRadius: '50%',
@@ -211,10 +212,10 @@ export default function Message({ message: msg, prevMessage, currentUser, onRepl
<div className="msg-bubble-wrap">
<div className="msg-bubble-with-actions"
onMouseEnter={() => setShowActions(true)}
onMouseLeave={() => { if (!showEmojiPicker) setShowActions(false); }}
onTouchStart={isDirect ? handleTouchStart : undefined}
onTouchEnd={isDirect ? handleTouchEnd : undefined}
onTouchMove={isDirect ? handleTouchEnd : undefined}
onMouseLeave={() => { if (!showEmojiPicker && !showOptionsMenu) setShowActions(false); }}
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
onTouchMove={handleTouchEnd}
>
{/* Actions toolbar — floats above the bubble, aligned to correct side */}
{!isDeleted && (showActions || showEmojiPicker) && (
@@ -238,14 +239,6 @@ export default function Message({ message: msg, prevMessage, currentUser, onRepl
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
</button>
)}
{isDirect && (
<div style={{ position: 'relative' }}>
<button className="btn-icon action-btn" onClick={() => setShowOptionsMenu(p => !p)} title="More options">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="5" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="12" cy="19" r="1"/></svg>
</button>
</div>
)}
{/* Emoji picker anchored to the toolbar */}
{showEmojiPicker && (
<div
@@ -282,27 +275,37 @@ export default function Message({ message: msg, prevMessage, currentUser, onRepl
<span className="msg-time">{formatTime(msg.created_at)}</span>
{/* Pin/unpin options menu — desktop triple-dot + mobile long-press */}
{isDirect && showOptionsMenu && (
<div
ref={optionsMenuRef}
className={`msg-options-menu ${isOwn ? 'options-left' : 'options-right'}`}
onMouseDown={e => e.stopPropagation()}
>
{isPinned ? (
<button onClick={() => { onUnpin(msg.id); setShowOptionsMenu(false); }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M16 2v4l-3 3v7l-4-4-4 4V9L2 6V2h14zm2 0h2v2h-2V2z"/></svg>
Unpin message
</button>
) : (
<button
onClick={() => { if (pinCount < 5) { onPin(msg.id); setShowOptionsMenu(false); } }}
disabled={pinCount >= 5}
title={pinCount >= 5 ? 'Maximum 5 pinned messages reached' : ''}
{/* Triple-dot pin button — sits outside the hover toolbar so mouse-leave doesn't race */}
{isDirect && (
<div className="msg-pin-wrap" ref={optionsMenuRef}>
<button
className={`btn-icon msg-pin-btn ${showOptionsMenu ? 'active' : ''}`}
onClick={e => { e.stopPropagation(); setShowOptionsMenu(p => !p); }}
title="Message options"
>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2"><circle cx="12" cy="5" r="1.2" fill="currentColor"/><circle cx="12" cy="12" r="1.2" fill="currentColor"/><circle cx="12" cy="19" r="1.2" fill="currentColor"/></svg>
</button>
{showOptionsMenu && (
<div
className={`msg-options-menu ${isOwn ? 'options-left' : 'options-right'}`}
onMouseDown={e => e.stopPropagation()}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M16 2v4l-3 3v7l-4-4-4 4V9L2 6V2h14zm2 0h2v2h-2V2z"/></svg>
{pinCount >= 5 ? 'Pin (5/5)' : `Pin (${pinCount + 1}/5)`}
</button>
{isPinned ? (
<button onClick={() => { onUnpin(msg.id); setShowOptionsMenu(false); }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M16 2v4l-3 3v7l-4-4-4 4V9L2 6V2h14zm2 0h2v2h-2V2z"/></svg>
Unpin message
</button>
) : (
<button
onClick={() => { if (pinCount < 5) { onPin(msg.id); setShowOptionsMenu(false); } }}
disabled={pinCount >= 5}
title={pinCount >= 5 ? 'Maximum 5 pinned messages reached' : ''}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M16 2v4l-3 3v7l-4-4-4 4V9L2 6V2h14zm2 0h2v2h-2V2z"/></svg>
{pinCount >= 5 ? 'Pin (5/5)' : `Pin (${pinCount + 1}/5)`}
</button>
)}
</div>
)}
</div>
)}