cetirizine

An experimental matrix client written in reactjs utilizing tailwind and storybook
git clone git://archive.git.mtrnord.blog/MTRNord/cetirizine.git
Log | Files | Refs | README | LICENSE

commit 875cc90da173b926c56ee49d768459c7072f0238
parent a97d52191d45e0002c40503f9340597a7be87339
Author: MTRNord <mtrnord1@gmail.com>
Date:   Fri, 19 May 2023 20:21:32 +0200

Implement MSC4015 and make displaynames smaller

Diffstat:
Msrc/app/sdk/room.ts | 20++++++++++++++------
Msrc/components/avatar/avatar.tsx | 5+++++
Msrc/components/events/messageEvent.tsx | 126++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/components/events/unknownEvent.tsx | 70++++++++++++++++++++++++++++++++--------------------------------------
Asrc/components/events/wrapper.tsx | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 164 insertions(+), 112 deletions(-)

diff --git a/src/app/sdk/room.ts b/src/app/sdk/room.ts @@ -223,18 +223,26 @@ export class Room extends EventEmitter { public getMemberAvatar(userID: string, size: number = 32): string | undefined { let avatarURL: string | undefined = undefined; this.stateEvents.forEach((event) => { - if (event.type === "m.room.member") { - if (event.state_key === userID && event.content.membership == "join") { - const rawAvatarURL = event.content.avatar_url; - if (rawAvatarURL?.startsWith("mxc://")) { - avatarURL = this.client.convertMXC(rawAvatarURL, size); - } + if (event.type === "m.room.member" && event.state_key === userID && event.content.membership == "join") { + const rawAvatarURL = event.content.avatar_url; + if (rawAvatarURL?.startsWith("mxc://")) { + avatarURL = this.client.convertMXC(rawAvatarURL, size); } } }); return avatarURL; } + public isBot(userID: string): boolean { + let isBot: boolean = false; + this.stateEvents.forEach((event) => { + if (event.type === "m.room.member" && event.state_key === userID && event.content.membership == "join") { + isBot = event.content.bot; + } + }); + return isBot; + } + public isEncrypted(): boolean { let isEncrypted: boolean = false; this.stateEvents.forEach((event) => { diff --git a/src/components/avatar/avatar.tsx b/src/components/avatar/avatar.tsx @@ -18,6 +18,11 @@ type AvatarProps = { * Wether the user is online. Only used if dm is true. */ online: OnlineState + /** + * Wether the user is a bot + * @default false + */ + isBot: boolean }; const Avatar: FC<AvatarProps> = memo(({ avatarUrl, displayname, dm = false, online = OnlineState.Unknown }: AvatarProps) => { diff --git a/src/components/events/messageEvent.tsx b/src/components/events/messageEvent.tsx @@ -1,7 +1,6 @@ import { memo, useContext, useEffect, useState } from "react"; import { IRoomEvent, isRoomMessageAudioEvent, isRoomMessageImageEvent, isRoomMessageNoticeEvent, isRoomMessageTextEvent } from "../../app/sdk/api/events"; import { FC } from "react"; -import Avatar from "../avatar/avatar"; import { MatrixClient, MatrixContext } from "../../app/sdk/client"; import Linkify from "linkify-react"; import linkifyHtml from 'linkify-html'; @@ -13,6 +12,7 @@ import hljs from 'highlight.js'; import 'highlight.js/styles/base16/solarized-dark.css'; import { OnlineState } from "../../app/sdk/api/otherEnums"; import { Room } from "../../app/sdk/room"; +import { MessageWrapper } from "./wrapper"; type MessageEventProps = { /** @@ -117,24 +117,22 @@ const MessageEvent: FC<MessageEventProps> = memo(({ event, room, hasPreviousEven } return ( - <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"}> - {!hasPreviousEvent && <Avatar - displayname={room?.getMemberName(event.sender) || event.sender} - avatarUrl={room?.getMemberAvatar(event.sender)} - online={room?.presence || OnlineState.Unknown} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} - {/* TODO: Loading circle while image is loading */} - <img - src={url} - alt={event.content.body} - title={event.content.body} - className="rounded-md object-cover border-slate-400 border-2 max-h-[50rem] max-w-[50rem] h-[unset]" - /> - </div> - </div> + <MessageWrapper + displayname={room?.getMemberName(event.sender) || event.sender} + avatar_url={room?.getMemberAvatar(event.sender) || ""} + onlineState={room?.presence || OnlineState.Unknown} + isBot={room?.isBot(event.sender) || false} + dm={room?.isDM() || false} + hasPreviousEvent={hasPreviousEvent} + > + {/* TODO: Loading circle while image is loading */} + <img + src={url} + alt={event.content.body} + title={event.content.body} + className="rounded-md object-cover border-slate-400 border-2 max-h-[50rem] max-w-[50rem] h-[unset]" + /> + </MessageWrapper> ) } else if (isRoomMessageAudioEvent(event)) { const [url, setUrl] = useState<string | undefined>(undefined); @@ -169,33 +167,29 @@ const MessageEvent: FC<MessageEventProps> = memo(({ event, room, hasPreviousEven } return ( - <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"}> - {!hasPreviousEvent && <Avatar - displayname={room?.getMemberName(event.sender) || event.sender} - avatarUrl={room?.getMemberAvatar(event.sender)} - online={room?.presence || OnlineState.Unknown} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "flex-1 ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} - {url && <Waveform src_url={url} />} - </div> - </div> + <MessageWrapper + displayname={room?.getMemberName(event.sender) || event.sender} + avatar_url={room?.getMemberAvatar(event.sender) || ""} + onlineState={room?.presence || OnlineState.Unknown} + isBot={room?.isBot(event.sender) || false} + dm={room?.isDM() || false} + hasPreviousEvent={hasPreviousEvent} + > + {url && <Waveform src_url={url} />} + </MessageWrapper> ) } else { return ( - <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"}> - {!hasPreviousEvent && <Avatar - displayname={room?.getMemberName(event.sender) || event.sender} - avatarUrl={room?.getMemberAvatar(event.sender)} - online={room?.presence || OnlineState.Unknown} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} - <Linkify options={linkifyOptions} as='p' className="text-black text-base font-normal">{event.content.body}</Linkify> - </div> - </div> + <MessageWrapper + displayname={room?.getMemberName(event.sender) || event.sender} + avatar_url={room?.getMemberAvatar(event.sender) || ""} + onlineState={room?.presence || OnlineState.Unknown} + isBot={room?.isBot(event.sender) || false} + dm={room?.isDM() || false} + hasPreviousEvent={hasPreviousEvent} + > + <Linkify options={linkifyOptions} as='p' className="text-black text-base font-normal">{event.content.body}</Linkify> + </MessageWrapper > ) } } @@ -286,34 +280,30 @@ const TextMessage: FC<TextMessage> = memo(({ event, room, hasPreviousEvent, mess // TODO: sanitize the attributes allowed by matrix spec return ( - <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"}> - {!hasPreviousEvent && <Avatar - displayname={room?.getMemberName(event.sender) || event.sender} - avatarUrl={room?.getMemberAvatar(event.sender)} - online={room?.presence || OnlineState.Unknown} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} - {/* TODO: Fixme */} - <p className={`${text_color} text-base font-normal`} dangerouslySetInnerHTML={{ __html: linkified }}></p> - </div> - </div> + <MessageWrapper + displayname={room?.getMemberName(event.sender) || event.sender} + avatar_url={room?.getMemberAvatar(event.sender) || ""} + onlineState={room?.presence || OnlineState.Unknown} + isBot={room?.isBot(event.sender) || false} + dm={room?.isDM() || false} + hasPreviousEvent={hasPreviousEvent} + > + {/* TODO: Fixme */} + <p className={`${text_color} text-base font-normal`} dangerouslySetInnerHTML={{ __html: linkified }}></p> + </MessageWrapper> ) } else { return ( - <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"}> - {!hasPreviousEvent && <Avatar - displayname={room?.getMemberName(event.sender) || event.sender} - avatarUrl={room?.getMemberAvatar(event.sender)} - online={room?.presence || OnlineState.Unknown} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} - <Linkify options={linkifyOptions} as='p' className={`${text_color} text-base font-normal`}>{event.content.body}</Linkify> - </div> - </div> + <MessageWrapper + displayname={room?.getMemberName(event.sender) || event.sender} + avatar_url={room?.getMemberAvatar(event.sender) || ""} + onlineState={room?.presence || OnlineState.Unknown} + isBot={room?.isBot(event.sender) || false} + dm={room?.isDM() || false} + hasPreviousEvent={hasPreviousEvent} + > + <Linkify options={linkifyOptions} as='p' className={`${text_color} text-base font-normal`}>{event.content.body}</Linkify> + </MessageWrapper> ) } }) diff --git a/src/components/events/unknownEvent.tsx b/src/components/events/unknownEvent.tsx @@ -2,9 +2,9 @@ import { memo } from "react"; import { IRoomEvent } from "../../app/sdk/api/events"; import { FC } from "react"; import Linkify from "linkify-react"; -import Avatar from "../avatar/avatar"; import { OnlineState } from "../../app/sdk/api/otherEnums"; import { Room } from "../../app/sdk/room"; +import { MessageWrapper } from "./wrapper"; type UnknownEventProps = { /** @@ -30,18 +30,16 @@ const linkifyOptions = { const UnknownEvent: FC<UnknownEventProps> = memo(({ event, room, hasPreviousEvent }) => { return ( - <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"}> - {!hasPreviousEvent && <Avatar - displayname={room?.getMemberName(event.sender) || event.sender} - avatarUrl={room?.getMemberAvatar(event.sender)} - online={room?.presence || OnlineState.Unknown} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} - <Linkify options={linkifyOptions} as='p' className="whitespace-break-spaces text-black text-base font-normal">{JSON.stringify(event, null, 4)}</Linkify> - </div> - </div> + <MessageWrapper + displayname={room?.getMemberName(event.sender) || event.sender} + avatar_url={room?.getMemberAvatar(event.sender) || ""} + onlineState={room?.presence || OnlineState.Unknown} + isBot={room?.isBot(event.sender) || false} + dm={room?.isDM() || false} + hasPreviousEvent={hasPreviousEvent} + > + <Linkify options={linkifyOptions} as='p' className="whitespace-break-spaces text-black text-base font-normal">{JSON.stringify(event, null, 4)}</Linkify> + </MessageWrapper> ) }); @@ -64,18 +62,16 @@ type UndecryptableEventProps = { export const UndecryptableEvent: FC<UndecryptableEventProps> = memo(({ event, room, hasPreviousEvent }) => { return ( - <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"}> - {!hasPreviousEvent && <Avatar - displayname={room?.getMemberName(event.sender) || event.sender} - avatarUrl={room?.getMemberAvatar(event.sender)} - online={room?.presence || OnlineState.Unknown} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} - <Linkify options={linkifyOptions} as='p' className="whitespace-pre-wrap text-base font-normal text-orange-600">Unable to decrypt event</Linkify> - </div> - </div> + <MessageWrapper + displayname={room?.getMemberName(event.sender) || event.sender} + avatar_url={room?.getMemberAvatar(event.sender) || ""} + onlineState={room?.presence || OnlineState.Unknown} + isBot={room?.isBot(event.sender) || false} + dm={room?.isDM() || false} + hasPreviousEvent={hasPreviousEvent} + > + <Linkify options={linkifyOptions} as='p' className="whitespace-pre-wrap text-base font-normal text-orange-600">Unable to decrypt event</Linkify> + </MessageWrapper> ) }); @@ -100,19 +96,17 @@ type RedactedEventProps = { export const RedactedEvent: FC<RedactedEventProps> = memo(({ event, redacted_because, room, hasPreviousEvent }) => { return ( - <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"}> - {!hasPreviousEvent && <Avatar - displayname={room?.getMemberName(event.sender) || event.sender} - avatarUrl={room?.getMemberAvatar(event.sender)} - online={room?.presence || OnlineState.Unknown} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-xl font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} - {redacted_because && <p className="whitespace-pre-wrap text-base font-normal text-blue-600 italic">Message was redacted: {redacted_because}</p>} - {!redacted_because && <p className="whitespace-pre-wrap text-base font-normal text-blue-600 italic">Message was redacted</p>} - </div> - </div> + <MessageWrapper + displayname={room?.getMemberName(event.sender) || event.sender} + avatar_url={room?.getMemberAvatar(event.sender) || ""} + onlineState={room?.presence || OnlineState.Unknown} + isBot={room?.isBot(event.sender) || false} + dm={room?.isDM() || false} + hasPreviousEvent={hasPreviousEvent} + > + {redacted_because && <p className="whitespace-pre-wrap text-base font-normal text-blue-600 italic">Message was redacted: {redacted_because}</p>} + {!redacted_because && <p className="whitespace-pre-wrap text-base font-normal text-blue-600 italic">Message was redacted</p>} + </MessageWrapper> ) }); diff --git a/src/components/events/wrapper.tsx b/src/components/events/wrapper.tsx @@ -0,0 +1,54 @@ +import { FC, memo } from "react"; +import { OnlineState } from "../../app/sdk/api/otherEnums"; +import Avatar from "../avatar/avatar"; +import { Bot } from "lucide-react"; + +export type MessageWrapperProps = { + /** + * The displayname to render + */ + displayname: string; + /** + * The avatar_url to render + */ + avatar_url: string; + /** + * If the sender is a bot + */ + isBot: boolean; + /** + * If the sender is online + */ + onlineState: OnlineState; + /** + * If the room is a DM + */ + dm: boolean; + /** + * If the previous event was sent by the same user + */ + hasPreviousEvent?: boolean; + /** + * Children to render + */ + children: React.ReactNode; +}; + + +export const MessageWrapper: FC<MessageWrapperProps> = memo(({ hasPreviousEvent, displayname, avatar_url, onlineState, isBot, dm, children }) => { + return ( + <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"}> + {!hasPreviousEvent && <Avatar + displayname={displayname} + avatarUrl={avatar_url} + online={onlineState} + dm={dm} + isBot={isBot} + />} + <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> + {!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>} + {children} + </div> + </div> + ) +}); +\ No newline at end of file