@@ -2243,7 +2243,7 @@ _setThreadParentHeader(meta = {}) {
22432243} ,
22442244
22452245_setThreadReply ( msgEl , msgId ) {
2246- const author = msgEl . querySelector ( '.thread-msg-author' ) ?. textContent || 'someone' ;
2246+ const author = msgEl . querySelector ( '.thread-msg-author' ) ?. textContent || msgEl . dataset . username || 'someone' ;
22472247 const rawContent = msgEl . dataset . rawContent || msgEl . querySelector ( '.thread-msg-content' ) ?. textContent || '' ;
22482248 const preview = rawContent . length > 70 ? rawContent . substring ( 0 , 70 ) + '…' : rawContent ;
22492249 this . _threadReplyingTo = { id : msgId , username : author , content : rawContent } ;
@@ -2266,7 +2266,7 @@ _clearThreadReply() {
22662266
22672267_quoteThreadMessage ( msgEl ) {
22682268 const rawContent = msgEl . dataset . rawContent || msgEl . querySelector ( '.thread-msg-content' ) ?. textContent || '' ;
2269- const author = msgEl . querySelector ( '.thread-msg-author' ) ?. textContent || 'someone' ;
2269+ const author = msgEl . querySelector ( '.thread-msg-author' ) ?. textContent || msgEl . dataset . username || 'someone' ;
22702270 const quotedLines = rawContent . split ( '\n' ) . map ( l => `> ${ l } ` ) . join ( '\n' ) ;
22712271 const quoteText = `> @${ author } wrote:\n${ quotedLines } \n` ;
22722272
@@ -2588,8 +2588,16 @@ _appendDMPiPMessage(msg) {
25882588 let prevMsg = null ;
25892589 const lastEl = list . lastElementChild ;
25902590 if ( lastEl && lastEl . dataset && lastEl . dataset . userId && lastEl . dataset . msgId ) {
2591+ // Rebuild enough of the previous message for `_createMessageEl`'s grouping
2592+ // check. It compares username and persona too, so a prev that only carried
2593+ // user_id + time never matched and every message rendered ungrouped. (the
2594+ // dataset already stores these from when the element was created.)
25912595 prevMsg = {
25922596 user_id : parseInt ( lastEl . dataset . userId , 10 ) ,
2597+ username : lastEl . dataset . username || null ,
2598+ persona_id : lastEl . dataset . personaId ? parseInt ( lastEl . dataset . personaId , 10 ) : null ,
2599+ persona_username : lastEl . dataset . personaUsername || null ,
2600+ break_chain : lastEl . dataset . breakChain === '1' ? 1 : 0 ,
25932601 created_at : lastEl . dataset . time
25942602 } ;
25952603 }
@@ -2918,29 +2926,67 @@ _appendThreadMessage(msg) {
29182926 ? `<div class="thread-msg-more"><button class="thread-msg-more-btn" type="button" aria-label="More actions">${ iMore } </button><div class="thread-msg-overflow">${ threadOverflowToolbarBtns } </div></div>`
29192927 : '' ;
29202928
2929+ // Group consecutive replies from the same author (within 5 min, no reply
2930+ // banner) into compact rows, the same way the main channel does — drop the
2931+ // avatar and author header, keep the content and the hover toolbar. The
2932+ // thread's parent message lives in a separate preview element, not in this
2933+ // container, so we only ever group reply-against-reply.
2934+ let threadCompact = false ;
2935+ const prevEl = container . lastElementChild ;
2936+ if ( prevEl && prevEl . classList ?. contains ( 'thread-message' ) && ! msg . reply_to ) {
2937+ const samePerson = parseInt ( prevEl . dataset . userId , 10 ) === msg . user_id
2938+ && ( prevEl . dataset . personaId || '' ) === ( msg . persona_id ? String ( msg . persona_id ) : '' ) ;
2939+ const prevTime = prevEl . dataset . time ? new Date ( prevEl . dataset . time ) . getTime ( ) : 0 ;
2940+ const within = prevTime && ( new Date ( msg . created_at ) . getTime ( ) - prevTime ) < 5 * 60 * 1000 ;
2941+ threadCompact = samePerson && within ;
2942+ }
2943+
29212944 const el = document . createElement ( 'div' ) ;
2922- el . className = 'thread-message' ;
2945+ el . className = 'thread-message' + ( threadCompact ? ' thread-compact' : '' ) ;
29232946 el . dataset . msgId = msg . id ;
29242947 el . dataset . rawContent = msg . content ;
2925- el . innerHTML = `
2926- <div class="thread-msg-row">
2927- ${ avatarHtml }
2928- <div class="thread-msg-body">
2929- <div class="thread-msg-header">
2930- <span class="thread-msg-author" style="color:${ color } ">${ this . _escapeHtml ( displayName ) } </span>
2931- <span class="thread-msg-time">${ this . _formatTime ( msg . created_at ) } </span>
2932- <span class="thread-msg-header-spacer"></span>
2948+ el . dataset . userId = msg . user_id ;
2949+ el . dataset . time = msg . created_at ;
2950+ // Stash the display name so reply/quote still resolve the author on compact
2951+ // rows, which don't render a `.thread-msg-author` element.
2952+ el . dataset . username = displayName ;
2953+ if ( msg . persona_id ) el . dataset . personaId = String ( msg . persona_id ) ;
2954+ if ( threadCompact ) {
2955+ const shortTime = new Date ( msg . created_at ) . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
2956+ el . innerHTML = `
2957+ <div class="thread-msg-row">
2958+ <div class="thread-msg-avatar thread-msg-compact-spacer"><span class="thread-compact-time">${ this . _escapeHtml ( shortTime ) } </span></div>
2959+ <div class="thread-msg-body">
29332960 <div class="thread-msg-toolbar">
29342961 <div class="msg-toolbar-group">${ threadCoreToolbarBtns } </div>
29352962 ${ threadOverflowHtml }
29362963 </div>
2964+ <div class="thread-msg-content">${ this . _formatContent ( msg . content ) } </div>
2965+ ${ reactionsHtml }
29372966 </div>
2938- ${ replyHtml }
2939- <div class="thread-msg-content">${ this . _formatContent ( msg . content ) } </div>
2940- ${ reactionsHtml }
29412967 </div>
2942- </div>
2943- ` ;
2968+ ` ;
2969+ } else {
2970+ el . innerHTML = `
2971+ <div class="thread-msg-row">
2972+ ${ avatarHtml }
2973+ <div class="thread-msg-body">
2974+ <div class="thread-msg-header">
2975+ <span class="thread-msg-author" style="color:${ color } ">${ this . _escapeHtml ( displayName ) } </span>
2976+ <span class="thread-msg-time">${ this . _formatTime ( msg . created_at ) } </span>
2977+ <span class="thread-msg-header-spacer"></span>
2978+ <div class="thread-msg-toolbar">
2979+ <div class="msg-toolbar-group">${ threadCoreToolbarBtns } </div>
2980+ ${ threadOverflowHtml }
2981+ </div>
2982+ </div>
2983+ ${ replyHtml }
2984+ <div class="thread-msg-content">${ this . _formatContent ( msg . content ) } </div>
2985+ ${ reactionsHtml }
2986+ </div>
2987+ </div>
2988+ ` ;
2989+ }
29442990 container . appendChild ( el ) ;
29452991 try { this . _decryptE2EImages ?. ( el ) ; } catch { }
29462992 try { this . _decryptE2EFiles ?. ( el ) ; } catch { }
0 commit comments