feat: DM infrastructure overhaul + volume sliders + LiveKit dynamic URL

- Add DM edit/delete endpoints (REST + WebSocket)
- Add DM typing indicators with real-time broadcast
- Fix volume sliders to control LiveKit mic/speaker
- Fix Send Message button on user profile popout
- Add New DM modal with user search
- Fix message pagination (SQL cursor instead of in-memory)
- Guard optional attachments on DM messages
- Dynamic LiveKit URL from request Host header
- DM sidebar auto-sorts by most recent message
This commit is contained in:
Jannis Braun
2026-02-18 17:29:39 +01:00
parent 4f65ea9c86
commit 7168149d98
31 changed files with 1189 additions and 136 deletions
+8 -8
View File
@@ -125,9 +125,9 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
if (member?.roles && member.roles.length > 0) {
return { color: member.roles[0]!.color };
}
if (member?.role === 'owner') return { color: '#da373c' };
if (member?.role === 'owner') return { color: '#f23f43' };
if (member?.role === 'admin') return { color: '#5865f2' };
return { color: '#dbdee1' };
return { color: '#dcdcdf' };
})();
const replyRoleColor = (msg: any) => {
@@ -135,20 +135,20 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
if (member?.roles && member.roles.length > 0) {
return { color: member.roles[0]!.color };
}
if (member?.role === 'owner') return { color: '#da373c' };
if (member?.role === 'owner') return { color: '#f23f43' };
if (member?.role === 'admin') return { color: '#5865f2' };
return { color: '#dbdee1' };
return { color: '#dcdcdf' };
};
const content = (
<div
className={`group relative flex px-4 py-0.5 hover:bg-[#2e3035]/30 transition-colors ${isFirstInGroup || message.replyTo ? 'mt-[1.0625rem]' : ''}`}
className={`group relative flex px-4 py-0.5 hover:bg-discord-modifier-hover transition-colors ${isFirstInGroup || message.replyTo ? 'mt-[1.0625rem]' : ''}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{/* Reply Line */}
{message.replyTo && (
<div className="absolute left-[36px] top-[-14px] w-[33px] h-[22px] border-l-2 border-t-2 border-[#4e5058] rounded-tl-[6px] opacity-60" />
<div className="absolute left-[36px] top-[-14px] w-[33px] h-[22px] border-l-2 border-t-2 border-discord-interactive-muted rounded-tl-[6px] opacity-60" />
)}
{/* Avatar or timestamp column */}
@@ -181,7 +181,7 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
>
{message.replyTo.user.displayName ?? message.replyTo.user.username}
</span>
<span className="text-[14px] text-discord-text-normal truncate max-w-[400px] hover:text-white">
<span className="text-[14px] text-discord-text-normal truncate max-w-[400px] hover:text-discord-text-primary">
{message.replyTo.content}
</span>
</div>
@@ -280,7 +280,7 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
{!isEditing && firstUrl && <Embed url={firstUrl} />}
{/* Attachments */}
{message.attachments.length > 0 && (
{message.attachments && message.attachments.length > 0 && (
<div className="mt-1 grid gap-2">
{message.attachments.map((att) => {
const isImage = att.mimetype.startsWith('image/');