eleven / ocho Public

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>
Paul Campbell 1263f46ef4566eb87637723b560f4fc487980715 parent 0b02a55
3 files changed, +31 −0
  • web/pages/index.html +1 −0
  • web/static/groupactions.js +8 −0
  • web/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);
}