From 3f0f62856960d858f6e1151a4dde2d13bd48d8fb Mon Sep 17 00:00:00 2001 From: olcxja Date: Sat, 13 Jun 2026 07:19:12 +0200 Subject: [PATCH] Fixed context menu flicker, resize scrolling and race conditions --- .../src/main/assets/public/blah/en-cat.json | 14 ++- .../src/main/assets/public/blah/en-us.json | 13 ++- android/app/src/main/assets/public/main.js | 93 ++++++++++--------- android/app/src/main/assets/public/screens.js | 4 +- android/app/src/main/assets/public/style.css | 5 + webroot/blah/en-cat.json | 14 ++- webroot/blah/en-us.json | 13 ++- webroot/main.js | 93 ++++++++++--------- webroot/screens.js | 4 +- webroot/style.css | 5 + 10 files changed, 140 insertions(+), 118 deletions(-) diff --git a/android/app/src/main/assets/public/blah/en-cat.json b/android/app/src/main/assets/public/blah/en-cat.json index 11bc708c..0c6b11a4 100644 --- a/android/app/src/main/assets/public/blah/en-cat.json +++ b/android/app/src/main/assets/public/blah/en-cat.json @@ -45,15 +45,12 @@ "password.cant.empty": "meow word cannot be empty", "username.cant.empty": "cat name cannot be empty", "bad.request": "bad request!!!", - "letters": "letters", "numbers": "numbers", "underscores": "underscores", - "loading.connecting": "connecting...", "loading.loading": "loading...", "loading.done": "ready! :3", - "title.home": "bed", "title.dms": "direct meowchats", "title.groups": "clowder", @@ -80,7 +77,6 @@ "title.sent": "sent", "title.all": "all", "title.unread": "unread", - "desc.no.invites": "no invites found :c", "desc.no.notifications": "no notifications found :c", "desc.fetching.invites": "fetching invites...", @@ -89,7 +85,6 @@ "desc.invite.dm.sent": "you invited {0} ({1}) to meowchat", "desc.invite.group.received": "{0} ({1}) invited you to a clowder", "desc.invite.group.sent": "you invited {0} ({1}) to a clowder", - "action.fetching.invites.sent": "fetching sent invites...", "action.fetching.invites.recv": "fetching received invites...", "action.dm.fetch": "fetching direct meowchats...", @@ -98,7 +93,6 @@ "action.invite.revoking": "revoking invite...", "action.invite.accepting": "accepting invite...", "action.invite.declining": "declining invite...", - "title.sign.up": "sign up", "title.sign.in": "sign in", "title.sign.in.to": "sign in to your larpix instance", @@ -132,6 +126,7 @@ "title.react.message": "purr", "title.delete.message": "hiss away", "title.replied.to": "meowed back to", + "title.replying.to": "meowing back to", "action.message.deleting": "hissing message away...", "action.message.sending": "sending meow...", "action.message.reacting": "adding purr...", @@ -140,5 +135,8 @@ "error:message.react.failed": "failed to add purr", "info.sending.message": "sending...", "placeholder.message.input": "meow...", - "larp.redacted": "this meow was hissed away." -} + "larp.redacted": "this meow was hissed away.", + "placeholder.invitation.code": "invitation code meow", + "info.messages.loading.older": "Loading older meowsages...", + "error:message.not.found": "Meowsage not found" +} \ No newline at end of file diff --git a/android/app/src/main/assets/public/blah/en-us.json b/android/app/src/main/assets/public/blah/en-us.json index 6b1710ad..dca7c841 100644 --- a/android/app/src/main/assets/public/blah/en-us.json +++ b/android/app/src/main/assets/public/blah/en-us.json @@ -45,15 +45,12 @@ "password.cant.empty": "Password cannot be empty", "username.cant.empty": "Username cannot be empty", "bad.request": "Bad request", - "letters": "letters", "numbers": "numbers", "underscores": "underscores", - "loading.connecting": "Connecting...", "loading.loading": "Loading...", "loading.done": "Ready!", - "title.home": "Home", "title.dms": "Direct messages", "title.groups": "Groups", @@ -88,7 +85,6 @@ "desc.invite.dm.sent": "You invited {0} ({1}) to chat", "desc.invite.group.received": "{0} ({1}) invited you to a group", "desc.invite.group.sent": "You invited {0} ({1}) to a group", - "action.fetching.invites.sent": "Fetching sent invites...", "action.fetching.invites.recv": "Fetching received invites...", "action.dm.fetch": "Fetching dms...", @@ -97,7 +93,6 @@ "action.invite.revoking": "Revoking invite...", "action.invite.accepting": "Accepting invite...", "action.invite.declining": "Declining invite...", - "title.sign.up": "Sign Up", "title.sign.in": "Sign In", "title.sign.in.to": "Sign in to your larpix instance", @@ -132,6 +127,7 @@ "title.react.message": "React", "title.delete.message": "Delete", "title.replied.to": "Replied to", + "title.replying.to": "Replying to", "action.message.deleting": "Deleting message...", "action.message.sending": "Sending message...", "action.message.reacting": "Adding reaction...", @@ -139,5 +135,8 @@ "error:message.send.failed": "Failed to send message", "error:message.react.failed": "Failed to add reaction", "info.sending.message": "Sending...", - "larp.redacted": "This message was deleted." -} + "larp.redacted": "This message was deleted.", + "placeholder.invitation.code": "invitation code", + "info.messages.loading.older": "Loading older messages...", + "error:message.not.found": "Message not found" +} \ No newline at end of file diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js index a1bc9de9..19223816 100644 --- a/android/app/src/main/assets/public/main.js +++ b/android/app/src/main/assets/public/main.js @@ -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 = `error:messages.decrypt.failed`; + content = `error:messages.decrypt.failed`; 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 ? `` : ``; + html += `
${showAvatar ? `` : `
`} -
+
${showAvatar ? `
${authorName} ${timeStr}
` : ""} ${repliedHtml} -
${content}
+
${redactedIcon}${content}
${reactionsHtml}
@@ -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, diff --git a/android/app/src/main/assets/public/screens.js b/android/app/src/main/assets/public/screens.js index 76dff8f4..56cfec0c 100644 --- a/android/app/src/main/assets/public/screens.js +++ b/android/app/src/main/assets/public/screens.js @@ -133,9 +133,9 @@ var chatScreen = `
-