message grouping
This commit is contained in:
+40
@@ -627,6 +627,46 @@ body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-item.grouped {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.message-item.grouped .message-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-avatar-placeholder {
|
||||
width: 40px;
|
||||
height: 20px; /* Match approximate line-height */
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.thread-message-item .message-avatar-placeholder {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.message-hover-timestamp {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 0.65rem;
|
||||
color: var(--text-muted);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
line-height: 1.4;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.message-item.grouped:hover .message-hover-timestamp {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.message-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
msg,
|
||||
isThread = false,
|
||||
isHighlighted = false,
|
||||
isGrouped = false,
|
||||
onContentLoad
|
||||
}: {
|
||||
msg: Types.Message;
|
||||
isThread?: boolean;
|
||||
isHighlighted?: boolean;
|
||||
isGrouped?: boolean;
|
||||
onContentLoad?: () => void;
|
||||
} = $props();
|
||||
|
||||
@@ -145,7 +147,7 @@
|
||||
|
||||
<svelte:window onclick={handleGlobalClick} />
|
||||
|
||||
<div class="message-item {isHighlighted ? 'active' : ''} {isThread ? 'thread-message-item' : ''}">
|
||||
<div class="message-item {isHighlighted ? 'active' : ''} {isThread ? 'thread-message-item' : ''} {isGrouped ? 'grouped' : ''}">
|
||||
<div class="message-actions-toolbar">
|
||||
{#if chat.isFullyAuthenticated}
|
||||
<div class="add-reaction-wrapper" style="position: relative;">
|
||||
@@ -186,17 +188,25 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div oncontextmenu={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const user = chat.users.find(u => u.identity.isEqual(msg.sender));
|
||||
if (user) {
|
||||
chat.userContextMenu = { x: e.clientX, y: e.clientY, user };
|
||||
}
|
||||
}}>
|
||||
<Avatar user={chat.users.find(u => u.identity.isEqual(msg.sender))} size={isThread ? "small" : "medium"} class="message-avatar" />
|
||||
</div>
|
||||
{#if isGrouped}
|
||||
<div class="message-avatar-placeholder">
|
||||
<div class="message-hover-timestamp">
|
||||
{chat.formatTime(msg.sent)}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div oncontextmenu={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const user = chat.users.find(u => u.identity.isEqual(msg.sender));
|
||||
if (user) {
|
||||
chat.userContextMenu = { x: e.clientX, y: e.clientY, user };
|
||||
}
|
||||
}}>
|
||||
<Avatar user={chat.users.find(u => u.identity.isEqual(msg.sender))} size={isThread ? "small" : "medium"} class="message-avatar" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="message-content">
|
||||
<div class="message-header">
|
||||
@@ -281,11 +291,11 @@
|
||||
{/if}
|
||||
{/if}
|
||||
<span class="count">{group.length}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if !isThread && existingThread}
|
||||
{#if !isThread && existingThread}
|
||||
{@const threadMessageCount = chat.allMessages.filter(m => m.threadId === existingThread.id).length}
|
||||
<button
|
||||
class="thread-link"
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
const messages = chat.channelMessages;
|
||||
const messagesLocal = chat.channelMessages;
|
||||
const currentChannelId = chat.activeChannelId;
|
||||
|
||||
if (isPrepending && scrollContainer) {
|
||||
@@ -113,9 +113,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.length > 0 && scrollContainer) {
|
||||
if (messagesLocal.length > 0 && scrollContainer) {
|
||||
const isChannelSwitch = currentChannelId !== lastChannelId;
|
||||
const lastMsg = messages[messages.length - 1];
|
||||
const lastMsg = messagesLocal[messagesLocal.length - 1];
|
||||
const myIdHex = chat.identity?.toHexString();
|
||||
const lastMsgSenderHex = lastMsg?.sender.toHexString();
|
||||
const sentByMe = myIdHex && lastMsgSenderHex && myIdHex === lastMsgSenderHex;
|
||||
@@ -133,6 +133,27 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const messages = $derived(chat.channelMessages);
|
||||
|
||||
const messageGroups = $derived.by(() => {
|
||||
return messages.map((msg, index) => {
|
||||
if (index === 0) return false;
|
||||
const prevMsg = messages[index - 1];
|
||||
|
||||
try {
|
||||
const sameSender = msg.sender.toHexString() === prevMsg.sender.toHexString();
|
||||
const sameThread = msg.threadId === prevMsg.threadId;
|
||||
const diff = msg.sent.microsSinceUnixEpoch - prevMsg.sent.microsSinceUnixEpoch;
|
||||
// Ensure non-negative and within 5 mins (300,000,000 micros)
|
||||
const withinFiveMinutes = diff >= 0n && diff < 300000000n;
|
||||
|
||||
return sameSender && sameThread && withinFiveMinutes;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -144,11 +165,12 @@
|
||||
bind:this={scrollContainer}
|
||||
onscroll={handleScroll}
|
||||
>
|
||||
{#each chat.channelMessages as msg (msg.id.toString())}
|
||||
{#each messages as msg, i (msg.id.toString())}
|
||||
{@const isHighlighted = (chat.pendingThreadParentMessageId === msg.id) || (chat.activeThread?.parentMessageId === msg.id)}
|
||||
<MessageItem
|
||||
{msg}
|
||||
{isHighlighted}
|
||||
isGrouped={messageGroups[i]}
|
||||
onContentLoad={handleContentLoad}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
@@ -9,13 +9,35 @@
|
||||
threadMessages: readonly Types.Message[],
|
||||
onContentLoad?: () => void
|
||||
} = $props();
|
||||
|
||||
const messages = $derived(threadMessages);
|
||||
|
||||
const messageGroups = $derived.by(() => {
|
||||
return messages.map((msg, index) => {
|
||||
if (index === 0) return false;
|
||||
const prevMsg = messages[index - 1];
|
||||
|
||||
try {
|
||||
const sameSender = msg.sender.toHexString() === prevMsg.sender.toHexString();
|
||||
const sameThread = msg.threadId === prevMsg.threadId;
|
||||
const diff = msg.sent.microsSinceUnixEpoch - prevMsg.sent.microsSinceUnixEpoch;
|
||||
// Ensure non-negative and within 5 mins (300,000,000 micros)
|
||||
const withinFiveMinutes = diff >= 0n && diff < 300000000n;
|
||||
|
||||
return sameSender && sameThread && withinFiveMinutes;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="thread-messages-inner" style="display: flex; flex-direction: column;">
|
||||
{#each threadMessages as msg (msg.id.toString())}
|
||||
{#each messages as msg, i (msg.id.toString())}
|
||||
<MessageItem
|
||||
{msg}
|
||||
isThread={true}
|
||||
isGrouped={messageGroups[i]}
|
||||
{onContentLoad}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
@@ -171,7 +171,7 @@ export class MessagingService {
|
||||
if (!isExpanded) {
|
||||
// In non-expanded mode, strictly ONLY show the cache for THIS channel
|
||||
return mappedRecent.sort((a, b) =>
|
||||
Number(BigInt(a.sent.microsSinceUnixEpoch) - BigInt(b.sent.microsSinceUnixEpoch))
|
||||
a.sent.microsSinceUnixEpoch < b.sent.microsSinceUnixEpoch ? -1 : (a.sent.microsSinceUnixEpoch > b.sent.microsSinceUnixEpoch ? 1 : 0)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ export class MessagingService {
|
||||
}
|
||||
|
||||
return Array.from(msgMap.values()).sort((a, b) =>
|
||||
Number(BigInt(a.sent.microsSinceUnixEpoch) - BigInt(b.sent.microsSinceUnixEpoch))
|
||||
a.sent.microsSinceUnixEpoch < b.sent.microsSinceUnixEpoch ? -1 : (a.sent.microsSinceUnixEpoch > b.sent.microsSinceUnixEpoch ? 1 : 0)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ export class MessagingService {
|
||||
}
|
||||
|
||||
return Array.from(msgMap.values()).sort((a, b) =>
|
||||
Number(BigInt(a.sent.microsSinceUnixEpoch) - BigInt(b.sent.microsSinceUnixEpoch))
|
||||
a.sent.microsSinceUnixEpoch < b.sent.microsSinceUnixEpoch ? -1 : (a.sent.microsSinceUnixEpoch > b.sent.microsSinceUnixEpoch ? 1 : 0)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user