| index 4d7144a..c916782 100644 |
| --- a/web/static/chat.js |
| +++ b/web/static/chat.js |
| @@ -363,6 +363,8 @@ async function start(session) { |
| |
| const key = await importKey(session.key); |
| const feed = document.getElementById("feed"); |
| + const isCoarse = |
| + window.matchMedia && window.matchMedia("(pointer: coarse)").matches; |
| |
| setupMenu(session); |
| |
| @@ -423,8 +425,8 @@ async function start(session) { |
| bubbleRow.appendChild(content); |
| el.appendChild(bubbleRow); |
| |
| - // Press-and-hold a message to react (no per-message button). |
| - attachLongPress(el, msg.id); |
| + // React: double-tap on touch, press-and-hold on desktop (no per-message button). |
| + attachReact(el, msg.id); |
| |
| const reactions = document.createElement("div"); |
| reactions.className = "reactions"; |
| @@ -536,7 +538,34 @@ async function start(session) { |
| }, 0); |
| } |
| |
| - // Press-and-hold (touch or mouse) on a message opens the reaction picker. |
| + // Touch reacts with a double-tap (leaving long-press free for text selection); |
| + // desktop reacts with a press-and-hold (which doesn't clash with drag-select). |
| + function attachReact(el, msgId) { |
| + if (isCoarse) attachDoubleTap(el, msgId); |
| + else attachLongPress(el, msgId); |
| + } |
| + |
| + // Double-tap to react (touch). Two taps within 320ms near the same spot. |
| + function attachDoubleTap(el, msgId) { |
| + let lastT = 0; |
| + let lx = 0; |
| + let ly = 0; |
| + el.addEventListener("pointerup", (e) => { |
| + if (e.pointerType === "mouse") return; |
| + const t = e.timeStamp; |
| + if (t - lastT < 320 && Math.abs(e.clientX - lx) < 30 && Math.abs(e.clientY - ly) < 30) { |
| + e.preventDefault(); |
| + lastT = 0; |
| + openReactionPicker(e.clientX, e.clientY, (emoji) => toggleReaction(msgId, emoji)); |
| + } else { |
| + lastT = t; |
| + lx = e.clientX; |
| + ly = e.clientY; |
| + } |
| + }); |
| + } |
| + |
| + // Press-and-hold (mouse) on a message opens the reaction picker. |
| function attachLongPress(el, msgId) { |
| let timer = null; |
| let sx = 0; |
| @@ -783,8 +812,6 @@ async function start(session) { |
| const attachBtn = document.getElementById("attach-btn"); |
| const fileInput = document.getElementById("file-input"); |
| const previewBox = document.getElementById("composer-preview"); |
| - const coarse = |
| - window.matchMedia && window.matchMedia("(pointer: coarse)").matches; |
| |
| // The attachment staged for the next send (uploaded already), or null. |
| let pendingAttachment = null; |
| @@ -999,7 +1026,7 @@ async function start(session) { |
| syncSendBtn(); |
| }); |
| input.addEventListener("keydown", (e) => { |
| - if (shouldSend(e, coarse)) { |
| + if (shouldSend(e, isCoarse)) { |
| e.preventDefault(); |
| send(); |
| } |