add message read/unread render & fix profile display
All checks were successful
Android Build / publish (push) Successful in 48s
Linux Build / publish (push) Successful in 52s

This commit is contained in:
olcxja 2026-07-04 17:24:46 +02:00
commit 68ac0beedf
14 changed files with 290 additions and 38 deletions

View file

@ -1533,6 +1533,13 @@ async function openProfile(accountId) {
try {
if (!accountId) accountId = window.id; //var fallback
if (accountId.endsWith(';' + host)) {
accountId = accountId.substring(0, accountId.length - host.length - 1);
} else if (accountId.endsWith(':' + host)) {
accountId = accountId.substring(0, accountId.length - host.length - 1);
}
let nameWithHost = await fetchAsync(`${url}/idtoname?id=${accountId}`);
if (nameWithHost.startsWith("error:")) nameWithHost = accountId; //api fallback
@ -1632,7 +1639,7 @@ async function openProfile(accountId) {
<div class="profile-info">
<div>
<div class="profile-name">${profileName}</div>
<div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0]})</div>
<div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0].split(';')[0]})</div>
</div>
${bioHtml}
${actionBtnHtml}
@ -2098,11 +2105,14 @@ async function renderDms(res) {
let pfp = await getAvatarUrl(targetId, targetUsername);
let displayName = targetUsername.split(':')[0];
let isRead = dmData.string3 === "true";
let nameStyle = isRead ? "opacity: 0.8;" : "opacity: 1;";
html += `
<button class="room-entry" id="dm-btn-${dmId}" onclick="openDm('${dmId}', '${targetUsername}', '${targetId}')">
<img src="${pfp}" class="room-pfp">
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
<span class="room-name">${displayName}</span>
<span class="room-name" style="${nameStyle}">${displayName}</span>
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${targetUsername} (${targetId.split(';')[0]})</span>
</div>
</button>
@ -2188,6 +2198,8 @@ async function openDm(dmId, username, targetId) {
try {
showAction("action.dm.opening", "dmopen");
markDmAsRead(dmId);
currentDmKey = await ensureDmRoomKey(dmId);
if (!currentDmKey) {
throw new Error("Missing DM room key");
@ -2484,13 +2496,21 @@ async function renderMessages(messages, isPrepend = false) {
container.innerHTML = html;
let targetScrollTop = 0;
if (wasAtBottom) {
container.scrollTop = container.scrollHeight;
targetScrollTop = container.scrollHeight;
} else if (isPrepend) {
container.scrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);
targetScrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);
} else {
container.scrollTop = oldScrollTop;
targetScrollTop = oldScrollTop;
}
ignoreScroll = true;
container.scrollTop = targetScrollTop;
lastChatScroll = container.scrollHeight - targetScrollTop - container.clientHeight;
if (lastChatScroll < 0) lastChatScroll = 0;
setTimeout(() => { ignoreScroll = false; }, 50);
}
var lastChatScroll = 0;
@ -2528,6 +2548,9 @@ function setupChatScrollListener() {
if (!ignoreScroll) {
isAdjusting = false;
lastChatScroll = container.scrollHeight - container.scrollTop - container.clientHeight;
if (lastChatScroll <= 10) {
handleDmInteraction();
}
}
if (container.scrollTop === 0 && !isLoadingOlderMessages && oldestLoadedMsgId !== null && oldestLoadedMsgId > 0) {
@ -2678,7 +2701,18 @@ function setupWebSocket() {
appWebSocket.onmessage = (event) => {
let data = event.data;
if (data.startsWith("dm_message:")) {
let msgDmId = data.substring("dm_message:".length);
let parts = data.split(":");
let msgDmId = parts[1];
let msgType = parts[2] || "unread";
if (msgType === "unread") {
updateDmListUI(msgDmId, false, Date.now().toString());
} else if (msgType === "read") {
updateDmListUI(msgDmId, true, Date.now().toString());
} else if (msgType === "update") {
// do nothing to UI sorting/read state
}
if (currentDmId === msgDmId) {
if (dmMessageLoadTimeout) clearTimeout(dmMessageLoadTimeout);
dmMessageLoadTimeout = setTimeout(() => {
@ -2709,12 +2743,21 @@ function handleMessageContextMenu(e, msgId) {
showFixedContextMenu({top: e.clientY, right: e.clientX, bottom: e.clientY, left: e.clientX}, messageContextMenu);
let delBtn = fixedContextMenu.querySelector("#context-delete-btn");
if (delBtn) {
if (loadedMessages[msgId] && loadedMessages[msgId].author !== id) {
delBtn.style.display = "none";
} else {
delBtn.style.display = "";
let reactBtn = fixedContextMenu.querySelector("#context-react-btn");
let msg = loadedMessages[msgId];
if (msg && msg.type === "larp.redacted") {
if (delBtn) delBtn.style.display = "none";
if (reactBtn) reactBtn.style.display = "none";
} else {
if (delBtn) {
if (msg && msg.author !== id) {
delBtn.style.display = "none";
} else {
delBtn.style.display = "";
}
}
if (reactBtn) reactBtn.style.display = "";
}
}
@ -2895,6 +2938,11 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
showAction("action.message.reacting", "msgreact");
try {
let currentMsg = loadedMessages[msgId];
if (currentMsg && currentMsg.type === "larp.redacted") {
clearAction("msgreact");
showNotification(processBlah("error.message.already.deleted"), "error");
return;
}
let existingReactions = [];
if (currentMsg && currentMsg.reactions) {
let rxStr = currentMsg.reactions;
@ -2922,7 +2970,9 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
let encryptedReactions = await encryptAesGcmToBase64(JSON.stringify(existingReactions), dmKeyBytes);
let originalReactionsEncrypted = "";
if (currentMsg) {
originalReactionsEncrypted = currentMsg.reactions || "";
currentMsg.reactions = encryptedReactions;
renderMessages(loadedMessages);
}
@ -2935,6 +2985,10 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
let res = await fetchEncrypted("dm/message/react", JSON.stringify(msgPayload));
if (res && res.startsWith("error:")) {
showBlahNotification(res);
if (currentMsg) {
currentMsg.reactions = originalReactionsEncrypted;
renderMessages(loadedMessages);
}
}
} catch (e) {
showBlahNotification("error:message.react.failed");
@ -3430,4 +3484,69 @@ if (container) {
document.addEventListener('touchmove', onDrag, {passive: true});
document.addEventListener('mouseup', endDrag);
document.addEventListener('touchend', endDrag);
}
}
function updateDmListUI(dmId, isRead, timestamp = null) {
try {
if (!window.cachedDms) return;
let parsedDms = JSON.parse(window.cachedDms);
if (!parsedDms.dms || !parsedDms.dms[dmId]) return;
let changed = false;
if (parsedDms.dms[dmId].string3 !== (isRead ? "true" : "false")) {
parsedDms.dms[dmId].string3 = isRead ? "true" : "false";
changed = true;
}
if (timestamp && parsedDms.dms[dmId].string2 !== timestamp) {
parsedDms.dms[dmId].string2 = timestamp;
changed = true;
}
if (changed) {
window.cachedDms = JSON.stringify(parsedDms);
}
let btn = document.getElementById(`dm-btn-${dmId}`);
let container = document.getElementById("dms-list");
if (btn && container) {
let nameSpan = btn.querySelector('.room-name');
if (nameSpan) {
nameSpan.style.opacity = isRead ? "0.8" : "1";
}
if (timestamp) {
container.prepend(btn);
}
} else {
if (typeof refreshDms === 'function') refreshDms(0, false);
}
} catch (e) {
console.error(e);
if (typeof refreshDms === 'function') refreshDms(0, false);
}
}
function markDmAsRead(dmId) {
fetchEncrypted("dm/read", JSON.stringify({string1: dmId})).catch(()=>{});
updateDmListUI(dmId, true);
}
function handleDmInteraction() {
if (!currentDmId) return;
let container = document.getElementById("chat-messages");
if (!container) return;
if (lastChatScroll <= 10) {
try {
if (window.cachedDms) {
let parsedDms = JSON.parse(window.cachedDms);
if (parsedDms.dms && parsedDms.dms[currentDmId] && parsedDms.dms[currentDmId].string3 !== "true") {
markDmAsRead(currentDmId);
}
}
} catch (e) {}
}
}
document.addEventListener('click', handleDmInteraction);
document.addEventListener('keydown', handleDmInteraction);
document.addEventListener('touchstart', handleDmInteraction);