web/timeline: omit profile on consecutive messages from same sender

This commit is contained in:
Tulir Asokan 2024-10-12 13:41:15 +03:00
parent cba5dbd912
commit 3ded0f4eb9
3 changed files with 61 additions and 12 deletions

View file

@ -33,15 +33,40 @@ div.timeline-event {
}
}
> div.event-time-only {
grid-area: timestamp;
display: none;
align-items: end;
justify-content: center;
font-size: .8rem;
max-height: 1.25rem;
margin-right: .25rem;
}
> div.event-content {
grid-area: content;
overflow: hidden;
}
&.same-sender {
grid-template:
"timestamp content" auto
/ 2.75rem 1fr;
margin-top: 0;
> div.sender-avatar, > div.event-sender-and-time {
display: none;
}
> div.event-time-only {
display: flex;
}
}
&.hidden-event {
grid-template:
"sender avatar content" auto
/ 2.5rem 1.5rem 1fr;
"timestamp avatar content" auto
/ 2.75rem 1.5rem 1fr;
margin-top: 0;
> div.sender-avatar {
@ -58,9 +83,11 @@ div.timeline-event {
}
> div.event-sender-and-time {
> span.event-sender, > span.event-edited {
display: none;
}
> div.event-time-only {
display: flex;
}
}
}

View file

@ -27,6 +27,7 @@ import "./TimelineEvent.css"
export interface TimelineEventProps {
room: RoomStateStore
evt: MemDBEvent
prevEvt: MemDBEvent | null
}
function getBodyType(evt: MemDBEvent): React.FunctionComponent<EventContentProps> {
@ -61,13 +62,22 @@ const EventReactions = ({ reactions }: { reactions: Record<string, number> }) =>
</div>
}
const TimelineEvent = ({ room, evt }: TimelineEventProps) => {
const TimelineEvent = ({ room, evt, prevEvt }: TimelineEventProps) => {
const memberEvt = room.getStateEvent("m.room.member", evt.sender)
const memberEvtContent = memberEvt?.content as MemberEventContent | undefined
const BodyType = getBodyType(evt)
const eventTS = new Date(evt.timestamp)
const editEventTS = evt.last_edit ? new Date(evt.last_edit.timestamp) : null
return <div className={`timeline-event ${BodyType === HiddenEvent ? "hidden-event" : ""}`}>
const wrapperClassNames = ["timeline-event"]
if (BodyType === HiddenEvent) {
wrapperClassNames.push("hidden-event")
} else if (prevEvt?.sender === evt.sender && getBodyType(prevEvt) !== HiddenEvent) {
wrapperClassNames.push("same-sender")
}
const fullTime = fullTimeFormatter.format(eventTS)
const shortTime = formatShortTime(eventTS)
const editTime = editEventTS ? `Edited at ${fullTimeFormatter.format(editEventTS)}` : null
return <div className={wrapperClassNames.join(" ")}>
<div className="sender-avatar" title={evt.sender}>
<img
className="avatar"
@ -78,11 +88,14 @@ const TimelineEvent = ({ room, evt }: TimelineEventProps) => {
</div>
<div className="event-sender-and-time">
<span className="event-sender">{memberEvtContent?.displayname ?? evt.sender}</span>
<span className="event-time" title={fullTimeFormatter.format(eventTS)}>{formatShortTime(eventTS)}</span>
{editEventTS ? <span className="event-edited" title={`Edited at ${fullTimeFormatter.format(editEventTS)}`}>
<span className="event-time" title={fullTime}>{shortTime}</span>
{(editEventTS && editTime) ? <span className="event-edited" title={editTime}>
(edited at {formatShortTime(editEventTS)})
</span> : null}
</div>
<div className="event-time-only">
<span className="event-time" title={editTime ? `${fullTime} - ${editTime}` : fullTime}>{shortTime}</span>
</div>
<div className="event-content">
<BodyType room={room} event={evt}/>
{evt.reactions ? <EventReactions reactions={evt.reactions}/> : null}

View file

@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { use, useCallback, useEffect, useRef } from "react"
import { RoomStateStore, useRoomTimeline } from "../../api/statestore.ts"
import { MemDBEvent } from "../../api/types"
import { ClientContext } from "../ClientContext.ts"
import TimelineEvent from "./TimelineEvent.tsx"
import "./TimelineView.css"
@ -80,15 +81,23 @@ const TimelineView = ({ room }: TimelineViewProps) => {
return () => observer.unobserve(topElem)
}, [loadHistory, topRef])
let prevEvt: MemDBEvent | null = null
return <div className="timeline-view" onScroll={handleScroll} ref={timelineViewRef}>
<div className="timeline-beginning">
<button onClick={loadHistory}>Load history</button>
</div>
<div className="timeline-list">
<div className="timeline-top-ref" ref={topRef}/>
{timeline.map(entry => entry ? <TimelineEvent
key={entry.rowid} room={room} evt={entry}
/> : null)}
{timeline.map(entry => {
if (!entry) {
return null
}
const thisEvt = <TimelineEvent
key={entry.rowid} room={room} evt={entry} prevEvt={prevEvt}
/>
prevEvt = entry
return thisEvt
})}
<div className="timeline-bottom-ref" ref={bottomRef}/>
</div>
</div>