web: Block in the DM kebab + Enter submits the group rename (#988)
Two gaps Paul found testing: (1) the group-rename dialog didn't submit on Enter — the name input now clicks Save on Enter; (2) blocking a DM partner was only reachable by tapping their header name — the DM kebab now carries a Block/Unblock <name> item (real people only, not bots or keyless invitees) that opens their profile, where Block keeps its ends-your-DMs consequence note.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
3 files changed,
+31
−0
web/pages/index.html+1 −0web/static/groupactions.js+8 −0web/static/shell.js+22 −0
diff --git a/web/pages/index.html b/web/pages/index.html| index 37301bc..234cf09 100644 |
| --- a/web/pages/index.html |
| +++ b/web/pages/index.html |
| @@ -208,6 +208,7 @@ |
| <button class="menu-item" id="thread-pin" role="menuitem">Pin chat</button> |
| <button class="menu-item" id="thread-mute" role="menuitem">Mute chat</button> |
| <button class="menu-item" id="thread-close" role="menuitem">Close chat</button> |
| + <button class="menu-item danger" id="thread-block" role="menuitem">Block</button> |
| <button class="menu-item danger" id="thread-end" role="menuitem">End conversation</button> |
| <button class="menu-item danger" id="thread-leave" role="menuitem">Leave group</button> |
| </div> |
diff --git a/web/static/groupactions.js b/web/static/groupactions.js| index 7cb8725..7e005a9 100644 |
| --- a/web/static/groupactions.js |
| +++ b/web/static/groupactions.js |
| @@ -203,6 +203,14 @@ export async function editGroup() { |
| cancel.onclick = () => backdrop.remove(); |
| const save = document.createElement("button"); |
| save.textContent = "Save"; |
| + // Enter in the name field submits, like any rename dialog (blur first so the |
| + // trimmed value is read). |
| + input.addEventListener("keydown", (e) => { |
| + if (e.key === "Enter") { |
| + e.preventDefault(); |
| + save.click(); |
| + } |
| + }); |
| save.onclick = async () => { |
| const next = input.value.trim(); |
| const flipOpen = openCb && openCb.checked !== (t.visibility === "open"); |
diff --git a/web/static/shell.js b/web/static/shell.js| index a9cbf7c..340aaa8 100644 |
| --- a/web/static/shell.js |
| +++ b/web/static/shell.js |
| @@ -2788,6 +2788,11 @@ function renderApp() { |
| closeTMenu(); |
| endConversation(); |
| }; |
| + document.getElementById("thread-block").onclick = (e) => { |
| + closeTMenu(); |
| + const uid = e.currentTarget.dataset.uid; |
| + if (uid) viewProfile(uid); // the profile's Block button + its consequence note |
| + }; |
| document.getElementById("thread-leave").onclick = () => { |
| closeTMenu(); |
| leaveGroup(); |
| @@ -3294,6 +3299,23 @@ function paintThreadHeader(id) { |
| // End conversation is a DM-only act (issue #578) — and once ended, the |
| // banner's Reopen is the way back, so the item hides on a closed DM too. |
| document.getElementById("thread-end").style.display = t && t.kind === "dm" && !dmClosed[id] ? "" : "none"; |
| + // Block lives in the DM kebab too (a person can be blocked from their profile, |
| + // but the menu is where people look). DMs with a real member only — not bots, |
| + // not a keyless invitee. It opens their profile, where Block carries its |
| + // ends-your-DMs consequence note. |
| + const blockEl = document.getElementById("thread-block"); |
| + if (blockEl) { |
| + const dmOther = t && t.kind === "dm" ? members.find((x) => x !== myUserId) : null; |
| + const pe = dmOther && roster.find((x) => x.user_id === dmOther); |
| + const canBlock = !!(pe && !pe.is_agent && pe.public_key); |
| + blockEl.style.display = canBlock ? "" : "none"; |
| + if (canBlock) { |
| + blockEl.textContent = blockedPeople.has(dmOther) |
| + ? `Unblock ${rosterLabel(dmOther)}` |
| + : `Block ${rosterLabel(dmOther)}`; |
| + blockEl.dataset.uid = dmOther; |
| + } |
| + } |
| paintViewToggle(id); |
| paintHeaderAvatar(id); |
| } |