8.diff (21033B)
1 diff --git a/changelog.d/8.feature b/changelog.d/8.feature 2 new file mode 100644 3 index 0000000..ccf3dd6 4 --- /dev/null 5 +++ b/changelog.d/8.feature 6 @@ -0,0 +1 @@ 7 +Add support for MSC4015 8 \ No newline at end of file 9 diff --git a/src/app/sdk/room.ts b/src/app/sdk/room.ts 10 index edbba95..ba7a436 100644 11 --- a/src/app/sdk/room.ts 12 +++ b/src/app/sdk/room.ts 13 @@ -223,18 +223,26 @@ export class Room extends EventEmitter { 14 public getMemberAvatar(userID: string, size: number = 32): string | undefined { 15 let avatarURL: string | undefined = undefined; 16 this.stateEvents.forEach((event) => { 17 - if (event.type === "m.room.member") { 18 - if (event.state_key === userID && event.content.membership == "join") { 19 - const rawAvatarURL = event.content.avatar_url; 20 - if (rawAvatarURL?.startsWith("mxc://")) { 21 - avatarURL = this.client.convertMXC(rawAvatarURL, size); 22 - } 23 + if (event.type === "m.room.member" && event.state_key === userID && event.content.membership == "join") { 24 + const rawAvatarURL = event.content.avatar_url; 25 + if (rawAvatarURL?.startsWith("mxc://")) { 26 + avatarURL = this.client.convertMXC(rawAvatarURL, size); 27 } 28 } 29 }); 30 return avatarURL; 31 } 32 33 + public isBot(userID: string): boolean { 34 + let isBot: boolean = false; 35 + this.stateEvents.forEach((event) => { 36 + if (event.type === "m.room.member" && event.state_key === userID && event.content.membership == "join") { 37 + isBot = event.content.bot; 38 + } 39 + }); 40 + return isBot; 41 + } 42 + 43 public isEncrypted(): boolean { 44 let isEncrypted: boolean = false; 45 this.stateEvents.forEach((event) => { 46 diff --git a/src/components/avatar/avatar.tsx b/src/components/avatar/avatar.tsx 47 index e32d0cf..f154e99 100644 48 --- a/src/components/avatar/avatar.tsx 49 +++ b/src/components/avatar/avatar.tsx 50 @@ -18,6 +18,11 @@ type AvatarProps = { 51 * Wether the user is online. Only used if dm is true. 52 */ 53 online: OnlineState 54 + /** 55 + * Wether the user is a bot 56 + * @default false 57 + */ 58 + isBot: boolean 59 }; 60 61 const Avatar: FC<AvatarProps> = memo(({ avatarUrl, displayname, dm = false, online = OnlineState.Unknown }: AvatarProps) => { 62 diff --git a/src/components/events/messageEvent.tsx b/src/components/events/messageEvent.tsx 63 index 700eba7..fa9e9f7 100644 64 --- a/src/components/events/messageEvent.tsx 65 +++ b/src/components/events/messageEvent.tsx 66 @@ -1,7 +1,6 @@ 67 import { memo, useContext, useEffect, useState } from "react"; 68 import { IRoomEvent, isRoomMessageAudioEvent, isRoomMessageImageEvent, isRoomMessageNoticeEvent, isRoomMessageTextEvent } from "../../app/sdk/api/events"; 69 import { FC } from "react"; 70 -import Avatar from "../avatar/avatar"; 71 import { MatrixClient, MatrixContext } from "../../app/sdk/client"; 72 import Linkify from "linkify-react"; 73 import linkifyHtml from 'linkify-html'; 74 @@ -13,6 +12,7 @@ import hljs from 'highlight.js'; 75 import 'highlight.js/styles/base16/solarized-dark.css'; 76 import { OnlineState } from "../../app/sdk/api/otherEnums"; 77 import { Room } from "../../app/sdk/room"; 78 +import { MessageWrapper } from "./wrapper"; 79 80 type MessageEventProps = { 81 /** 82 @@ -117,24 +117,22 @@ const MessageEvent: FC<MessageEventProps> = memo(({ event, room, hasPreviousEven 83 } 84 85 return ( 86 - <div className={!hasPreviousEvent ? "flex flex-row gap-4 p-2 pb-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start" : "flex flex-row p-2 pb-1 pt-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> 87 - {!hasPreviousEvent && <Avatar 88 - displayname={room?.getMemberName(event.sender) || event.sender} 89 - avatarUrl={room?.getMemberAvatar(event.sender)} 90 - online={room?.presence || OnlineState.Unknown} 91 - dm={room?.isDM() || false} 92 - />} 93 - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> 94 - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} 95 - {/* TODO: Loading circle while image is loading */} 96 - <img 97 - src={url} 98 - alt={event.content.body} 99 - title={event.content.body} 100 - className="rounded-md object-cover border-slate-400 border-2 max-h-[50rem] max-w-[50rem] h-[unset]" 101 - /> 102 - </div> 103 - </div> 104 + <MessageWrapper 105 + displayname={room?.getMemberName(event.sender) || event.sender} 106 + avatar_url={room?.getMemberAvatar(event.sender) || ""} 107 + onlineState={room?.presence || OnlineState.Unknown} 108 + isBot={room?.isBot(event.sender) || false} 109 + dm={room?.isDM() || false} 110 + hasPreviousEvent={hasPreviousEvent} 111 + > 112 + {/* TODO: Loading circle while image is loading */} 113 + <img 114 + src={url} 115 + alt={event.content.body} 116 + title={event.content.body} 117 + className="rounded-md object-cover border-slate-400 border-2 max-h-[50rem] max-w-[50rem] h-[unset]" 118 + /> 119 + </MessageWrapper> 120 ) 121 } else if (isRoomMessageAudioEvent(event)) { 122 const [url, setUrl] = useState<string | undefined>(undefined); 123 @@ -169,33 +167,29 @@ const MessageEvent: FC<MessageEventProps> = memo(({ event, room, hasPreviousEven 124 } 125 126 return ( 127 - <div className={!hasPreviousEvent ? "flex flex-row gap-4 p-2 pb-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start" : "flex flex-row p-2 pb-1 pt-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> 128 - {!hasPreviousEvent && <Avatar 129 - displayname={room?.getMemberName(event.sender) || event.sender} 130 - avatarUrl={room?.getMemberAvatar(event.sender)} 131 - online={room?.presence || OnlineState.Unknown} 132 - dm={room?.isDM() || false} 133 - />} 134 - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "flex-1 ml-[3.7rem]"}> 135 - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} 136 - {url && <Waveform src_url={url} />} 137 - </div> 138 - </div> 139 + <MessageWrapper 140 + displayname={room?.getMemberName(event.sender) || event.sender} 141 + avatar_url={room?.getMemberAvatar(event.sender) || ""} 142 + onlineState={room?.presence || OnlineState.Unknown} 143 + isBot={room?.isBot(event.sender) || false} 144 + dm={room?.isDM() || false} 145 + hasPreviousEvent={hasPreviousEvent} 146 + > 147 + {url && <Waveform src_url={url} />} 148 + </MessageWrapper> 149 ) 150 } else { 151 return ( 152 - <div className={!hasPreviousEvent ? "flex flex-row gap-4 p-2 pb-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start" : "flex flex-row p-2 pb-1 pt-0 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> 153 - {!hasPreviousEvent && <Avatar 154 - displayname={room?.getMemberName(event.sender) || event.sender} 155 - avatarUrl={room?.getMemberAvatar(event.sender)} 156 - online={room?.presence || OnlineState.Unknown} 157 - dm={room?.isDM() || false} 158 - />} 159 - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> 160 - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} 161 - <Linkify options={linkifyOptions} as='p' className="text-black text-base font-normal">{event.content.body}</Linkify> 162 - </div> 163 - </div> 164 + <MessageWrapper 165 + displayname={room?.getMemberName(event.sender) || event.sender} 166 + avatar_url={room?.getMemberAvatar(event.sender) || ""} 167 + onlineState={room?.presence || OnlineState.Unknown} 168 + isBot={room?.isBot(event.sender) || false} 169 + dm={room?.isDM() || false} 170 + hasPreviousEvent={hasPreviousEvent} 171 + > 172 + <Linkify options={linkifyOptions} as='p' className="text-black text-base font-normal">{event.content.body}</Linkify> 173 + </MessageWrapper > 174 ) 175 } 176 } 177 @@ -286,34 +280,30 @@ const TextMessage: FC<TextMessage> = memo(({ event, room, hasPreviousEvent, mess 178 // TODO: sanitize the attributes allowed by matrix spec 179 180 return ( 181 - <div className={!hasPreviousEvent ? "flex flex-row gap-4 p-2 pb-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start" : "flex flex-row p-2 pb-1 pt-0 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> 182 - {!hasPreviousEvent && <Avatar 183 - displayname={room?.getMemberName(event.sender) || event.sender} 184 - avatarUrl={room?.getMemberAvatar(event.sender)} 185 - online={room?.presence || OnlineState.Unknown} 186 - dm={room?.isDM() || false} 187 - />} 188 - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> 189 - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} 190 - {/* TODO: Fixme */} 191 - <p className={`${text_color} text-base font-normal`} dangerouslySetInnerHTML={{ __html: linkified }}></p> 192 - </div> 193 - </div> 194 + <MessageWrapper 195 + displayname={room?.getMemberName(event.sender) || event.sender} 196 + avatar_url={room?.getMemberAvatar(event.sender) || ""} 197 + onlineState={room?.presence || OnlineState.Unknown} 198 + isBot={room?.isBot(event.sender) || false} 199 + dm={room?.isDM() || false} 200 + hasPreviousEvent={hasPreviousEvent} 201 + > 202 + {/* TODO: Fixme */} 203 + <p className={`${text_color} text-base font-normal`} dangerouslySetInnerHTML={{ __html: linkified }}></p> 204 + </MessageWrapper> 205 ) 206 } else { 207 return ( 208 - <div className={!hasPreviousEvent ? "flex flex-row gap-4 p-2 pb-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start" : "flex flex-row p-2 pb-1 pt-0 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> 209 - {!hasPreviousEvent && <Avatar 210 - displayname={room?.getMemberName(event.sender) || event.sender} 211 - avatarUrl={room?.getMemberAvatar(event.sender)} 212 - online={room?.presence || OnlineState.Unknown} 213 - dm={room?.isDM() || false} 214 - />} 215 - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> 216 - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} 217 - <Linkify options={linkifyOptions} as='p' className={`${text_color} text-base font-normal`}>{event.content.body}</Linkify> 218 - </div> 219 - </div> 220 + <MessageWrapper 221 + displayname={room?.getMemberName(event.sender) || event.sender} 222 + avatar_url={room?.getMemberAvatar(event.sender) || ""} 223 + onlineState={room?.presence || OnlineState.Unknown} 224 + isBot={room?.isBot(event.sender) || false} 225 + dm={room?.isDM() || false} 226 + hasPreviousEvent={hasPreviousEvent} 227 + > 228 + <Linkify options={linkifyOptions} as='p' className={`${text_color} text-base font-normal`}>{event.content.body}</Linkify> 229 + </MessageWrapper> 230 ) 231 } 232 }) 233 diff --git a/src/components/events/unknownEvent.tsx b/src/components/events/unknownEvent.tsx 234 index d67f8f6..98886a8 100644 235 --- a/src/components/events/unknownEvent.tsx 236 +++ b/src/components/events/unknownEvent.tsx 237 @@ -2,9 +2,9 @@ import { memo } from "react"; 238 import { IRoomEvent } from "../../app/sdk/api/events"; 239 import { FC } from "react"; 240 import Linkify from "linkify-react"; 241 -import Avatar from "../avatar/avatar"; 242 import { OnlineState } from "../../app/sdk/api/otherEnums"; 243 import { Room } from "../../app/sdk/room"; 244 +import { MessageWrapper } from "./wrapper"; 245 246 type UnknownEventProps = { 247 /** 248 @@ -30,18 +30,16 @@ const linkifyOptions = { 249 250 const UnknownEvent: FC<UnknownEventProps> = memo(({ event, room, hasPreviousEvent }) => { 251 return ( 252 - <div className={!hasPreviousEvent ? "flex flex-row gap-4 p-2 pb-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start" : "flex flex-row p-2 pb-1 pt-0 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> 253 - {!hasPreviousEvent && <Avatar 254 - displayname={room?.getMemberName(event.sender) || event.sender} 255 - avatarUrl={room?.getMemberAvatar(event.sender)} 256 - online={room?.presence || OnlineState.Unknown} 257 - dm={room?.isDM() || false} 258 - />} 259 - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> 260 - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} 261 - <Linkify options={linkifyOptions} as='p' className="whitespace-break-spaces text-black text-base font-normal">{JSON.stringify(event, null, 4)}</Linkify> 262 - </div> 263 - </div> 264 + <MessageWrapper 265 + displayname={room?.getMemberName(event.sender) || event.sender} 266 + avatar_url={room?.getMemberAvatar(event.sender) || ""} 267 + onlineState={room?.presence || OnlineState.Unknown} 268 + isBot={room?.isBot(event.sender) || false} 269 + dm={room?.isDM() || false} 270 + hasPreviousEvent={hasPreviousEvent} 271 + > 272 + <Linkify options={linkifyOptions} as='p' className="whitespace-break-spaces text-black text-base font-normal">{JSON.stringify(event, null, 4)}</Linkify> 273 + </MessageWrapper> 274 ) 275 }); 276 277 @@ -64,18 +62,16 @@ type UndecryptableEventProps = { 278 279 export const UndecryptableEvent: FC<UndecryptableEventProps> = memo(({ event, room, hasPreviousEvent }) => { 280 return ( 281 - <div className={!hasPreviousEvent ? "flex flex-row gap-4 p-2 pb-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start" : "flex flex-row p-2 pb-1 pt-0 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> 282 - {!hasPreviousEvent && <Avatar 283 - displayname={room?.getMemberName(event.sender) || event.sender} 284 - avatarUrl={room?.getMemberAvatar(event.sender)} 285 - online={room?.presence || OnlineState.Unknown} 286 - dm={room?.isDM() || false} 287 - />} 288 - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> 289 - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} 290 - <Linkify options={linkifyOptions} as='p' className="whitespace-pre-wrap text-base font-normal text-orange-600">Unable to decrypt event</Linkify> 291 - </div> 292 - </div> 293 + <MessageWrapper 294 + displayname={room?.getMemberName(event.sender) || event.sender} 295 + avatar_url={room?.getMemberAvatar(event.sender) || ""} 296 + onlineState={room?.presence || OnlineState.Unknown} 297 + isBot={room?.isBot(event.sender) || false} 298 + dm={room?.isDM() || false} 299 + hasPreviousEvent={hasPreviousEvent} 300 + > 301 + <Linkify options={linkifyOptions} as='p' className="whitespace-pre-wrap text-base font-normal text-orange-600">Unable to decrypt event</Linkify> 302 + </MessageWrapper> 303 ) 304 }); 305 306 @@ -100,19 +96,17 @@ type RedactedEventProps = { 307 308 export const RedactedEvent: FC<RedactedEventProps> = memo(({ event, redacted_because, room, hasPreviousEvent }) => { 309 return ( 310 - <div className={!hasPreviousEvent ? "flex flex-row gap-4 p-2 pb-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start" : "flex flex-row p-2 pb-1 pt-0 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> 311 - {!hasPreviousEvent && <Avatar 312 - displayname={room?.getMemberName(event.sender) || event.sender} 313 - avatarUrl={room?.getMemberAvatar(event.sender)} 314 - online={room?.presence || OnlineState.Unknown} 315 - dm={room?.isDM() || false} 316 - />} 317 - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> 318 - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} 319 - {redacted_because && <p className="whitespace-pre-wrap text-base font-normal text-blue-600 italic">Message was redacted: {redacted_because}</p>} 320 - {!redacted_because && <p className="whitespace-pre-wrap text-base font-normal text-blue-600 italic">Message was redacted</p>} 321 - </div> 322 - </div> 323 + <MessageWrapper 324 + displayname={room?.getMemberName(event.sender) || event.sender} 325 + avatar_url={room?.getMemberAvatar(event.sender) || ""} 326 + onlineState={room?.presence || OnlineState.Unknown} 327 + isBot={room?.isBot(event.sender) || false} 328 + dm={room?.isDM() || false} 329 + hasPreviousEvent={hasPreviousEvent} 330 + > 331 + {redacted_because && <p className="whitespace-pre-wrap text-base font-normal text-blue-600 italic">Message was redacted: {redacted_because}</p>} 332 + {!redacted_because && <p className="whitespace-pre-wrap text-base font-normal text-blue-600 italic">Message was redacted</p>} 333 + </MessageWrapper> 334 ) 335 }); 336 337 diff --git a/src/components/events/wrapper.tsx b/src/components/events/wrapper.tsx 338 new file mode 100644 339 index 0000000..23be1ee 340 --- /dev/null 341 +++ b/src/components/events/wrapper.tsx 342 @@ -0,0 +1,54 @@ 343 +import { FC, memo } from "react"; 344 +import { OnlineState } from "../../app/sdk/api/otherEnums"; 345 +import Avatar from "../avatar/avatar"; 346 +import { Bot } from "lucide-react"; 347 + 348 +export type MessageWrapperProps = { 349 + /** 350 + * The displayname to render 351 + */ 352 + displayname: string; 353 + /** 354 + * The avatar_url to render 355 + */ 356 + avatar_url: string; 357 + /** 358 + * If the sender is a bot 359 + */ 360 + isBot: boolean; 361 + /** 362 + * If the sender is online 363 + */ 364 + onlineState: OnlineState; 365 + /** 366 + * If the room is a DM 367 + */ 368 + dm: boolean; 369 + /** 370 + * If the previous event was sent by the same user 371 + */ 372 + hasPreviousEvent?: boolean; 373 + /** 374 + * Children to render 375 + */ 376 + children: React.ReactNode; 377 +}; 378 + 379 + 380 +export const MessageWrapper: FC<MessageWrapperProps> = memo(({ hasPreviousEvent, displayname, avatar_url, onlineState, isBot, dm, children }) => { 381 + return ( 382 + <div className={!hasPreviousEvent ? "flex flex-row gap-4 p-2 pb-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start" : "flex flex-row p-2 pb-1 pt-1 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> 383 + {!hasPreviousEvent && <Avatar 384 + displayname={displayname} 385 + avatarUrl={avatar_url} 386 + online={onlineState} 387 + dm={dm} 388 + isBot={isBot} 389 + />} 390 + <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> 391 + {!hasPreviousEvent && <h2 className="flex flex-row items-center gap-2 text-base font-medium text-red-500 whitespace-pre-wrap">{isBot ? <Bot size={16} /> : <></>}{displayname}</h2>} 392 + {children} 393 + </div> 394 + </div> 395 + ) 396 +}); 397 \ No newline at end of file