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 61806d86dd2e468cf10104de32a8107aa9920f39
parent d563b153e63de281edf9b37a1d50dcfccb1b7147
Author: MTRNord <mtrnord1@gmail.com>
Date:   Thu,  4 May 2023 21:43:26 +0200

Purify some of the html (attributes missing) and align avatars properly in text messages

Diffstat:
Mpackage-lock.json | 26++++++++++++++++----------
Mpackage.json | 2+-
Msrc/components/events/messageEvent.tsx | 54+++++++++++++++++++++++++++++++++++++++++++++++++-----
3 files changed, 66 insertions(+), 16 deletions(-)

diff --git a/package-lock.json b/package-lock.json @@ -19,7 +19,6 @@ "@types/react-dom": "^18.0.11", "dompurify": "^3.0.2", "idb": "^7.1.1", - "immer": "^10.0.1", "lexical": "^0.10.0", "linkify-react": "^4.1.1", "linkifyjs": "^4.1.1", @@ -43,6 +42,7 @@ "@storybook/react-vite": "^7.0.7", "@storybook/testing-library": "^0.1.0", "@tailwindcss/forms": "^0.5.3", + "@types/dompurify": "^3.0.2", "@vitejs/plugin-react-swc": "^3.3.0", "autoprefixer": "^10.4.14", "postcss": "^8.4.23", @@ -5401,6 +5401,15 @@ "integrity": "sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==", "dev": true }, + "node_modules/@types/dompurify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.2.tgz", + "integrity": "sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==", + "dev": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/ejs": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.2.tgz", @@ -5656,6 +5665,12 @@ "@types/jest": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==", + "dev": true + }, "node_modules/@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -8987,15 +9002,6 @@ "node": ">=0.10.0" } }, - "node_modules/immer": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.1.tgz", - "integrity": "sha512-zg++jJLsKKTwXGeSYIw0HgChSYQGtu0UDTnbKx5aGLYgte4CwTmH9eJDYyQ6FheyUtBe+lQW9FrGxya1G+Dtmg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/immutable": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", diff --git a/package.json b/package.json @@ -15,7 +15,6 @@ "@types/react-dom": "^18.0.11", "dompurify": "^3.0.2", "idb": "^7.1.1", - "immer": "^10.0.1", "lexical": "^0.10.0", "linkify-react": "^4.1.1", "linkifyjs": "^4.1.1", @@ -74,6 +73,7 @@ "@storybook/react-vite": "^7.0.7", "@storybook/testing-library": "^0.1.0", "@tailwindcss/forms": "^0.5.3", + "@types/dompurify": "^3.0.2", "@vitejs/plugin-react-swc": "^3.3.0", "autoprefixer": "^10.4.14", "postcss": "^8.4.23", diff --git a/src/components/events/messageEvent.tsx b/src/components/events/messageEvent.tsx @@ -4,6 +4,7 @@ import { FC } from "react"; import Avatar from "../avatar/avatar"; import { useRoom } from "../../app/sdk/client"; import Linkify from "linkify-react"; +import DOMPurify from "dompurify"; type MessageEventProps = { /** @@ -32,9 +33,52 @@ const MessageEvent: FC<MessageEventProps> = memo(({ event, roomID, hasPreviousEv const renderCorrectMessage = (event: IRoomEvent) => { if (isRoomMessageTextEvent(event)) { if (event.content.format === "org.matrix.custom.html") { - // TODO: Sanitize HTML + const 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" + ] + }) + // 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" : "flex flex-row p-2 pt-0 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> + <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 pt-0 hover:bg-gray-200 rounded-md duration-200 ease-in-out"}> {!hasPreviousEvent && <Avatar displayname={room?.getMemberName(event.sender) || ""} avatarUrl={room?.getMemberAvatar(event.sender)} @@ -44,13 +88,13 @@ const MessageEvent: FC<MessageEventProps> = memo(({ event, roomID, hasPreviousEv <div className={!hasPreviousEvent ? "flex flex-col gap-1" : "ml-[3.7rem]"}> {!hasPreviousEvent && <h2 className="text-sm font-medium text-red-500 whitespace-normal">{room?.getMemberName(event.sender)}</h2>} {/* TODO: Fixme */} - <p className="whitespace-normal" dangerouslySetInnerHTML={{ __html: event.content.formatted_body! }}></p> + <p className="whitespace-normal text-black text-base font-normal" dangerouslySetInnerHTML={{ __html: sanitized }}></p> </div> </div> ) } else { return ( - <div className="flex flex-row gap-4 p-2 hover:bg-gray-200 rounded-md duration-200 ease-in-out"> + <div className="flex flex-row gap-4 p-2 hover:bg-gray-200 rounded-md duration-200 ease-in-out items-start"> <Avatar displayname={room?.getMemberName(event.sender) || ""} avatarUrl={room?.getMemberAvatar(event.sender)} @@ -59,7 +103,7 @@ const MessageEvent: FC<MessageEventProps> = memo(({ event, roomID, hasPreviousEv /> <div className="flex flex-col gap-2"> <h2 className="text-sm font-medium text-red-500 whitespace-normal">{room?.getMemberName(event.sender)}</h2> - <Linkify options={linkifyOptions} as='p' className="whitespace-normal">{event.content.body}</Linkify> + <Linkify options={linkifyOptions} as='p' className="whitespace-normal text-black text-base font-normal">{event.content.body}</Linkify> </div> </div> )