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 a9badaa7ba3afe045f4fd01a7a33aa805f659a6e
parent 458b9388d71cbc657fcb6e1c965111224128ef2e
Author: MTRNord <mtrnord1@gmail.com>
Date:   Mon,  8 May 2023 15:05:12 +0200

Add notices support

Diffstat:
Msrc/app/sdk/api/apiTypes.ts | 6++++++
Msrc/components/events/messageEvent.tsx | 214++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
2 files changed, 126 insertions(+), 94 deletions(-)

diff --git a/src/app/sdk/api/apiTypes.ts b/src/app/sdk/api/apiTypes.ts @@ -332,6 +332,12 @@ export function isRoomMessageTextEvent(event: IRoomEvent): event is IRoomMessage return event.type === "m.room.message" && event.content.msgtype === "m.text"; } +export interface IRoomMessageNoticeEvent extends IRoomMessageEvent<IRoomMessageTextContent> { } + +export function isRoomMessageNoticeEvent(event: IRoomEvent): event is IRoomMessageNoticeEvent { + return event.type === "m.room.message" && event.content.msgtype === "m.notice"; +} + export function isRoomMessageImageEvent(event: IRoomEvent): event is IRoomMessageEvent<IRoomMessageImageContent> { return event.type === "m.room.message" && event.content.msgtype === "m.image"; } diff --git a/src/components/events/messageEvent.tsx b/src/components/events/messageEvent.tsx @@ -1,5 +1,5 @@ import { memo, useContext, useEffect, useState } from "react"; -import { IRoomEvent, isRoomMessageAudioEvent, isRoomMessageImageEvent, isRoomMessageTextEvent } from "../../app/sdk/api/apiTypes"; +import { IRoomEvent, isRoomMessageAudioEvent, isRoomMessageImageEvent, isRoomMessageNoticeEvent, isRoomMessageTextEvent } from "../../app/sdk/api/apiTypes"; import { FC } from "react"; import Avatar from "../avatar/avatar"; import { MatrixContext, useRoom } from "../../app/sdk/client"; @@ -38,105 +38,16 @@ const linkifyOptions = { className: "text-blue-500 hover:text-blue-700 active:text-blue-700 visited:text-blue-500" } -const MessageEvent: FC<MessageEventProps> = memo(({ event, roomID, hasPreviousEvent }) => { +const MessageEvent: FC<MessageEventProps> = memo(({ event, roomID, hasPreviousEvent, reactions }) => { const client = useContext(MatrixContext); const room = useRoom(roomID); const renderCorrectMessage = (event: IRoomEvent) => { if (isRoomMessageTextEvent(event)) { - if (event.content.format === "org.matrix.custom.html") { - let sanitized = DOMPurify.sanitize(event.content.formatted_body!, { - ADD_TAGS: [ - "font", - "del", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "blockquote", - "p", - "a", - "ul", - "ol", - "sup", - "sub", - "li", - "b", - "i", - "u", - "strong", - "em", - "strike", - "code", - "hr", - "br", - "div", - "table", - "thead", - "tbody", - "tr", - "th", - "td", - "caption", - "pre", - "span", - "img", - "details", - "summary" - ] - }) - // Extract code and language from the html - const codeRegex = /<pre><code (?:class="language-(?<language>.*?)")?.*?>(?<code>[\s\S]*?)<\/code><\/pre>/; - const code = codeRegex.exec(sanitized); - - if (code?.groups?.["code"]) { - if (code.groups?.["language"]) { - // Highlight the code - const highlighted = hljs.highlight(code.groups?.["code"], { language: code.groups?.["language"] }).value; - sanitized = sanitized.replace(code.groups?.["code"], `${highlighted}`); - } else { - // Highlight the code - const highlighted = hljs.highlightAuto(code.groups?.["code"]).value; - sanitized = sanitized.replace(code[0], `${highlighted}`); - } - } - const linkified = linkifyHtml(sanitized, linkifyOptions); - // 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?.isOnline() || false} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-sm font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} - {/* TODO: Fixme */} - <p className="whitespace-pre-wrap text-black text-base font-normal" dangerouslySetInnerHTML={{ __html: linkified }}></p> - </div> - </div> - ) - } 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?.isOnline() || false} - dm={room?.isDM() || false} - />} - <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> - {!hasPreviousEvent && <h2 className="text-sm 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-black text-base font-normal">{event.content.body}</Linkify> - </div> - </div> - ) - } + return (<TextMessage event={event} roomID={roomID} hasPreviousEvent={hasPreviousEvent} reactions={reactions} />) + } else if (isRoomMessageNoticeEvent(event)) { + return (<TextMessage event={event} roomID={roomID} hasPreviousEvent={hasPreviousEvent} reactions={reactions} message_type={MessageType.Notice} />) } else if (isRoomMessageImageEvent(event)) { const [url, setUrl] = useState<string | undefined>(undefined); const [unableToDecrypt, setUnableToDecrypt] = useState<boolean>(event.content.file !== undefined); @@ -310,6 +221,121 @@ const MessageEvent: FC<MessageEventProps> = memo(({ event, roomID, hasPreviousEv export default MessageEvent; +enum MessageType { + Text, + Notice, + Emote +} + +interface TextMessage extends MessageEventProps { + /** + * Which type the message is + */ + message_type?: MessageType; +} + +const TextMessage: FC<TextMessage> = memo(({ event, roomID, hasPreviousEvent, message_type = MessageType.Text }) => { + const room = useRoom(roomID); + + let text_color = "text-black"; + if (message_type === MessageType.Notice) { + text_color = "text-slate-500" + } + + if (event.content.format === "org.matrix.custom.html") { + let sanitized = DOMPurify.sanitize(event.content.formatted_body!, { + ADD_TAGS: [ + "font", + "del", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "blockquote", + "p", + "a", + "ul", + "ol", + "sup", + "sub", + "li", + "b", + "i", + "u", + "strong", + "em", + "strike", + "code", + "hr", + "br", + "div", + "table", + "thead", + "tbody", + "tr", + "th", + "td", + "caption", + "pre", + "span", + "img", + "details", + "summary" + ] + }) + // Extract code and language from the html + const codeRegex = /<pre><code (?:class="language-(?<language>.*?)")?.*?>(?<code>[\s\S]*?)<\/code><\/pre>/; + const code = codeRegex.exec(sanitized); + + if (code?.groups?.["code"]) { + if (code.groups?.["language"]) { + // Highlight the code + const highlighted = hljs.highlight(code.groups?.["code"], { language: code.groups?.["language"] }).value; + sanitized = sanitized.replace(code.groups?.["code"], `${highlighted}`); + } else { + // Highlight the code + const highlighted = hljs.highlightAuto(code.groups?.["code"]).value; + sanitized = sanitized.replace(code[0], `${highlighted}`); + } + } + const linkified = linkifyHtml(sanitized, linkifyOptions); + // 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?.isOnline() || false} + dm={room?.isDM() || false} + />} + <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> + {!hasPreviousEvent && <h2 className="text-sm font-medium text-red-500 whitespace-pre-wrap">{room?.getMemberName(event.sender) || event.sender}</h2>} + {/* TODO: Fixme */} + <p className={`whitespace-pre-wrap ${text_color} text-base font-normal`} dangerouslySetInnerHTML={{ __html: linkified }}></p> + </div> + </div> + ) + } 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?.isOnline() || false} + dm={room?.isDM() || false} + />} + <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> + {!hasPreviousEvent && <h2 className="text-sm 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_color} text-base font-normal`}>{event.content.body}</Linkify> + </div> + </div> + ) + } +}) + // WARNING: We have to be very careful about what mime-types we allow into blobs, // as for performance reasons these are now rendered via URL.createObjectURL()