Fixed context menu flicker, resize scrolling and race conditions
All checks were successful
Android Build / publish (push) Successful in 50s
Linux Build / publish (push) Successful in 51s

This commit is contained in:
olcxja 2026-06-13 07:19:12 +02:00
commit 3f0f628569
10 changed files with 140 additions and 118 deletions

View file

@ -1750,6 +1750,9 @@ document.addEventListener('touchmove', e => {
document.addEventListener('touchend', e => {
touchEndX = e.changedTouches[0].screenX;
touchEndY = e.changedTouches[0].screenY;
isAdjusting = false; //resize scroll fix
handleMobileSwipe();
if (activeTouchButton) {
@ -1985,7 +1988,7 @@ async function renderMessages(messages, isPrepend = false) {
if (reactions) reactions = await decryptAesGcmFromBase64(reactions, dmKeyBytes);
decrypted = true;
} catch (e) {
content = `<span style="color:var(--error-color)"><blah>error:messages.decrypt.failed</blah></span>`;
content = `<span style="color:var(--big-red)"><blah>error:messages.decrypt.failed</blah></span>`;
decrypted = false;
}
} else {
@ -2145,16 +2148,20 @@ async function renderMessages(messages, isPrepend = false) {
}
}
let isRedacted = msg.type === "larp.redacted";
let redactedStyle = isRedacted ? ` opacity: 0.5; font-style: italic;` : ``;
let redactedIcon = isRedacted ? `<svg xmlns="http://www.w3.org/2000/svg" height="1rem" viewBox="0 -960 960 960" fill="currentColor" style="vertical-align: -0.165em; margin-right: 0.2rem;"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>` : ``;
html += `
<div class="chat-message ${extraClass}" data-msg-id="${msgId}" id="msg-${msgId}">
${showAvatar ? `<img src="${pfp}" class="chat-message-pfp">` : `<div style="width: 2.5rem; flex-shrink: 0;"></div>`}
<div class="chat-message-content" oncontextmenu="handleMessageContextMenu(event, '${msgId}')" ontouchstart="handleMessageTouchStart(event, '${msgId}')" ontouchend="handleMessageTouchEnd()" ontouchmove="handleMessageTouchMove()">
<div class="chat-message-content" oncontextmenu="handleMessageContextMenu(event, '${msgId}')">
${showAvatar ? `<div class="chat-message-header">
<span class="chat-message-author">${authorName}</span>
<span class="chat-message-timestamp">${timeStr}</span>
</div>` : ""}
${repliedHtml}
<div class="chat-message-text">${content}</div>
<div class="chat-message-text" style="${redactedStyle}">${redactedIcon}${content}</div>
${reactionsHtml}
</div>
</div>
@ -2197,7 +2204,7 @@ window.visualViewport?.addEventListener("resize", async () => {
requestAnimationFrame(() => {
ignoreScroll = false;
});
await delay(10);
await delay(1);
}
});
function setupChatScrollListener() {
@ -2277,6 +2284,23 @@ async function sendMessage() {
showBlahNotification(res);
let pElem = document.getElementById(pendingMsgId);
if (pElem) pElem.remove();
} else if (res && res.startsWith("success:")) {
let parts = res.split(":");
if (parts.length > 1 && parts[1] !== "message.sent") {
let newMsgId = parts[1];
loadedMessages[newMsgId] = {
author: id,
timestamp: Date.now().toString(),
type: "larp.text",
content: encryptedContent,
attachment: "",
key: "0",
pervious: "",
responded: replyingToMsgId || "",
reactions: ""
};
renderMessages(loadedMessages);
}
}
} catch (e) {
clearAction("msgsending");
@ -2309,20 +2333,28 @@ function handleChatInputKey(event) {
let dmMessagePollInterval = null;
let appWebSocket = null;
let dmMessageLoadTimeout = null;
function setupWebSocket() {
if (appWebSocket && appWebSocket.readyState === WebSocket.OPEN) return;
if (appWebSocket && (appWebSocket.readyState === WebSocket.OPEN || appWebSocket.readyState === WebSocket.CONNECTING)) return;
let wsUrl = url.replace(/^http/, "ws") + "/ws";
appWebSocket = new WebSocket(wsUrl);
appWebSocket.onopen = async () => {
let release;
const lock = new Promise(resolve => release = resolve);
const prevMutex = requestMutex;
requestMutex = prevMutex.then(() => lock);
await prevMutex;
try {
let nonce = await getNonce(id, passwordHash);
let secretEnc = await encryptWithNonce(passwordHash, passwordHash, nonce);
appWebSocket.send(JSON.stringify({string1: id, string2: secretEnc}));
} catch (e) {
console.error("WS auth failed", e);
} finally {
release();
}
};
@ -2331,7 +2363,10 @@ function setupWebSocket() {
if (data.startsWith("dm_message:")) {
let msgDmId = data.substring("dm_message:".length);
if (currentDmId === msgDmId) {
loadDmMessages(msgDmId);
if (dmMessageLoadTimeout) clearTimeout(dmMessageLoadTimeout);
dmMessageLoadTimeout = setTimeout(() => {
loadDmMessages(msgDmId);
}, 300);
}
}
};
@ -2342,11 +2377,14 @@ function setupWebSocket() {
}
let currentContextMenuMsgId = null;
let touchTimeout = null;
let touchTarget = null;
let touchContextMenuFired = false;
function handleMessageContextMenu(e, msgId) {
e.preventDefault();
if (touchContextMenuFired) {
touchContextMenuFired = false;
return;
}
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
currentContextMenuMsgId = msgId;
let elem = document.getElementById(`msg-${msgId}`);
@ -2363,40 +2401,6 @@ function handleMessageContextMenu(e, msgId) {
}
}
function handleMessageTouchStart(e, msgId) {
touchTarget = e.target;
touchTimeout = setTimeout(() => {
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
currentContextMenuMsgId = msgId;
let elem = document.getElementById(`msg-${msgId}`);
if (elem) elem.classList.add("context-menu-open");
let touch = e.touches[0];
showFixedContextMenu({
top: touch.clientY,
right: touch.clientX,
bottom: touch.clientY,
left: touch.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 = "";
}
}
}, 500);
}
function handleMessageTouchEnd() {
if (touchTimeout) clearTimeout(touchTimeout);
}
function handleMessageTouchMove() {
if (touchTimeout) clearTimeout(touchTimeout);
}
async function deleteMessage(msgId) {
if (fixedContextMenu) fixedContextMenu.classList.remove("show");
showAction("action.message.deleting", "msgdel");
@ -2535,6 +2539,11 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
let encryptedReactions = await encryptAesGcmToBase64(JSON.stringify(existingReactions), dmKeyBytes);
if (currentMsg) {
currentMsg.reactions = encryptedReactions;
renderMessages(loadedMessages);
}
let msgPayload = {
string1: currentDmId,
string2: msgId,