forked from Mirrors/gomuks
web/timeline: subscribe to event sender state
This commit is contained in:
parent
ab06dbf5aa
commit
3296c38454
7 changed files with 38 additions and 14 deletions
|
@ -14,7 +14,7 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { useSyncExternalStore } from "react"
|
||||
import type { EventID, MemDBEvent } from "../types"
|
||||
import type { EventID, EventType, MemDBEvent } from "../types"
|
||||
import { RoomStateStore } from "./room.ts"
|
||||
|
||||
export function useRoomTimeline(room: RoomStateStore): (MemDBEvent | null)[] {
|
||||
|
@ -24,6 +24,15 @@ export function useRoomTimeline(room: RoomStateStore): (MemDBEvent | null)[] {
|
|||
)
|
||||
}
|
||||
|
||||
export function useRoomState(
|
||||
room: RoomStateStore, type: EventType, stateKey: string | undefined = "",
|
||||
): MemDBEvent | null {
|
||||
return useSyncExternalStore(
|
||||
stateKey === undefined ? noopSubscribe : room.getStateSubscriber(type, stateKey).subscribe,
|
||||
stateKey === undefined ? returnNull : (() => room.getStateEvent(type, stateKey) ?? null),
|
||||
)
|
||||
}
|
||||
|
||||
const noopSubscribe = () => () => {}
|
||||
const returnNull = () => null
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ export class RoomStateStore {
|
|||
readonly eventsByRowID: Map<EventRowID, MemDBEvent> = new Map()
|
||||
readonly eventsByID: Map<EventID, MemDBEvent> = new Map()
|
||||
readonly timelineSub = new Subscribable()
|
||||
readonly stateSubs: Map<EventID, Subscribable> = new Map()
|
||||
readonly eventSubs: Map<EventID, EventSubscribable> = new Map()
|
||||
readonly openNotifications: Map<EventRowID, Notification> = new Map()
|
||||
readonly pendingEvents: EventRowID[] = []
|
||||
|
@ -110,6 +111,21 @@ export class RoomStateStore {
|
|||
return sub
|
||||
}
|
||||
|
||||
notifyStateSubscribers(eventType: EventType, stateKey: string) {
|
||||
const subKey = `${eventType}:${stateKey}`
|
||||
this.stateSubs.get(subKey)?.notify()
|
||||
}
|
||||
|
||||
getStateSubscriber(eventType: EventType, stateKey: string): Subscribable {
|
||||
const subKey = `${eventType}:${stateKey}`
|
||||
let sub = this.stateSubs.get(subKey)
|
||||
if (!sub) {
|
||||
sub = new Subscribable(() => this.stateSubs.delete(subKey))
|
||||
this.stateSubs.set(subKey, sub)
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
getStateEvent(type: EventType, stateKey: string): MemDBEvent | undefined {
|
||||
const rowID = this.state.get(type)?.get(stateKey)
|
||||
if (!rowID) {
|
||||
|
@ -200,6 +216,7 @@ export class RoomStateStore {
|
|||
}
|
||||
for (const [key, rowID] of Object.entries(changedEvts)) {
|
||||
stateMap.set(key, rowID)
|
||||
this.notifyStateSubscribers(evtType, key)
|
||||
}
|
||||
}
|
||||
if (sync.reset) {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { use } from "react"
|
||||
import { getAvatarURL } from "@/api/media.ts"
|
||||
import { RoomStateStore, useRoomEvent } from "@/api/statestore"
|
||||
import { RoomStateStore, useRoomEvent, useRoomState } from "@/api/statestore"
|
||||
import type { EventID, MemDBEvent, MemberEventContent } from "@/api/types"
|
||||
import { ClientContext } from "../ClientContext.ts"
|
||||
import getBodyType, { ContentErrorBoundary } from "./content"
|
||||
|
@ -63,7 +63,7 @@ const onClickReply = (evt: React.MouseEvent) => {
|
|||
}
|
||||
|
||||
export const ReplyBody = ({ room, event, onClose }: ReplyBodyProps) => {
|
||||
const memberEvt = room.getStateEvent("m.room.member", event.sender)
|
||||
const memberEvt = useRoomState(room, "m.room.member", event.sender)
|
||||
const memberEvtContent = memberEvt?.content as MemberEventContent | undefined
|
||||
const BodyType = getBodyType(event, true)
|
||||
return <blockquote
|
||||
|
@ -84,7 +84,7 @@ export const ReplyBody = ({ room, event, onClose }: ReplyBodyProps) => {
|
|||
{onClose && <button className="close-reply" onClick={onClose}><CloseButton/></button>}
|
||||
</div>
|
||||
<ContentErrorBoundary>
|
||||
<BodyType room={room} event={event}/>
|
||||
<BodyType room={room} event={event} sender={memberEvt}/>
|
||||
</ContentErrorBoundary>
|
||||
</blockquote>
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import React, { use, useCallback } from "react"
|
||||
import { getAvatarURL } from "@/api/media.ts"
|
||||
import { RoomStateStore } from "@/api/statestore"
|
||||
import { RoomStateStore, useRoomState } from "@/api/statestore"
|
||||
import { EventID, MemDBEvent, MemberEventContent, UnreadType } from "@/api/types"
|
||||
import { isEventID } from "@/util/validation.ts"
|
||||
import { ClientContext } from "../ClientContext.ts"
|
||||
|
@ -66,7 +66,7 @@ function isSmallEvent(bodyType: React.FunctionComponent<EventContentProps>): boo
|
|||
const TimelineEvent = ({ room, evt, prevEvt, setReplyToRef }: TimelineEventProps) => {
|
||||
const wrappedSetReplyTo = useCallback(() => setReplyToRef.current(evt.event_id), [evt, setReplyToRef])
|
||||
const client = use(ClientContext)!
|
||||
const memberEvt = room.getStateEvent("m.room.member", evt.sender)
|
||||
const memberEvt = useRoomState(room, "m.room.member", evt.sender)
|
||||
const memberEvtContent = memberEvt?.content as MemberEventContent | undefined
|
||||
const BodyType = getBodyType(evt)
|
||||
const eventTS = new Date(evt.timestamp)
|
||||
|
|
|
@ -18,11 +18,11 @@ import TextMessageBody from "./TextMessageBody.tsx"
|
|||
import EventContentProps from "./props.ts"
|
||||
import { useMediaContent } from "./useMediaContent.tsx"
|
||||
|
||||
const MediaMessageBody = ({ event, room }: EventContentProps) => {
|
||||
const MediaMessageBody = ({ event, room, sender }: EventContentProps) => {
|
||||
const content = event.content as MediaMessageEventContent
|
||||
let caption = null
|
||||
if (content.body && content.filename && content.body !== content.filename) {
|
||||
caption = <TextMessageBody event={event} room={room} />
|
||||
caption = <TextMessageBody event={event} room={room} sender={sender} />
|
||||
}
|
||||
const [mediaContent, containerClass, containerStyle] = useMediaContent(content, event.type)
|
||||
return <>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { MemberEventContent, MessageEventContent } from "@/api/types"
|
||||
import { MessageEventContent } from "@/api/types"
|
||||
import EventContentProps from "./props.ts"
|
||||
|
||||
const onClickHTML = (evt: React.MouseEvent<HTMLDivElement>) => {
|
||||
|
@ -23,7 +23,7 @@ const onClickHTML = (evt: React.MouseEvent<HTMLDivElement>) => {
|
|||
}
|
||||
}
|
||||
|
||||
const TextMessageBody = ({ event, room }: EventContentProps) => {
|
||||
const TextMessageBody = ({ event, sender }: EventContentProps) => {
|
||||
const content = event.content as MessageEventContent
|
||||
const classNames = ["message-text"]
|
||||
let eventSenderName: string | undefined
|
||||
|
@ -31,9 +31,7 @@ const TextMessageBody = ({ event, room }: EventContentProps) => {
|
|||
classNames.push("notice-message")
|
||||
} else if (content.msgtype === "m.emote") {
|
||||
classNames.push("emote-message")
|
||||
const memberEvt = room.getStateEvent("m.room.member", event.sender)
|
||||
const memberEvtContent = memberEvt?.content as MemberEventContent | undefined
|
||||
eventSenderName = memberEvtContent?.displayname || event.sender
|
||||
eventSenderName = sender?.content?.displayname || event.sender
|
||||
}
|
||||
if (event.local_content?.sanitized_html) {
|
||||
classNames.push("html-body")
|
||||
|
|
|
@ -19,5 +19,5 @@ import { MemDBEvent } from "@/api/types"
|
|||
export default interface EventContentProps {
|
||||
room: RoomStateStore
|
||||
event: MemDBEvent
|
||||
sender?: MemDBEvent
|
||||
sender: MemDBEvent | null
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue