fix: schedule button, compression visibility, activity log, insights overflow, bookmarks UI
Schedule message: modal now always opens (even with empty composer); includes its own message textarea pre-filled from editor content; removed null-content early-return guard from handleScheduleClick; fixed error text to use color.Critical.Main not CSS var Image compression: removed 200KB size threshold — checkbox now shows for all JPEG/PNG uploads (not just large ones); 'no significant saving' message handles already-small files gracefully Activity log: auto-paginate on mount — state events are absent from initial sync window, so the log was always empty until Load More was clicked manually Insights summary: Text size H4→H5 with toLocaleString() formatting and overflow:ellipsis so large numbers don't push tiles off screen Bookmarks panel: replaced var(--bg-*) CSS vars (undefined in folds themes) with color.Surface/SurfaceVariant/Primary folds tokens; added left accent border on message preview block, count badge, clear button in search, improved empty state, cleaner button hierarchy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
import React, { ChangeEvent, useState } from 'react';
|
||||
import { Box, Button, Header, Icon, IconButton, Icons, Input, Scroll, Text, config } from 'folds';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Header,
|
||||
Icon,
|
||||
IconButton,
|
||||
Icons,
|
||||
Input,
|
||||
Scroll,
|
||||
Text,
|
||||
color,
|
||||
config,
|
||||
} from 'folds';
|
||||
import { useBookmarks, Bookmark } from '../../hooks/useBookmarks';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||
@@ -14,8 +26,6 @@ function formatTimeAgo(ts: number): string {
|
||||
const days = Math.floor(hours / 24);
|
||||
if (days === 1) return 'yesterday';
|
||||
if (days < 7) return `${days}d ago`;
|
||||
const weeks = Math.floor(days / 7);
|
||||
if (weeks < 5) return `${weeks}w ago`;
|
||||
return new Date(ts).toLocaleDateString();
|
||||
}
|
||||
|
||||
@@ -30,65 +40,83 @@ function BookmarkItem({ bookmark, onJump, onRemove }: BookmarkItemProps) {
|
||||
const room = mx.getRoom(bookmark.roomId);
|
||||
const displayRoomName = room?.name ?? bookmark.roomName;
|
||||
|
||||
const handleJump = () => {
|
||||
onJump(bookmark.roomId, bookmark.eventId);
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
onRemove(bookmark.eventId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
direction="Column"
|
||||
gap="100"
|
||||
gap="200"
|
||||
style={{
|
||||
padding: `${config.space.S200} ${config.space.S300}`,
|
||||
borderBottomWidth: config.borderWidth.B300,
|
||||
borderBottomStyle: 'solid',
|
||||
borderBottomColor: 'var(--bg-surface-border)',
|
||||
padding: `${config.space.S300} ${config.space.S300}`,
|
||||
borderBottom: `1px solid ${color.Surface.ContainerLine}`,
|
||||
background: color.Surface.Container,
|
||||
transition: 'background 0.1s',
|
||||
}}
|
||||
>
|
||||
<Text size="L400" truncate>
|
||||
{displayRoomName}
|
||||
</Text>
|
||||
<Text
|
||||
size="T300"
|
||||
priority="300"
|
||||
{/* Room name row */}
|
||||
<Box alignItems="Center" gap="100">
|
||||
<Icon src={Icons.Hash} size="50" style={{ opacity: 0.5, flexShrink: 0 }} />
|
||||
<Text
|
||||
size="T200"
|
||||
style={{
|
||||
color: color.Primary.Main,
|
||||
fontWeight: 600,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{displayRoomName}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Message preview */}
|
||||
<Box
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
wordBreak: 'break-word',
|
||||
background: color.SurfaceVariant.Container,
|
||||
borderRadius: config.radii.R300,
|
||||
borderLeft: `3px solid ${color.Primary.Main}`,
|
||||
padding: `${config.space.S100} ${config.space.S200}`,
|
||||
}}
|
||||
>
|
||||
{bookmark.previewText || '(no preview)'}
|
||||
</Text>
|
||||
<Text
|
||||
size="T300"
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
wordBreak: 'break-word',
|
||||
opacity: 0.9,
|
||||
}}
|
||||
>
|
||||
{bookmark.previewText || '(no preview)'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Footer row */}
|
||||
<Box alignItems="Center" justifyContent="SpaceBetween" gap="200">
|
||||
<Text size="T200" priority="300">
|
||||
Saved {formatTimeAgo(bookmark.savedAt)}
|
||||
<Text size="T200" style={{ opacity: 0.5 }}>
|
||||
{formatTimeAgo(bookmark.savedAt)}
|
||||
</Text>
|
||||
<Box gap="100" shrink="No">
|
||||
<Button
|
||||
size="300"
|
||||
variant="Secondary"
|
||||
variant="Primary"
|
||||
fill="Soft"
|
||||
radii="300"
|
||||
onClick={handleJump}
|
||||
onClick={() => onJump(bookmark.roomId, bookmark.eventId)}
|
||||
before={<Icon size="100" src={Icons.ArrowRight} />}
|
||||
>
|
||||
<Text size="T300">Jump</Text>
|
||||
</Button>
|
||||
<IconButton
|
||||
size="300"
|
||||
variant="Critical"
|
||||
variant="Surface"
|
||||
fill="None"
|
||||
radii="300"
|
||||
onClick={handleRemove}
|
||||
onClick={() => onRemove(bookmark.eventId)}
|
||||
aria-label="Remove bookmark"
|
||||
>
|
||||
<Icon size="100" src={Icons.Cross} />
|
||||
<Icon size="100" src={Icons.Delete} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -129,22 +157,21 @@ export function BookmarksPanel({ onClose }: BookmarksPanelProps) {
|
||||
width: '266px',
|
||||
height: '100%',
|
||||
flexShrink: 0,
|
||||
borderLeftWidth: config.borderWidth.B300,
|
||||
borderLeftStyle: 'solid',
|
||||
borderLeftColor: 'var(--bg-surface-border)',
|
||||
backgroundColor: 'var(--bg-background)',
|
||||
borderLeft: `1px solid ${color.Surface.ContainerLine}`,
|
||||
background: color.Surface.Container,
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Header
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
padding: `0 ${config.space.S200} 0 ${config.space.S300}`,
|
||||
borderBottomWidth: config.borderWidth.B300,
|
||||
borderBottom: `1px solid ${color.Surface.ContainerLine}`,
|
||||
}}
|
||||
variant="Background"
|
||||
variant="Surface"
|
||||
size="600"
|
||||
>
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
@@ -152,44 +179,84 @@ export function BookmarksPanel({ onClose }: BookmarksPanelProps) {
|
||||
<Box grow="Yes">
|
||||
<Text size="H5">Saved Messages</Text>
|
||||
</Box>
|
||||
<IconButton variant="Background" aria-label="Close saved messages" onClick={onClose}>
|
||||
<Icon src={Icons.Cross} />
|
||||
<IconButton
|
||||
size="300"
|
||||
variant="Surface"
|
||||
radii="300"
|
||||
aria-label="Close saved messages"
|
||||
onClick={onClose}
|
||||
>
|
||||
<Icon src={Icons.Cross} size="100" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Header>
|
||||
|
||||
{/* Search */}
|
||||
<Box
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
padding: config.space.S200,
|
||||
borderBottomWidth: config.borderWidth.B300,
|
||||
borderBottomStyle: 'solid',
|
||||
borderBottomColor: 'var(--bg-surface-border)',
|
||||
borderBottom: `1px solid ${color.Surface.ContainerLine}`,
|
||||
background: color.SurfaceVariant.Container,
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
variant="Background"
|
||||
placeholder="Filter saved messages..."
|
||||
variant="Surface"
|
||||
size="400"
|
||||
radii="400"
|
||||
placeholder="Search saved messages…"
|
||||
value={filter}
|
||||
onChange={handleFilterChange}
|
||||
before={<Icon size="200" src={Icons.Search} />}
|
||||
after={
|
||||
filter.length > 0 ? (
|
||||
<IconButton
|
||||
size="300"
|
||||
variant="Surface"
|
||||
radii="300"
|
||||
aria-label="Clear search"
|
||||
onClick={() => setFilter('')}
|
||||
>
|
||||
<Icon size="100" src={Icons.Cross} />
|
||||
</IconButton>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Count badge */}
|
||||
{bookmarks.length > 0 && (
|
||||
<Box
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
padding: `${config.space.S100} ${config.space.S300}`,
|
||||
borderBottom: `1px solid ${color.Surface.ContainerLine}`,
|
||||
background: color.SurfaceVariant.Container,
|
||||
}}
|
||||
>
|
||||
<Text size="T200" style={{ opacity: 0.6 }}>
|
||||
{filtered.length === bookmarks.length
|
||||
? `${bookmarks.length} saved message${bookmarks.length !== 1 ? 's' : ''}`
|
||||
: `${filtered.length} of ${bookmarks.length} messages`}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* List */}
|
||||
<Scroll variant="Background" size="300" style={{ flexGrow: 1, minHeight: 0 }}>
|
||||
{filtered.length === 0 ? (
|
||||
<Box
|
||||
direction="Column"
|
||||
alignItems="Center"
|
||||
justifyContent="Center"
|
||||
gap="200"
|
||||
style={{ padding: config.space.S500 }}
|
||||
gap="300"
|
||||
style={{ padding: config.space.S600, textAlign: 'center' }}
|
||||
>
|
||||
<Icon size="600" src={Icons.Star} />
|
||||
<Icon size="600" src={Icons.Star} style={{ opacity: 0.3 }} />
|
||||
<Text size="T300" priority="300" align="Center">
|
||||
{bookmarks.length === 0
|
||||
? 'No saved messages yet. Right-click any message to bookmark it.'
|
||||
: 'No bookmarks match your filter.'}
|
||||
? 'No saved messages yet.\nRight-click any message to bookmark it.'
|
||||
: 'No bookmarks match your search.'}
|
||||
</Text>
|
||||
</Box>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user