v0.8.5 fix pinning
This commit is contained in:
@@ -76,6 +76,35 @@ export default function Sidebar({ groups, activeGroupId, onSelectGroup, notifica
|
||||
const appName = settings.app_name || 'jama';
|
||||
const logoUrl = settings.logo_url;
|
||||
|
||||
// Conversation pinning — derive from groups data (is_pinned flag from backend)
|
||||
const [pinnedConvIds, setPinnedConvIds] = useState(new Set());
|
||||
|
||||
// Sync pinnedConvIds whenever groups data changes
|
||||
useEffect(() => {
|
||||
const allG = [...(groups.publicGroups || []), ...(groups.privateGroups || [])];
|
||||
setPinnedConvIds(new Set(allG.filter(g => g.is_pinned).map(g => g.id)));
|
||||
}, [groups]);
|
||||
|
||||
const handlePinConversation = async (e, groupId) => {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
await api.pinConversation(groupId);
|
||||
setPinnedConvIds(prev => new Set([...prev, groupId]));
|
||||
} catch (err) {
|
||||
toast('Could not pin conversation', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnpinConversation = async (e, groupId) => {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
await api.unpinConversation(groupId);
|
||||
setPinnedConvIds(prev => { const n = new Set(prev); n.delete(groupId); return n; });
|
||||
} catch (err) {
|
||||
toast('Could not unpin conversation', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const allGroups = [
|
||||
...(groups.publicGroups || []),
|
||||
...(groups.privateGroups || [])
|
||||
@@ -83,14 +112,17 @@ export default function Sidebar({ groups, activeGroupId, onSelectGroup, notifica
|
||||
|
||||
const publicFiltered = allGroups.filter(g => g.type === 'public');
|
||||
// All private groups (DMs + group chats) sorted together by most recent message
|
||||
const privateFiltered = allGroups
|
||||
.filter(g => g.type === 'private')
|
||||
.sort((a, b) => {
|
||||
if (!a.last_message_at && !b.last_message_at) return 0;
|
||||
if (!a.last_message_at) return 1;
|
||||
if (!b.last_message_at) return -1;
|
||||
return new Date(b.last_message_at) - new Date(a.last_message_at);
|
||||
});
|
||||
const sortWithPinned = (arr) => [...arr].sort((a, b) => {
|
||||
const aPinned = pinnedConvIds.has(a.id) ? 1 : 0;
|
||||
const bPinned = pinnedConvIds.has(b.id) ? 1 : 0;
|
||||
if (bPinned !== aPinned) return bPinned - aPinned;
|
||||
if (!a.last_message_at && !b.last_message_at) return 0;
|
||||
if (!a.last_message_at) return 1;
|
||||
if (!b.last_message_at) return -1;
|
||||
return new Date(b.last_message_at) - new Date(a.last_message_at);
|
||||
});
|
||||
|
||||
const privateFiltered = sortWithPinned(allGroups.filter(g => g.type === 'private'));
|
||||
|
||||
const getNotifCount = (groupId) => notifications.filter(n => n.groupId === groupId).length;
|
||||
|
||||
@@ -102,11 +134,24 @@ export default function Sidebar({ groups, activeGroupId, onSelectGroup, notifica
|
||||
const hasUnread = unreadCount > 0;
|
||||
const isActive = group.id === activeGroupId;
|
||||
const isOnline = !!group.is_direct && !!group.peer_id && (onlineUserIds instanceof Set ? onlineUserIds.has(Number(group.peer_id)) : false);
|
||||
const isPinned = pinnedConvIds.has(group.id);
|
||||
// Long-press for mobile pin
|
||||
const longPressTimer = useRef(null);
|
||||
const handleTouchStart = () => {
|
||||
longPressTimer.current = setTimeout(() => {
|
||||
isPinned ? handleUnpinConversation({ stopPropagation: () => {} }, group.id)
|
||||
: handlePinConversation({ stopPropagation: () => {} }, group.id);
|
||||
}, 600);
|
||||
};
|
||||
const handleTouchEnd = () => clearTimeout(longPressTimer.current);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`group-item ${isActive ? 'active' : ''} ${hasUnread ? 'has-unread' : ''}`}
|
||||
className={`group-item ${isActive ? 'active' : ''} ${hasUnread ? 'has-unread' : ''} ${isPinned ? 'is-pinned' : ''}`}
|
||||
onClick={() => onSelectGroup(group.id)}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onTouchMove={handleTouchEnd}
|
||||
>
|
||||
<div className="group-icon-wrap">
|
||||
{group.is_direct && group.peer_avatar ? (
|
||||
@@ -126,13 +171,29 @@ export default function Sidebar({ groups, activeGroupId, onSelectGroup, notifica
|
||||
<div className="group-info flex-1 overflow-hidden">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className={`group-name truncate ${hasUnread ? 'unread-name' : ''}`}>
|
||||
{isPinned && (
|
||||
<svg className="conv-pin-indicator" width="10" height="10" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M16 2v4l-3 3v6l-2-2-2 2V9L6 6V2h10z"/>
|
||||
</svg>
|
||||
)}
|
||||
{group.is_direct && group.peer_display_name
|
||||
? <>{group.peer_display_name}<span className="dm-real-name"> ({group.peer_real_name})</span></>
|
||||
: group.is_direct && group.peer_real_name ? group.peer_real_name : group.name}
|
||||
</span>
|
||||
{group.last_message_at && (
|
||||
<span className="group-time">{formatTime(group.last_message_at)}</span>
|
||||
)}
|
||||
<div className="group-item-actions">
|
||||
{group.last_message_at && (
|
||||
<span className="group-time">{formatTime(group.last_message_at)}</span>
|
||||
)}
|
||||
<button
|
||||
className={`conv-pin-btn ${isPinned ? 'pinned' : ''}`}
|
||||
onClick={(e) => isPinned ? handleUnpinConversation(e, group.id) : handlePinConversation(e, group.id)}
|
||||
title={isPinned ? 'Unpin conversation' : 'Pin to top'}
|
||||
>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M16 2v4l-3 3v6l-2-2-2 2V9L6 6V2h10z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="group-last-msg truncate">
|
||||
|
||||
Reference in New Issue
Block a user