1
0
Fork 0
forked from Mirrors/gomuks

web/timeline: allow jumping to reply if it's loaded in the timeline

This commit is contained in:
Tulir Asokan 2024-10-19 17:09:35 +03:00
parent e6121149b3
commit 3fdaf8ae4e
4 changed files with 40 additions and 4 deletions

View file

@ -180,7 +180,10 @@ const MessageComposer = ({ room, scrollToBottomRef, setReplyToRef }: MessageComp
}, [room, state]) }, [room, state])
const openFilePicker = useCallback(() => fileInput.current!.click(), []) const openFilePicker = useCallback(() => fileInput.current!.click(), [])
const clearMedia = useCallback(() => setState({ media: null }), []) const clearMedia = useCallback(() => setState({ media: null }), [])
const closeReply = useCallback(() => setState({ replyTo: null }), []) const closeReply = useCallback((evt: React.MouseEvent) => {
evt.stopPropagation()
setState({ replyTo: null })
}, [])
return <div className="message-composer"> return <div className="message-composer">
{replyToEvt && <ReplyBody room={room} event={replyToEvt} onClose={closeReply}/>} {replyToEvt && <ReplyBody room={room} event={replyToEvt} onClose={closeReply}/>}
{loadingMedia && <div className="composer-media"><ScaleLoader/></div>} {loadingMedia && <div className="composer-media"><ScaleLoader/></div>}

View file

@ -25,7 +25,7 @@ import "./ReplyBody.css"
interface ReplyBodyProps { interface ReplyBodyProps {
room: RoomStateStore room: RoomStateStore
event: MemDBEvent event: MemDBEvent
onClose?: () => void onClose?: (evt: React.MouseEvent) => void
} }
interface ReplyIDBodyProps { interface ReplyIDBodyProps {
@ -45,10 +45,31 @@ export const ReplyIDBody = ({ room, eventID }: ReplyIDBodyProps) => {
return <ReplyBody room={room} event={event}/> return <ReplyBody room={room} event={event}/>
} }
const onClickReply = (evt: React.MouseEvent) => {
const targetEvt = document.querySelector(`div[data-event-id="${evt.currentTarget.getAttribute("data-reply-to")}"]`)
if (targetEvt) {
targetEvt.scrollIntoView({
block: "center",
})
targetEvt.classList.add("jump-highlight")
setTimeout(() => {
targetEvt.classList.add("jump-highlight-fadeout")
targetEvt.classList.remove("jump-highlight")
setTimeout(() => {
targetEvt.classList.remove("jump-highlight-fadeout")
}, 1500)
}, 3000)
}
}
export const ReplyBody = ({ room, event, onClose }: ReplyBodyProps) => { export const ReplyBody = ({ room, event, onClose }: ReplyBodyProps) => {
const memberEvt = room.getStateEvent("m.room.member", event.sender) const memberEvt = room.getStateEvent("m.room.member", event.sender)
const memberEvtContent = memberEvt?.content as MemberEventContent | undefined const memberEvtContent = memberEvt?.content as MemberEventContent | undefined
return <blockquote className={`reply-body ${onClose ? "composer" : ""}`}> return <blockquote
data-reply-to={event.event_id}
className={`reply-body ${onClose ? "composer" : ""}`}
onClick={onClickReply}
>
<div className="reply-sender"> <div className="reply-sender">
<div className="sender-avatar" title={event.sender}> <div className="sender-avatar" title={event.sender}>
<img <img

View file

@ -14,12 +14,24 @@ div.timeline-event {
background-color: rgba(255, 255, 0, .1); background-color: rgba(255, 255, 0, .1);
} }
&.jump-highlight {
background-color: rgba(0, 255, 0, .2);
}
&.jump-highlight-fadeout {
transition: background-color 1s;
}
&:hover { &:hover {
background-color: #eee; background-color: #eee;
&.highlight { &.highlight {
background-color: #eec; background-color: #eec;
} }
&.jump-highlight {
background-color: #cec;
}
} }
> div.sender-avatar { > div.sender-avatar {

View file

@ -135,7 +135,7 @@ const TimelineEvent = ({ room, evt, prevEvt, setReplyToRef }: TimelineEventProps
const shortTime = formatShortTime(eventTS) const shortTime = formatShortTime(eventTS)
const editTime = editEventTS ? `Edited at ${fullTimeFormatter.format(editEventTS)}` : null const editTime = editEventTS ? `Edited at ${fullTimeFormatter.format(editEventTS)}` : null
const replyTo = (evt.orig_content ?? evt.content)["m.relates_to"]?.["m.in_reply_to"]?.event_id const replyTo = (evt.orig_content ?? evt.content)["m.relates_to"]?.["m.in_reply_to"]?.event_id
const mainEvent = <div className={wrapperClassNames.join(" ")}> const mainEvent = <div data-event-id={evt.event_id} className={wrapperClassNames.join(" ")}>
<div className="sender-avatar" title={evt.sender}> <div className="sender-avatar" title={evt.sender}>
<img <img
className={`${smallAvatar ? "small" : ""} avatar`} className={`${smallAvatar ? "small" : ""} avatar`}