diff --git a/.changeset/fix-color-parser.md b/.changeset/fix-color-parser.md new file mode 100644 index 000000000..0abdd8938 --- /dev/null +++ b/.changeset/fix-color-parser.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix opacity rendering in name colors. diff --git a/.changeset/fix-delated-file-uploads.md b/.changeset/fix-delated-file-uploads.md new file mode 100644 index 000000000..c3d5f936d --- /dev/null +++ b/.changeset/fix-delated-file-uploads.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix sending scheduled file attachments. diff --git a/.changeset/fix-reply-rendering.md b/.changeset/fix-reply-rendering.md new file mode 100644 index 000000000..413388960 --- /dev/null +++ b/.changeset/fix-reply-rendering.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix replies rendering new lines when messages have lists. diff --git a/.changeset/fix-thread-replies.md b/.changeset/fix-thread-replies.md new file mode 100644 index 000000000..d10cf9d9f --- /dev/null +++ b/.changeset/fix-thread-replies.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix threads rendering fallback replies. diff --git a/src/app/components/message/Reply.tsx b/src/app/components/message/Reply.tsx index 2afd2aae3..1e047e9e9 100644 --- a/src/app/components/message/Reply.tsx +++ b/src/app/components/message/Reply.tsx @@ -122,6 +122,8 @@ export const Reply = as<'div', ReplyProps>( .replaceAll(//gi, ' ') .replaceAll(/<\/p>\s*]*>/gi, ' ') .replaceAll(/<\/?p[^>]*>/gi, '') + .replaceAll(/<\/li>\s*]*>/gi, ' ') + .replaceAll(/<\/?(ul|ol|li|blockquote|h[1-6]|pre|div)[^>]*>/gi, '') .replaceAll(/(?:\r\n|\r|\n)/g, ' '); const parserOpts = getReactCustomHtmlParser(mx, room.roomId, { linkifyOpts: LINKIFY_OPTS, diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index ad9d63141..60ee8d20b 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -512,28 +512,74 @@ export const RoomInput = forwardRef( } } - await Promise.all( - contents.map((content) => - mx - .sendMessage(roomId, threadRootId ?? null, content as any) - .then((res) => { - debugLog.info('message', 'Uploaded file message sent', { - roomId, - eventId: res.event_id, - msgtype: content.msgtype, - }); - return res; - }) - .catch((error: unknown) => { - debugLog.error('message', 'Failed to send uploaded file message', { - roomId, - error: error instanceof Error ? error.message : String(error), - }); - log.error('failed to send uploaded message', { roomId }, error); - throw error; + const invalidate = () => + queryClient.invalidateQueries({ queryKey: ['delayedEvents', roomId] }); + + if (scheduledTime) { + try { + const delayMs = computeDelayMs(scheduledTime); + if (editingScheduledDelayId) { + await cancelDelayedEvent(mx, editingScheduledDelayId); + } + + await Promise.all( + contents.map((content) => { + if (isEncrypted) { + return sendDelayedMessageE2EE(mx, roomId, room, content, delayMs); + } + return sendDelayedMessage(mx, roomId, content, delayMs); }) - ) - ); + ); + + invalidate(); + setEditingScheduledDelayId(null); + setScheduledTime(null); + } catch (error) { + debugLog.error('message', 'Failed to schedule uploaded file message', { + roomId, + error: error instanceof Error ? error.message : String(error), + }); + log.error('failed to schedule uploaded message', { roomId }, error); + throw error; + } + } else { + if (editingScheduledDelayId) { + try { + await cancelDelayedEvent(mx, editingScheduledDelayId); + invalidate(); + setEditingScheduledDelayId(null); + } catch { + debugLog.error( + 'message', + 'Failed to cancel scheduled event before immediate file send', + { roomId } + ); + } + } + + await Promise.all( + contents.map((content) => + mx + .sendMessage(roomId, threadRootId ?? null, content as any) + .then((res) => { + debugLog.info('message', 'Uploaded file message sent', { + roomId, + eventId: res.event_id, + msgtype: content.msgtype, + }); + return res; + }) + .catch((error: unknown) => { + debugLog.error('message', 'Failed to send uploaded file message', { + roomId, + error: error instanceof Error ? error.message : String(error), + }); + log.error('failed to send uploaded message', { roomId }, error); + throw error; + }) + ) + ); + } }; const handleCloseAutocomplete = useCallback(() => { diff --git a/src/app/features/room/ThreadDrawer.tsx b/src/app/features/room/ThreadDrawer.tsx index fd7075bfd..00957bc34 100644 --- a/src/app/features/room/ThreadDrawer.tsx +++ b/src/app/features/room/ThreadDrawer.tsx @@ -180,6 +180,11 @@ function ThreadMessage({ const { replyEventId } = mEvent; + const relation = mEvent.getRelation(); + const contentRelatesTo = mEvent.getContent()?.['m.relates_to']; + const isFallback = + relation?.is_falling_back === true || contentRelatesTo?.is_falling_back === true; + return ( { if (typeof c !== 'string') return undefined; // silly tuwunel smh const cleaned = c.replaceAll(/["']/g, '').trim(); - return /^#([0-9A-F]{3,6})$/i.test(cleaned) ? cleaned : undefined; + // Strictly allow only 3 or 6 digit hex codes, aka no opacity + return /^#([0-9A-F]{3}|[0-9A-F]{6})$/i.test(cleaned) ? cleaned : undefined; }; const sanitizeFont = (f: string) => f.replaceAll(/[;{}<>]/g, '').slice(0, 32);