Private
Public Access
A bare "4:04 PM" gave no way to tell today's message from one sent last
week. Today stays time-only; older messages gain just enough date to
place them: "Yesterday 4:04 PM", a weekday for the past week ("Mon
4:04 PM"), then "Oct 2, 4:04 PM" (with the year if it isn't this one).
Hovering a time shows the full date. All in the viewer's local time.
A new calendar day now always starts a new message group. Previously a
same-sender run that crossed midnight within the 5-minute window (11:58
PM then 12:01 AM) stayed under one header, which would have put the
later message under the wrong day's timestamp. The grouping rule moves
out of MessageList into messageGrouping.ts so it's unit-tested.
useLocalDay refreshes "today" at local midnight and when a hidden tab
becomes visible again, so a tab left open overnight doesn't keep showing
yesterday's messages as a bare time. .message-time no longer wraps, and
.message-author can shrink, so a long display name can't push the longer
label out of the row on a phone.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
51 lines
2.2 KiB
TypeScript
51 lines
2.2 KiB
TypeScript
import { getUserAvatarUrl } from '../api/users'
|
|
import { hashIndex } from './avatar'
|
|
import { isSameLocalDay } from './formatMessageTime'
|
|
import type { RoomMember } from '../types'
|
|
|
|
// #73: Slack's own threshold for the same "still grouped, but it's been a
|
|
// while" call -- past this gap a same-sender message starts a new group
|
|
// (its own avatar/name/timestamp) even with nobody else posting in
|
|
// between, so a message sent minutes later doesn't hide under a stale
|
|
// timestamp from the start of the run.
|
|
export const GROUP_BREAK_MS = 5 * 60 * 1000
|
|
|
|
interface Groupable {
|
|
user_id: string
|
|
created_at: string
|
|
}
|
|
|
|
// Whether `msg` gets its own avatar/name/timestamp header, vs. continuing
|
|
// `prev`'s group. A new calendar day always breaks the group: now that
|
|
// timestamps can carry a date, a same-sender run that crosses midnight
|
|
// (11:58 PM, then 12:01 AM) would otherwise leave the later message under a
|
|
// header showing the wrong day.
|
|
export function isGroupStart(prev: Groupable | undefined, msg: Groupable): boolean {
|
|
if (!prev || prev.user_id !== msg.user_id) return true
|
|
if (new Date(msg.created_at).getTime() - new Date(prev.created_at).getTime() > GROUP_BREAK_MS) return true
|
|
return !isSameLocalDay(prev.created_at, msg.created_at)
|
|
}
|
|
|
|
export function senderColorIndex(username: string, members: RoomMember[]): number {
|
|
const idx = members.findIndex((m) => m.username === username)
|
|
if (idx >= 0) return idx
|
|
// Fallback for a sender no longer in the room (e.g. they left): derive a
|
|
// stable index from the username instead of always colliding on 0.
|
|
return hashIndex(username)
|
|
}
|
|
|
|
export function avatarUrlFor(username: string, members: RoomMember[]): string | null {
|
|
const member = members.find((m) => m.username === username)
|
|
if (!member?.avatar_filename) return null
|
|
return getUserAvatarUrl(member.user_id, member.avatar_filename)
|
|
}
|
|
|
|
export function displayNameFor(username: string, members: RoomMember[]): string {
|
|
const member = members.find((m) => m.username === username)
|
|
return member?.display_name || username
|
|
}
|
|
|
|
export function statusFor(username: string, members: RoomMember[]): 'online' | 'offline' | undefined {
|
|
return members.find((m) => m.username === username)?.status
|
|
}
|