add group context menu

This commit is contained in:
olcxja 2026-08-02 22:21:04 +02:00
commit 891f39e5b1
4 changed files with 236 additions and 2 deletions

View file

@ -1554,6 +1554,9 @@ async function switchDetailsContent(title, content, showBack = false, backAction
let pinCallBtns = showBack ? `${callBtnHtml}` : `${pinBtnHtml}${callBtnHtml}`;
if (window.currentRoomType === 'groups' && !showBack) {
pinCallBtns = `${groupMenuBtnHtml}${pinCallBtns}`;
}
let backBtn = showBack ? `<button class="topbar-action-btn" onclick="${backAction}" style="margin-right: 0.5rem">${ICONS.arrowBack({ width: "1.5rem", height: "1.5rem" })}</button>` : "";
roomsTopBarTransition.innerHTML = `<inherit style="display: flex; align-items: center;">${backBtn}<inherit>${processBlah(title)}</inherit></inherit><div class="flex-spacer"></div>${pinCallBtns}`;
@ -2542,7 +2545,7 @@ async function renderGroups(res) {
let pfp = await getGroupAvatarUrl(groupId, groupName);
html += `
<button class="room-entry" id="group-btn-${groupId}" onclick="openDm('${groupId}', decodeURIComponent('${safeGroupName}'), '${groupId}', 'groups')">
<button class="room-entry" id="group-btn-${groupId}" onclick="openDm('${groupId}', decodeURIComponent('${safeGroupName}'), '${groupId}', 'groups')" oncontextmenu="handleGroupContextMenu(event, '${groupId}')">
<img src="${pfp}" class="room-pfp">
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
<span class="room-name" style="${nameStyle}">${groupName}</span>
@ -4761,3 +4764,106 @@ if (typeof roomTopBar !== 'undefined' && roomTopBar) {
topbarMutationObserver.observe(roomTopBar.children.item(0), { childList: true, subtree: true });
}
}
var currentContextMenuGroupId = null;
function handleGroupContextMenu(e, groupId) {
if (e) {
e.preventDefault();
e.stopPropagation();
}
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
currentContextMenuGroupId = groupId;
let btn = e ? (e.currentTarget || e.target) : null;
let rect;
if (e && e.type === "click") {
rect = btn.getBoundingClientRect();
showFixedContextMenu({top: rect.top, right: rect.left, bottom: rect.bottom, left: rect.left}, groupContextMenu);
} else if (e) {
let clientX = e.clientX !== undefined ? e.clientX : (e.touches && e.touches.length > 0 ? e.touches[0].clientX : 0);
let clientY = e.clientY !== undefined ? e.clientY : (e.touches && e.touches.length > 0 ? e.touches[0].clientY : 0);
showFixedContextMenu({top: clientY, right: clientX, bottom: clientY, left: clientX}, groupContextMenu);
} else {
return;
}
let menuRect = fixedContextMenu.getBoundingClientRect();
let newLeft = e ? (e.clientX !== undefined ? e.clientX : (e.touches && e.touches.length > 0 ? e.touches[0].clientX : 0)) + 10 : 0;
let newTop = e ? (e.clientY !== undefined ? e.clientY : (e.touches && e.touches.length > 0 ? e.touches[0].clientY : 0)) : 0;
if (e && e.type === "click") {
newLeft = rect.left + 10;
newTop = rect.bottom;
}
if (newLeft + menuRect.width > window.innerWidth) {
newLeft = window.innerWidth - menuRect.width - 10;
}
if (newTop + menuRect.height > window.innerHeight) {
newTop = window.innerHeight - menuRect.height - 10;
}
fixedContextMenu.style.left = `${newLeft}px`;
fixedContextMenu.style.top = `${newTop}px`;
}
function gotoGroupSettings(groupId) {
if (fixedContextMenu) fixedContextMenu.classList.remove("show");
// Not implemented yet
}
function leaveGroupPrompt(groupId) {
if (fixedContextMenu) fixedContextMenu.classList.remove("show");
let popupContainer = document.getElementById("profile-popup-container");
let popupInner = document.getElementById("profile-popup-inner");
let html = `
<div style="text-align: center; margin-bottom: 1rem;">
<h3 style="margin-bottom: 0.5rem;"><blah>title.leave.group</blah></h3>
<p style="opacity: 0.7; font-size: 0.9rem;"><blah>desc.leave.group.confirm</blah></p>
</div>
<div style="display: flex; gap: 0.5rem; justify-content: center; width: 100%;">
<button class="profile-action-btn" style="background: var(--light-border-color); color: var(--text-color); flex: 1;" onclick="closeProfile()">
<blah>action.cancel</blah>
</button>
<button class="profile-action-btn" style="background: var(--big-red); color: white; border: none; flex: 1;" onclick="confirmLeaveGroup('${groupId}')">
<blah>title.leave.group</blah>
</button>
</div>
`;
popupInner.innerHTML = html;
let blahTags = popupInner.getElementsByTagName("blah");
for (let i = 0; i < blahTags.length; i++) {
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
}
popupContainer.style.display = "flex";
popupContainer.offsetHeight; // trigger reflow
popupContainer.classList.add("show");
isProfileOpen = true;
}
async function confirmLeaveGroup(groupId) {
closeProfile();
showAction("action.leaving.group", "leavegroup");
try {
let payload = JSON.stringify({ string1: groupId });
let res = await fetchEncrypted("group/leave", payload);
if (res === "success") {
if (currentDmId === groupId) {
gotoHome();
}
refreshGroups(0, false);
} else if (res.startsWith("error:")) {
showBlahNotification(res);
}
} catch (e) {
console.error(e);
showBlahNotification("error:something.wrong");
}
clearAction("leavegroup");
}

View file

@ -250,6 +250,16 @@ var addSpaceMenu = `
${ICONS.createSpace()}
<span class="blah">title.create.space</span>
</button>`
var groupContextMenu = `
<button onclick="gotoGroupSettings(currentContextMenuGroupId)">
${ICONS.settings()}
<span class="blah">title.group.settings</span>
</button>
<button onclick="leaveGroupPrompt(currentContextMenuGroupId)">
${ICONS.delete({ fill: "var(--big-red)" })}
<span class="blah" style="color: var(--big-red)">title.leave.group</span>
</button>
`;
var messageContextMenu = `
<button onclick="replyMessage(currentContextMenuMsgId)">
${ICONS.replyAction()}
@ -270,6 +280,7 @@ var messageContextMenu = `
var backBtnHtml = `<button class="mobile-nav-btn" onclick="mobileNavBack()">${ICONS.arrowBack({ width: "1.5rem", height: "1.5rem" })}</button>`;
var pinBtnHtml = `<button class="topbar-action-btn room-action-pin right" onclick="showPinnedMessages()">${ICONS.pin({ width: "1.5rem", height: "1.5rem" })}</button>`;
var callBtnHtml = `<button class="topbar-action-btn room-action-call right">${ICONS.call({ width: "1.5rem", height: "1.5rem" })}</button>`;
var groupMenuBtnHtml = `<button class="topbar-action-btn right" onclick="handleGroupContextMenu(arguments[0], currentDmId)">${ICONS.menu({ width: "1.5rem", height: "1.5rem" })}</button>`;
var settingsBar = `
<div class="sidebar-section-header">

View file

@ -1554,6 +1554,9 @@ async function switchDetailsContent(title, content, showBack = false, backAction
let pinCallBtns = showBack ? `${callBtnHtml}` : `${pinBtnHtml}${callBtnHtml}`;
if (window.currentRoomType === 'groups' && !showBack) {
pinCallBtns = `${groupMenuBtnHtml}${pinCallBtns}`;
}
let backBtn = showBack ? `<button class="topbar-action-btn" onclick="${backAction}" style="margin-right: 0.5rem">${ICONS.arrowBack({ width: "1.5rem", height: "1.5rem" })}</button>` : "";
roomsTopBarTransition.innerHTML = `<inherit style="display: flex; align-items: center;">${backBtn}<inherit>${processBlah(title)}</inherit></inherit><div class="flex-spacer"></div>${pinCallBtns}`;
@ -2542,7 +2545,7 @@ async function renderGroups(res) {
let pfp = await getGroupAvatarUrl(groupId, groupName);
html += `
<button class="room-entry" id="group-btn-${groupId}" onclick="openDm('${groupId}', decodeURIComponent('${safeGroupName}'), '${groupId}', 'groups')">
<button class="room-entry" id="group-btn-${groupId}" onclick="openDm('${groupId}', decodeURIComponent('${safeGroupName}'), '${groupId}', 'groups')" oncontextmenu="handleGroupContextMenu(event, '${groupId}')">
<img src="${pfp}" class="room-pfp">
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
<span class="room-name" style="${nameStyle}">${groupName}</span>
@ -4761,3 +4764,106 @@ if (typeof roomTopBar !== 'undefined' && roomTopBar) {
topbarMutationObserver.observe(roomTopBar.children.item(0), { childList: true, subtree: true });
}
}
var currentContextMenuGroupId = null;
function handleGroupContextMenu(e, groupId) {
if (e) {
e.preventDefault();
e.stopPropagation();
}
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
currentContextMenuGroupId = groupId;
let btn = e ? (e.currentTarget || e.target) : null;
let rect;
if (e && e.type === "click") {
rect = btn.getBoundingClientRect();
showFixedContextMenu({top: rect.top, right: rect.left, bottom: rect.bottom, left: rect.left}, groupContextMenu);
} else if (e) {
let clientX = e.clientX !== undefined ? e.clientX : (e.touches && e.touches.length > 0 ? e.touches[0].clientX : 0);
let clientY = e.clientY !== undefined ? e.clientY : (e.touches && e.touches.length > 0 ? e.touches[0].clientY : 0);
showFixedContextMenu({top: clientY, right: clientX, bottom: clientY, left: clientX}, groupContextMenu);
} else {
return;
}
let menuRect = fixedContextMenu.getBoundingClientRect();
let newLeft = e ? (e.clientX !== undefined ? e.clientX : (e.touches && e.touches.length > 0 ? e.touches[0].clientX : 0)) + 10 : 0;
let newTop = e ? (e.clientY !== undefined ? e.clientY : (e.touches && e.touches.length > 0 ? e.touches[0].clientY : 0)) : 0;
if (e && e.type === "click") {
newLeft = rect.left + 10;
newTop = rect.bottom;
}
if (newLeft + menuRect.width > window.innerWidth) {
newLeft = window.innerWidth - menuRect.width - 10;
}
if (newTop + menuRect.height > window.innerHeight) {
newTop = window.innerHeight - menuRect.height - 10;
}
fixedContextMenu.style.left = `${newLeft}px`;
fixedContextMenu.style.top = `${newTop}px`;
}
function gotoGroupSettings(groupId) {
if (fixedContextMenu) fixedContextMenu.classList.remove("show");
// Not implemented yet
}
function leaveGroupPrompt(groupId) {
if (fixedContextMenu) fixedContextMenu.classList.remove("show");
let popupContainer = document.getElementById("profile-popup-container");
let popupInner = document.getElementById("profile-popup-inner");
let html = `
<div style="text-align: center; margin-bottom: 1rem;">
<h3 style="margin-bottom: 0.5rem;"><blah>title.leave.group</blah></h3>
<p style="opacity: 0.7; font-size: 0.9rem;"><blah>desc.leave.group.confirm</blah></p>
</div>
<div style="display: flex; gap: 0.5rem; justify-content: center; width: 100%;">
<button class="profile-action-btn" style="background: var(--light-border-color); color: var(--text-color); flex: 1;" onclick="closeProfile()">
<blah>action.cancel</blah>
</button>
<button class="profile-action-btn" style="background: var(--big-red); color: white; border: none; flex: 1;" onclick="confirmLeaveGroup('${groupId}')">
<blah>title.leave.group</blah>
</button>
</div>
`;
popupInner.innerHTML = html;
let blahTags = popupInner.getElementsByTagName("blah");
for (let i = 0; i < blahTags.length; i++) {
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
}
popupContainer.style.display = "flex";
popupContainer.offsetHeight; // trigger reflow
popupContainer.classList.add("show");
isProfileOpen = true;
}
async function confirmLeaveGroup(groupId) {
closeProfile();
showAction("action.leaving.group", "leavegroup");
try {
let payload = JSON.stringify({ string1: groupId });
let res = await fetchEncrypted("group/leave", payload);
if (res === "success") {
if (currentDmId === groupId) {
gotoHome();
}
refreshGroups(0, false);
} else if (res.startsWith("error:")) {
showBlahNotification(res);
}
} catch (e) {
console.error(e);
showBlahNotification("error:something.wrong");
}
clearAction("leavegroup");
}

View file

@ -250,6 +250,16 @@ var addSpaceMenu = `
${ICONS.createSpace()}
<span class="blah">title.create.space</span>
</button>`
var groupContextMenu = `
<button onclick="gotoGroupSettings(currentContextMenuGroupId)">
${ICONS.settings()}
<span class="blah">title.group.settings</span>
</button>
<button onclick="leaveGroupPrompt(currentContextMenuGroupId)">
${ICONS.delete({ fill: "var(--big-red)" })}
<span class="blah" style="color: var(--big-red)">title.leave.group</span>
</button>
`;
var messageContextMenu = `
<button onclick="replyMessage(currentContextMenuMsgId)">
${ICONS.replyAction()}
@ -270,6 +280,7 @@ var messageContextMenu = `
var backBtnHtml = `<button class="mobile-nav-btn" onclick="mobileNavBack()">${ICONS.arrowBack({ width: "1.5rem", height: "1.5rem" })}</button>`;
var pinBtnHtml = `<button class="topbar-action-btn room-action-pin right" onclick="showPinnedMessages()">${ICONS.pin({ width: "1.5rem", height: "1.5rem" })}</button>`;
var callBtnHtml = `<button class="topbar-action-btn room-action-call right">${ICONS.call({ width: "1.5rem", height: "1.5rem" })}</button>`;
var groupMenuBtnHtml = `<button class="topbar-action-btn right" onclick="handleGroupContextMenu(arguments[0], currentDmId)">${ICONS.menu({ width: "1.5rem", height: "1.5rem" })}</button>`;
var settingsBar = `
<div class="sidebar-section-header">