import React, { ChangeEvent, useCallback, useEffect, useState } from 'react'; import { Avatar, Box, Button, Header, Icon, IconButton, Icons, Input, Scroll, Text, color, config, } from 'folds'; import classNames from 'classnames'; import { useBookmarks, Bookmark } from '../../hooks/useBookmarks'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { RoomAvatar } from '../../components/room-avatar'; import { getRoomAvatarUrl } from '../../utils/room'; import { nameInitials } from '../../utils/common'; import { ContainerColor } from '../../styles/ContainerColor.css'; import { stopPropagation } from '../../utils/keyboard'; import * as css from './BookmarksPanel.css'; function formatTimeAgo(ts: number): string { const diff = Date.now() - ts; const minutes = Math.floor(diff / 60_000); if (minutes < 1) return 'just now'; if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); if (days === 1) return 'yesterday'; if (days < 7) return `${days}d ago`; return new Date(ts).toLocaleDateString(); } type BookmarkItemProps = { bookmark: Bookmark; onJump: (roomId: string, eventId: string) => void; onRemove: (eventId: string) => void; }; function BookmarkItem({ bookmark, onJump, onRemove }: BookmarkItemProps) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const room = mx.getRoom(bookmark.roomId) ?? undefined; const displayRoomName = room?.name ?? bookmark.roomName; const avatarUrl = room ? (getRoomAvatarUrl(mx, room, 96, useAuthentication) ?? undefined) : undefined; return ( {/* Room identity + remove */} {nameInitials(displayRoomName)}} /> {displayRoomName} {formatTimeAgo(bookmark.savedAt)} onRemove(bookmark.eventId)} aria-label="Remove saved message" > {/* Message preview — clicking jumps to the message */} ); } type BookmarksPanelProps = { onClose: () => void; }; export function BookmarksPanel({ onClose }: BookmarksPanelProps) { const { bookmarks, removeBookmark } = useBookmarks(); const { navigateRoom } = useRoomNavigate(); const [filter, setFilter] = useState(''); // Escape closes the panel (parity with the app's other overlays/drawers). useEffect(() => { const handleKeyDown = (evt: KeyboardEvent) => { if (evt.key === 'Escape') { stopPropagation(evt); onClose(); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [onClose]); const handleJump = useCallback( (roomId: string, eventId: string) => { navigateRoom(roomId, eventId); onClose(); }, [navigateRoom, onClose], ); const handleFilterChange = (e: ChangeEvent) => { setFilter(e.target.value); }; const query = filter.trim().toLowerCase(); const filtered: Bookmark[] = query.length === 0 ? bookmarks : bookmarks.filter( (bk) => bk.previewText.toLowerCase().includes(query) || bk.roomName.toLowerCase().includes(query), ); return (
Saved Messages
{/* Search */} } after={ filter.length > 0 ? ( setFilter('')} > ) : undefined } /> {bookmarks.length > 0 && ( {filtered.length === bookmarks.length ? `${bookmarks.length} saved message${bookmarks.length !== 1 ? 's' : ''}` : `${filtered.length} of ${bookmarks.length} messages`} )} {/* List */} {filtered.length === 0 ? ( {bookmarks.length === 0 ? 'No saved messages yet. Right-click any message to save it.' : 'No saved messages match your search.'} ) : ( {filtered.map((bk) => ( ))} )}
); }