Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5897,6 +5897,7 @@ async function processChats(chats, keys) {
// Count of edits (from the other party) applied while user not viewing this chat
let editIncrements = 0;
const pendingReactionControls = [];
let didApplyPendingReaction = false;

// This check determines if we're currently chatting with the sender
// We ONLY want to avoid notifications if we're actively viewing this exact chat
Expand Down Expand Up @@ -6457,7 +6458,9 @@ async function processChats(chats, keys) {
});

for (const pendingReaction of pendingReactionControls) {
applyIncomingReaction(contact.messages, pendingReaction);
if (applyIncomingReaction(contact.messages, pendingReaction)) {
didApplyPendingReaction = true;
}
}
}

Expand Down Expand Up @@ -6506,6 +6509,10 @@ async function processChats(chats, keys) {
}
}

if (didApplyPendingReaction && added === 0 && inActiveChatWithSender) {
chatModal.appendChatModal();
}

// Show transfer notification even if no messages were added
if (hasNewTransfer) {
// Add bubble to Wallet tab if we're not on it
Expand Down Expand Up @@ -14763,6 +14770,8 @@ class ChatModal {
return;
}
const messages = contact.messages; // Already sorted descending
const currentUserAddress = normalizeAddress(myAccount.keys.address);
const contactAddress = normalizeAddress(contact.address);
// Last time user previously had this chat open (used to mark newly edited messages)
const lastReadTs = contact.lastChatOpenTs || 0;

Expand All @@ -14789,6 +14798,35 @@ class ChatModal {
// Add txid attribute if available
const txidAttribute = item?.txid ? `data-txid="${item.txid}"` : '';
const statusAttribute = item?.status ? `data-status="${item.status}"` : '';
let reactionsHTML = '';
if (item.reactions) {
const chips = [];
const contactEmoji = item.reactions[contactAddress];
const myEmoji = item.reactions[currentUserAddress];

if (contactEmoji) {
chips.push(`<span class="message-reaction-chip">${escapeHtml(contactEmoji)}</span>`);
}
if (myEmoji) {
chips.push(`<span class="message-reaction-chip my-reaction">${escapeHtml(myEmoji)}</span>`);
}

for (const [sender, emoji] of Object.entries(item.reactions)) {
const normalizedSender = normalizeAddress(sender);
if (normalizedSender === contactAddress || normalizedSender === currentUserAddress) {
continue;
}
chips.push(`<span class="message-reaction-chip">${escapeHtml(emoji)}</span>`);
}

if (chips.length > 0) {
reactionsHTML = `
<div class="message-reactions" aria-label="Reactions">
${chips.join('')}
</div>
`;
}
}

// Check if it's a payment based on the presence of the amount property (BigInt)
if (typeof item.amount === 'bigint') {
Expand Down Expand Up @@ -14817,6 +14855,7 @@ class ChatModal {
</div>
${itemMemo ? `<div class="payment-memo">${linkifyUrls(itemMemo)}</div>` : ''}
<div class="message-time">${timeString}${item.edited ? ' <span class="message-edited-label">edited</span>' : ''}${showEditedDot ? ' <span class="edited-new-dot" title="Edited since last read"></span>' : ''}</div>
${reactionsHTML}
</div>
`;
} else {
Expand Down Expand Up @@ -14975,6 +15014,7 @@ class ChatModal {
${attachmentsHTML}
${messageTextHTML}
<div class="message-time">${timeString}${item.edited ? ' <span class="message-edited-label">edited</span>' : ''}${showEditedDot ? ' <span class="edited-new-dot" title="Edited since last read"></span>' : ''}</div>
${reactionsHTML}
</div>
`;
}
Expand Down Expand Up @@ -17400,6 +17440,27 @@ class ChatModal {
reactAction: reaction.reactAction
});

const localReaction = reaction.reactAction === 'remove'
? {
sender: normalizeAddress(keys.address),
reactId: reaction.reactId,
action: 'remove'
}
: {
sender: normalizeAddress(keys.address),
reactId: reaction.reactId,
action: 'set',
emoji: reaction.reactMessage
};
const didApplyLocally = applyIncomingReaction(contact.messages, localReaction);
if (didApplyLocally) {
if (this.isActive() && this.address === currentAddress) {
this.appendChatModal();
}
} else {
console.warn('Reaction sent but local optimistic apply was skipped', localReaction);
}

const response = await injectTx(chatMessageObj, txid);
if (!response?.result?.success) {
console.error('reaction message failed to send', response);
Expand Down
44 changes: 44 additions & 0 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,7 @@ input[type='file'].form-control::file-selector-button {
padding: 0.75rem 1rem;
border-radius: 1rem;
position: relative;
margin-bottom: 16px;
text-align: left;
cursor: pointer;
transition: filter 0.2s ease;
Expand Down Expand Up @@ -2039,6 +2040,49 @@ input[type='file'].form-control::file-selector-button {
text-align: left;
}

.message-reactions {
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
position: absolute;
left: 10px;
bottom: -14px;
z-index: 2;
}

.message-reaction-chip {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 26px;
min-height: 24px;
padding: 2px 8px;
border-radius: 999px;
background: var(--surface-muted);
color: #1f2430;
font-size: 15px;
line-height: 1;
box-shadow:
0 2px 6px rgba(31, 36, 48, 0.12),
inset 0 0 0 1px rgba(31, 36, 48, 0.08);
position: relative;
z-index: 1;
}

.message-reaction-chip + .message-reaction-chip {
margin-left: -8px;
}

.message-reaction-chip.my-reaction {
background: var(--primary-color);
color: var(--background-color);
z-index: 1;
box-shadow:
0 2px 6px rgba(31, 36, 48, 0.16),
inset 0 0 0 1px rgba(255, 255, 255, 0.14);
}

/* Dot indicating a message was edited since last read */
.edited-new-dot {
display: inline-block;
Expand Down
Loading