bug fixes
All checks were successful
Android Build / publish (push) Successful in 52s
Linux Build / publish (push) Successful in 57s

This commit is contained in:
olcxja 2026-08-17 11:59:19 +02:00
commit 51951bd6ef
8 changed files with 540 additions and 28 deletions

View file

@ -267,5 +267,28 @@
"label.keys.to.encrypt": "keys to encrypt, meow", "label.keys.to.encrypt": "keys to encrypt, meow",
"action.group.creating": "Creating group, meow...", "action.group.creating": "Creating group, meow...",
"title.group.settings": "Group settings, meow", "title.group.settings": "Group settings, meow",
"title.leave.group": "Leave group, meow" "title.leave.group": "Leave group, meow",
"role.member": "Meowber",
"role.group_owner": "Big purr",
"role.Owner": "Big purr",
"action.kick": "Hiss and scratch from group",
"action.transfer_owner": "Give shiny toy",
"group.user.kicked.by": "{0} hissed at {1} and they ran away",
"group.owner.transferred.by": "{0} gave the shiny toy to {1}",
"group.user.kicked": "Kitty kicked",
"group.owner.transferred": "Shiny toy given",
"success:kicked": "Hissed away successfully",
"success:owner.transferred": "Toy transferred",
"error:cannot.kick.self": "You can't scratch yourself",
"action.saving": "Meowving...",
"desc.no.chat.selected": "No purr selected. Select a purr from the sidebar to start meowing.",
"failed.to.prepare.keys": "Failed to prepare meows",
"group.name.empty": "Purr name cannot be empty",
"group.refresh.failed": "Failed to refresh purr",
"group.settings.saved": "Purr settings saved successfully",
"kicked": "Bited successfully",
"message.delete.failed": "Failed to delete meow",
"message.react.failed": "Failed to react to meow",
"message.send.failed": "Failed to send meow",
"owner.transferred": "Purrship transferred successfully"
} }

View file

@ -249,7 +249,7 @@
"group.user.joined": "Joined group", "group.user.joined": "Joined group",
"prompt.invite.username": "Enter username to invite:", "prompt.invite.username": "Enter username to invite:",
"desc.failed.load.members": "Failed to load members", "desc.failed.load.members": "Failed to load members",
"title.group.settings": "Group Settings", "title.group.settings": "Group settings",
"title.group.name": "Group Name", "title.group.name": "Group Name",
"title.group.history.visible": "History visible to newcomers", "title.group.history.visible": "History visible to newcomers",
"desc.group.history.visible": "New group members will be able to see older messages (disables key rotation)", "desc.group.history.visible": "New group members will be able to see older messages (disables key rotation)",
@ -273,6 +273,28 @@
"desc.key.rotation.you": "You will generate a new encryption key for this group.", "desc.key.rotation.you": "You will generate a new encryption key for this group.",
"label.keys.to.encrypt": "keys to encrypt", "label.keys.to.encrypt": "keys to encrypt",
"action.group.creating": "Creating group...", "action.group.creating": "Creating group...",
"title.group.settings": "Group settings", "title.leave.group": "Leave group",
"title.leave.group": "Leave group" "role.member": "Member",
"role.group_owner": "Group owner",
"role.Owner": "Owner",
"action.kick": "Kick from group",
"action.transfer_owner": "Transfer ownership",
"group.user.kicked.by": "{0} kicked {1} from the group",
"group.owner.transferred.by": "{0} transferred ownership to {1}",
"group.user.kicked": "User kicked",
"group.owner.transferred": "Ownership transferred",
"success:kicked": "User kicked successfully",
"success:owner.transferred": "Ownership transferred",
"error:cannot.kick.self": "You cannot kick yourself",
"action.saving": "Saving...",
"desc.no.chat.selected": "No chat selected. Select a chat from the sidebar to start messaging.",
"failed.to.prepare.keys": "Failed to prepare keys",
"group.name.empty": "Group name cannot be empty",
"group.refresh.failed": "Failed to refresh group",
"group.settings.saved": "Group settings saved successfully",
"kicked": "Kicked successfully",
"message.delete.failed": "Failed to delete message",
"message.react.failed": "Failed to react to message",
"message.send.failed": "Failed to send message",
"owner.transferred": "Ownership transferred successfully"
} }

View file

@ -273,6 +273,28 @@
"desc.key.rotation.you": "Wygenerujesz nowy klucz szyfrujący dla tej grupy.", "desc.key.rotation.you": "Wygenerujesz nowy klucz szyfrujący dla tej grupy.",
"label.keys.to.encrypt": "kluczy do zaszyfrowania", "label.keys.to.encrypt": "kluczy do zaszyfrowania",
"action.group.creating": "Tworzenie grupy...", "action.group.creating": "Tworzenie grupy...",
"title.group.settings": "Ustawienia grupy", "title.leave.group": "Opuść grupę",
"title.leave.group": "Opuść grupę" "role.member": "Członek",
"role.group_owner": "Właściciel grupy",
"role.Owner": "Właściciel",
"action.kick": "Wyrzuć z grupy",
"action.transfer_owner": "Przekaż właściciela",
"group.user.kicked.by": "{0} wyrzucił/a {1} z grupy",
"group.owner.transferred.by": "{0} przekazał/a właściciela dla {1}",
"group.user.kicked": "Użytkownik wyrzucony",
"group.owner.transferred": "Właściciel przekazany",
"success:kicked": "Użytkownik wyrzucony pomyślnie",
"success:owner.transferred": "Właściciel przekazany pomyślnie",
"error:cannot.kick.self": "Nie możesz wyrzucić samego siebie",
"action.saving": "Zapisywanie...",
"desc.no.chat.selected": "Nie wybrano czatu. Wybierz czat z paska bocznego, aby rozpocząć pisanie.",
"failed.to.prepare.keys": "Nie udało się przygotować kluczy",
"group.name.empty": "Nazwa grupy nie może być pusta",
"group.refresh.failed": "Nie udało się odświeżyć grupy",
"group.settings.saved": "Ustawienia grupy zapisane pomyślnie",
"kicked": "Wyrzucono pomyślnie",
"message.delete.failed": "Nie udało się usunąć wiadomości",
"message.react.failed": "Nie udało się dodać reakcji",
"message.send.failed": "Nie udało się wysłać wiadomości",
"owner.transferred": "Własność przekazana pomyślnie"
} }

View file

@ -1563,13 +1563,19 @@ async function switchRoomContent(title, content, showRoomBar, icon = "", skipMob
roomsTopBarTransition.style.opacity = "0"; roomsTopBarTransition.style.opacity = "0";
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
let slideIn = false;
if (window.innerWidth <= 55 * rem && !skipMobileSlide) { if (window.innerWidth <= 55 * rem && !skipMobileSlide) {
if (title != "title.splash") //do not show splash on mobile if (title != "title.splash") //do not show splash on mobile
{ {
if (!mainScreen.classList.contains('mobile-content') && !mainScreen.classList.contains('mobile-details')) {
slideIn = true;
}
mainScreen.classList.remove('mobile-details'); mainScreen.classList.remove('mobile-details');
mainScreen.classList.add('mobile-content'); mainScreen.classList.add('mobile-content');
} }
} else { }
if (!slideIn) {
await delay(200); await delay(200);
} }
@ -1762,6 +1768,7 @@ function clearContextMenuStyles() {
} }
document.addEventListener("click", (e) => { document.addEventListener("click", (e) => {
if (touchContextMenuFired) return;
history.pushState({trap: true}, "", location.href); history.pushState({trap: true}, "", location.href);
if (fixedContextMenu.classList.contains("show")) { if (fixedContextMenu.classList.contains("show")) {
@ -1778,7 +1785,11 @@ document.addEventListener("click", (e) => {
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
if (window.innerWidth <= 55 * rem) { if (window.innerWidth <= 55 * rem) {
if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container') && !e.target.closest('.room-action-pin')) { if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container') && !e.target.closest('.room-action-pin')) {
mobileNavBack(); if (currentRoomContentTitle === "title.group.settings") {
openDm(window.currentSettingsGroupId, document.getElementById('group-settings-name').value, window.currentSettingsGroupId, 'groups');
} else {
mobileNavBack();
}
} }
} }
} }
@ -1848,6 +1859,7 @@ function logout() {
let isProfileOpen = false; let isProfileOpen = false;
async function openProfile(accountId) { async function openProfile(accountId) {
if (touchContextMenuFired) return;
if (fixedContextMenu.classList.contains("show")) { if (fixedContextMenu.classList.contains("show")) {
fixedContextMenu.classList.remove("show"); fixedContextMenu.classList.remove("show");
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
@ -1969,6 +1981,19 @@ async function openProfile(accountId) {
} }
} }
} }
let roleHtml = "";
if (window.currentDmId && window.currentRoomType && (window.currentRoomType === "groups" || window.currentRoomType === "dms" || window.currentRoomType === "spaces")) {
let payload = JSON.stringify({
string1: window.currentRoomType,
string2: window.currentDmId,
string3: accountId
});
let roleRes = await fetchEncrypted("room/role", payload);
if (roleRes && !roleRes.startsWith("error:")) {
let mappedRole = "role." + roleRes.replace(" ", "_");
roleHtml = `<div class="profile-role" style="opacity:0.7; font-size:0.9rem; margin-top:0.3rem;"><blah>${mappedRole}</blah></div>`;
}
}
popupInner.innerHTML = ` popupInner.innerHTML = `
${bannerHtml} ${bannerHtml}
@ -1979,6 +2004,7 @@ async function openProfile(accountId) {
<div> <div>
<div class="profile-name">${profileName}</div> <div class="profile-name">${profileName}</div>
<div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0].split(';')[0]})</div> <div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0].split(';')[0]})</div>
${roleHtml}
</div> </div>
${bioHtml} ${bioHtml}
${actionBtnHtml} ${actionBtnHtml}
@ -2423,6 +2449,11 @@ function setActiveSidebarIndicator(element, isTemporary = false) {
} }
function mobileNavBack() { function mobileNavBack() {
if (currentRoomContentTitle === "title.group.settings") {
openDm(window.currentSettingsGroupId, document.getElementById('group-settings-name').value, window.currentSettingsGroupId, 'groups');
return;
}
if (mainScreen.classList.contains('mobile-details')) { if (mainScreen.classList.contains('mobile-details')) {
mainScreen.classList.remove('mobile-details'); mainScreen.classList.remove('mobile-details');
mainScreen.classList.add('mobile-content'); mainScreen.classList.add('mobile-content');
@ -2512,6 +2543,7 @@ document.addEventListener('touchend', async (e) => {
} }
let elapsed = Date.now() - pressDate; //najgorszy fix swiata let elapsed = Date.now() - pressDate; //najgorszy fix swiata
if (elapsed < 100) { if (elapsed < 100) {
const remaining = 100 - elapsed; const remaining = 100 - elapsed;
await delay(remaining); await delay(remaining);
@ -2861,7 +2893,7 @@ async function generateGroupMembersHtml(groupId, groupName) {
let memberDisplayName = memberUsername.split(':')[0]; let memberDisplayName = memberUsername.split(':')[0];
html += ` html += `
<button class="room-entry" onclick="openProfile('${memberId}')" style="margin-bottom: 0;"> <button class="room-entry" onclick="openProfile('${memberId}')" oncontextmenu="handleMemberContextMenu(event, '${groupId}', '${memberId}')" style="margin-bottom: 0;">
<img src="${memberPfp}" class="room-pfp"> <img src="${memberPfp}" class="room-pfp">
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;"> <div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
<span class="room-name">${memberDisplayName}</span> <span class="room-name">${memberDisplayName}</span>
@ -2875,6 +2907,9 @@ async function generateGroupMembersHtml(groupId, groupName) {
} }
async function openDm(dmId, username, targetId, type = 'dms') { async function openDm(dmId, username, targetId, type = 'dms') {
if (touchContextMenuFired) {
return;
}
try { try {
showAction("action.dm.opening", "dmopen"); showAction("action.dm.opening", "dmopen");
@ -2894,8 +2929,10 @@ async function openDm(dmId, username, targetId, type = 'dms') {
if (infoPayload && !infoPayload.startsWith("error:")) { if (infoPayload && !infoPayload.startsWith("error:")) {
let info = JSON.parse(infoPayload); let info = JSON.parse(infoPayload);
window.currentGroupKeyIndex = info.string4 || "0"; window.currentGroupKeyIndex = info.string4 || "0";
window.currentGroupOwner = info.string7;
} else { } else {
window.currentGroupKeyIndex = "0"; window.currentGroupKeyIndex = "0";
window.currentGroupOwner = "";
} }
} }
@ -2972,6 +3009,16 @@ async function loadDmMessages(dmId, startOffset = "", isPrepend = false) {
let res = await fetchEncrypted(endpoint, payload); let res = await fetchEncrypted(endpoint, payload);
if (res && res.startsWith("error:")) { if (res && res.startsWith("error:")) {
if (res === "error:forbidden" || res === "error:not.a.member" || res === "error:group.not.found") {
if (dmMessagePollInterval) clearInterval(dmMessagePollInterval);
currentDmId = null;
showBlahNotification(res);
if (mainScreen.classList.contains('mobile-details')) {
mainScreen.classList.remove('mobile-details');
}
switchDetailsContent("title.details", `<div style="text-align: center; opacity: 0.5; padding: 1rem; margin-top: 2rem;"><blah>desc.no.chat.selected</blah></div>`);
return;
}
showBlahNotification(res); showBlahNotification(res);
return; return;
} }
@ -3070,7 +3117,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
try { try {
let atts = JSON.parse(msg.attachment); let atts = JSON.parse(msg.attachment);
if (atts.length > 0) { if (atts.length > 0) {
attachmentsHtml += `<div class="chat-message-attachments" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;">`; attachmentsHtml += `<div class="chat-message-attachments" data-raw="${escapeHtml(msg.attachment)}" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;">`;
for (let att of atts) { for (let att of atts) {
if (att.enc && att.enc.startsWith("ENC:")) { if (att.enc && att.enc.startsWith("ENC:")) {
try { try {
@ -3116,7 +3163,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
console.error("Failed to parse attachments", e); console.error("Failed to parse attachments", e);
} }
} else { } else {
attachmentsHtml += `<div class="chat-message-attachments" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;"> attachmentsHtml += `<div class="chat-message-attachments" data-raw="${escapeHtml(msg.attachment)}" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;">
<div style="padding: 0.5rem; background: rgba(0,0,0,0.2); border: var(--border-width) solid var(--border-color); border-radius: 0.5rem; font-size: 0.8rem; opacity: 0.7;">[${processBlah("attachment.legacy")}: ${escapeHtml(msg.attachment)}]</div> <div style="padding: 0.5rem; background: rgba(0,0,0,0.2); border: var(--border-width) solid var(--border-color); border-radius: 0.5rem; font-size: 0.8rem; opacity: 0.7;">[${processBlah("attachment.legacy")}: ${escapeHtml(msg.attachment)}]</div>
</div>`; </div>`;
} }
@ -3133,7 +3180,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
content = processBlah(content); content = processBlah(content);
if (msg.type && msg.type.startsWith("larp.info")) { if (msg.type && msg.type.startsWith("larp.info")) {
if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create" || msg.type === "larp.info.history") { if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create" || msg.type === "larp.info.history" || msg.type === "larp.info.kick" || msg.type === "larp.info.transfer_owner") {
let authorName = processBlah("title.unknown"); let authorName = processBlah("title.unknown");
if (msg.author && msg.author !== "0") { if (msg.author && msg.author !== "0") {
if (!dmUsernameCache[msg.author]) { if (!dmUsernameCache[msg.author]) {
@ -3157,6 +3204,8 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
else if (msg.type === "larp.info.leave") actionStr = "group.user.left.by"; else if (msg.type === "larp.info.leave") actionStr = "group.user.left.by";
else if (msg.type === "larp.info.create") actionStr = "group.created.by"; else if (msg.type === "larp.info.create") actionStr = "group.created.by";
else if (msg.type === "larp.info.history") actionStr = "group.history.changed.by"; else if (msg.type === "larp.info.history") actionStr = "group.history.changed.by";
else if (msg.type === "larp.info.kick") actionStr = "group.user.kicked.by";
else if (msg.type === "larp.info.transfer_owner") actionStr = "group.owner.transferred.by";
let clickableAuthor = `<span onclick="event.stopPropagation(); openProfile('${msg.author}')" style="cursor: pointer; font-weight: bold;">${authorName}</span>`; let clickableAuthor = `<span onclick="event.stopPropagation(); openProfile('${msg.author}')" style="cursor: pointer; font-weight: bold;">${authorName}</span>`;
@ -3166,6 +3215,18 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg); content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg);
} else if (msg.type === "larp.info.history") { } else if (msg.type === "larp.info.history") {
content = textStr.replace("{0}", clickableAuthor).replace("{1}", content); content = textStr.replace("{0}", clickableAuthor).replace("{1}", content);
} else if (msg.type === "larp.info.kick" || msg.type === "larp.info.transfer_owner") {
let targetUsername = "";
let targetId = "";
let parts = content.split(':');
if (parts.length >= 2 && parts[0].includes(';')) {
targetId = parts[0];
targetUsername = escapeHtml(parts.slice(1).join(':'));
} else {
targetUsername = escapeHtml(parts[0]);
}
let clickableTarget = targetId ? `<span onclick="event.stopPropagation(); openProfile('${targetId}')" style="font-weight: bold; cursor: pointer;">${targetUsername}</span>` : `<span style="font-weight: bold;">${targetUsername}</span>`;
content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableTarget);
} else { } else {
content = textStr.replace("{0}", clickableAuthor); content = textStr.replace("{0}", clickableAuthor);
} }
@ -3424,6 +3485,20 @@ async function renderMessages(messages, isPrepend = false) {
oldReactions.remove(); oldReactions.remove();
} }
let oldAttachments = existingChild.querySelector('.chat-message-attachments');
let newAttachments = newChild.querySelector('.chat-message-attachments');
if (newAttachments) {
if (oldAttachments) {
if (oldAttachments.getAttribute('data-raw') !== newAttachments.getAttribute('data-raw')) oldAttachments.innerHTML = newAttachments.innerHTML;
} else {
let contentNode = existingChild.querySelector('.chat-message-content');
if (oldReactions) contentNode.insertBefore(newAttachments, oldReactions);
else contentNode.appendChild(newAttachments);
}
} else if (oldAttachments) {
oldAttachments.remove();
}
let oldPfp = existingChild.querySelector('.chat-message-pfp'); let oldPfp = existingChild.querySelector('.chat-message-pfp');
let newPfp = newChild.querySelector('.chat-message-pfp'); let newPfp = newChild.querySelector('.chat-message-pfp');
if (oldPfp && newPfp && oldPfp.src !== newPfp.src) { if (oldPfp && newPfp && oldPfp.src !== newPfp.src) {
@ -3920,7 +3995,6 @@ let touchContextMenuFired = false;
function handleMessageContextMenu(e, msgId) { function handleMessageContextMenu(e, msgId) {
e.preventDefault(); e.preventDefault();
if (touchContextMenuFired) { if (touchContextMenuFired) {
touchContextMenuFired = false;
return; return;
} }
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
@ -5013,6 +5087,96 @@ if (typeof roomTopBar !== 'undefined' && roomTopBar) {
var currentContextMenuGroupId = null; var currentContextMenuGroupId = null;
function handleMemberContextMenu(e, groupId, memberId) {
if (window.currentRoomType !== 'groups') return;
e.preventDefault();
e.stopPropagation();
if (e && e.type === "contextmenu") {
touchContextMenuFired = true;
setTimeout(() => { touchContextMenuFired = false; }, 500);
}
let html = '';
let myIdStr = window.id + ";" + window.host;
if (window.currentGroupOwner === myIdStr && memberId !== myIdStr) {
html += `
<button onclick="kickFromGroup('${groupId}', '${memberId}')">
${ICONS.delete({ fill: "var(--big-red)" })}
<span class="blah" style="color: var(--big-red)">action.kick</span>
</button>
<button onclick="transferGroupOwnership('${groupId}', '${memberId}')">
${ICONS.profile()}
<span class="blah">action.transfer_owner</span>
</button>
`;
}
if (html !== '') {
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}, html);
}
}
async function kickFromGroup(groupId, memberId) {
if (fixedContextMenu.classList.contains("show")) {
fixedContextMenu.classList.remove("show");
}
showAction("action.processing", "kick.action");
let payload = JSON.stringify({
string1: groupId,
string2: memberId
});
let res = await fetchEncrypted("group/kick", payload);
clearAction("kick.action");
if (res && res.startsWith("success")) {
showBlahNotification("success:kicked");
showGroupMembers(groupId).then(html => {
let container = document.getElementById("right-sidebar-container");
if (container) {
container.innerHTML = html;
translate(container);
}
});
} else {
showBlahNotification(res || "error:something.wrong");
}
}
async function transferGroupOwnership(groupId, memberId) {
if (fixedContextMenu.classList.contains("show")) {
fixedContextMenu.classList.remove("show");
}
showAction("action.processing", "transfer.action");
let payload = JSON.stringify({
string1: groupId,
string2: memberId
});
let res = await fetchEncrypted("group/transferowner", payload);
clearAction("transfer.action");
if (res && res.startsWith("success")) {
window.currentGroupOwner = memberId;
showBlahNotification("success:owner.transferred");
showGroupMembers(groupId).then(html => {
let container = document.getElementById("right-sidebar-container");
if (container) {
container.innerHTML = html;
translate(container);
}
});
} else {
showBlahNotification(res || "error:something.wrong");
}
}
function handleGroupContextMenu(e, groupId) { function handleGroupContextMenu(e, groupId) {
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
@ -5020,6 +5184,10 @@ function handleGroupContextMenu(e, groupId) {
} }
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
currentContextMenuGroupId = groupId; currentContextMenuGroupId = groupId;
if (e && e.type === "contextmenu") {
touchContextMenuFired = true;
setTimeout(() => { touchContextMenuFired = false; }, 500);
}
let btn = e ? (e.currentTarget || e.target) : null; let btn = e ? (e.currentTarget || e.target) : null;
let rect; let rect;
@ -5062,6 +5230,8 @@ async function gotoGroupSettings(groupId) {
let infoStr = await fetchEncrypted("group/info", groupId); let infoStr = await fetchEncrypted("group/info", groupId);
if (!infoStr.startsWith("error:")) { if (!infoStr.startsWith("error:")) {
let info = JSON.parse(infoStr); let info = JSON.parse(infoStr);
window.currentGroupOwner = info.string7;
await switchRoomContent("title.group.settings", groupSettingsScreen, false); await switchRoomContent("title.group.settings", groupSettingsScreen, false);
document.getElementById('group-settings-name').value = info.string2; document.getElementById('group-settings-name').value = info.string2;
@ -5070,6 +5240,19 @@ async function gotoGroupSettings(groupId) {
document.getElementById('group-settings-avatar-preview').src = pfpUrl; document.getElementById('group-settings-avatar-preview').src = pfpUrl;
document.getElementById('group-settings-history').checked = (info.string6 === "true"); document.getElementById('group-settings-history').checked = (info.string6 === "true");
let myIdStr = window.id + ";" + window.host;
let historyCheckbox = document.getElementById('group-settings-history');
let historyContainer = historyCheckbox.closest('.setting-row');
if (historyContainer) {
if (window.currentGroupOwner !== myIdStr) {
historyContainer.style.display = 'none';
} else {
historyContainer.style.display = 'flex';
}
} else {
historyCheckbox.disabled = (window.currentGroupOwner !== myIdStr);
}
} else { } else {
showBlahNotification(infoStr); showBlahNotification(infoStr);
} }
@ -5120,7 +5303,13 @@ async function confirmLeaveGroup(groupId) {
let res = await fetchEncrypted("group/leave", payload); let res = await fetchEncrypted("group/leave", payload);
if (res === "success") { if (res === "success") {
if (currentDmId === groupId) { if (currentDmId === groupId) {
gotoHome(); currentDmId = null;
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
if (window.innerWidth <= 55 * rem) {
mobileNavBack();
} else {
gotoHome();
}
} }
refreshGroups(0, false); refreshGroups(0, false);
} else if (res.startsWith("error:")) { } else if (res.startsWith("error:")) {

View file

@ -267,5 +267,28 @@
"label.keys.to.encrypt": "keys to encrypt, meow", "label.keys.to.encrypt": "keys to encrypt, meow",
"action.group.creating": "Creating group, meow...", "action.group.creating": "Creating group, meow...",
"title.group.settings": "Group settings, meow", "title.group.settings": "Group settings, meow",
"title.leave.group": "Leave group, meow" "title.leave.group": "Leave group, meow",
"role.member": "Meowber",
"role.group_owner": "Big purr",
"role.Owner": "Big purr",
"action.kick": "Hiss and scratch from group",
"action.transfer_owner": "Give shiny toy",
"group.user.kicked.by": "{0} hissed at {1} and they ran away",
"group.owner.transferred.by": "{0} gave the shiny toy to {1}",
"group.user.kicked": "Kitty kicked",
"group.owner.transferred": "Shiny toy given",
"success:kicked": "Hissed away successfully",
"success:owner.transferred": "Toy transferred",
"error:cannot.kick.self": "You can't scratch yourself",
"action.saving": "Meowving...",
"desc.no.chat.selected": "No purr selected. Select a purr from the sidebar to start meowing.",
"failed.to.prepare.keys": "Failed to prepare meows",
"group.name.empty": "Purr name cannot be empty",
"group.refresh.failed": "Failed to refresh purr",
"group.settings.saved": "Purr settings saved successfully",
"kicked": "Bited successfully",
"message.delete.failed": "Failed to delete meow",
"message.react.failed": "Failed to react to meow",
"message.send.failed": "Failed to send meow",
"owner.transferred": "Purrship transferred successfully"
} }

View file

@ -249,7 +249,7 @@
"group.user.joined": "Joined group", "group.user.joined": "Joined group",
"prompt.invite.username": "Enter username to invite:", "prompt.invite.username": "Enter username to invite:",
"desc.failed.load.members": "Failed to load members", "desc.failed.load.members": "Failed to load members",
"title.group.settings": "Group Settings", "title.group.settings": "Group settings",
"title.group.name": "Group Name", "title.group.name": "Group Name",
"title.group.history.visible": "History visible to newcomers", "title.group.history.visible": "History visible to newcomers",
"desc.group.history.visible": "New group members will be able to see older messages (disables key rotation)", "desc.group.history.visible": "New group members will be able to see older messages (disables key rotation)",
@ -273,6 +273,28 @@
"desc.key.rotation.you": "You will generate a new encryption key for this group.", "desc.key.rotation.you": "You will generate a new encryption key for this group.",
"label.keys.to.encrypt": "keys to encrypt", "label.keys.to.encrypt": "keys to encrypt",
"action.group.creating": "Creating group...", "action.group.creating": "Creating group...",
"title.group.settings": "Group settings", "title.leave.group": "Leave group",
"title.leave.group": "Leave group" "role.member": "Member",
"role.group_owner": "Group owner",
"role.Owner": "Owner",
"action.kick": "Kick from group",
"action.transfer_owner": "Transfer ownership",
"group.user.kicked.by": "{0} kicked {1} from the group",
"group.owner.transferred.by": "{0} transferred ownership to {1}",
"group.user.kicked": "User kicked",
"group.owner.transferred": "Ownership transferred",
"success:kicked": "User kicked successfully",
"success:owner.transferred": "Ownership transferred",
"error:cannot.kick.self": "You cannot kick yourself",
"action.saving": "Saving...",
"desc.no.chat.selected": "No chat selected. Select a chat from the sidebar to start messaging.",
"failed.to.prepare.keys": "Failed to prepare keys",
"group.name.empty": "Group name cannot be empty",
"group.refresh.failed": "Failed to refresh group",
"group.settings.saved": "Group settings saved successfully",
"kicked": "Kicked successfully",
"message.delete.failed": "Failed to delete message",
"message.react.failed": "Failed to react to message",
"message.send.failed": "Failed to send message",
"owner.transferred": "Ownership transferred successfully"
} }

View file

@ -273,6 +273,28 @@
"desc.key.rotation.you": "Wygenerujesz nowy klucz szyfrujący dla tej grupy.", "desc.key.rotation.you": "Wygenerujesz nowy klucz szyfrujący dla tej grupy.",
"label.keys.to.encrypt": "kluczy do zaszyfrowania", "label.keys.to.encrypt": "kluczy do zaszyfrowania",
"action.group.creating": "Tworzenie grupy...", "action.group.creating": "Tworzenie grupy...",
"title.group.settings": "Ustawienia grupy", "title.leave.group": "Opuść grupę",
"title.leave.group": "Opuść grupę" "role.member": "Członek",
"role.group_owner": "Właściciel grupy",
"role.Owner": "Właściciel",
"action.kick": "Wyrzuć z grupy",
"action.transfer_owner": "Przekaż właściciela",
"group.user.kicked.by": "{0} wyrzucił/a {1} z grupy",
"group.owner.transferred.by": "{0} przekazał/a właściciela dla {1}",
"group.user.kicked": "Użytkownik wyrzucony",
"group.owner.transferred": "Właściciel przekazany",
"success:kicked": "Użytkownik wyrzucony pomyślnie",
"success:owner.transferred": "Właściciel przekazany pomyślnie",
"error:cannot.kick.self": "Nie możesz wyrzucić samego siebie",
"action.saving": "Zapisywanie...",
"desc.no.chat.selected": "Nie wybrano czatu. Wybierz czat z paska bocznego, aby rozpocząć pisanie.",
"failed.to.prepare.keys": "Nie udało się przygotować kluczy",
"group.name.empty": "Nazwa grupy nie może być pusta",
"group.refresh.failed": "Nie udało się odświeżyć grupy",
"group.settings.saved": "Ustawienia grupy zapisane pomyślnie",
"kicked": "Wyrzucono pomyślnie",
"message.delete.failed": "Nie udało się usunąć wiadomości",
"message.react.failed": "Nie udało się dodać reakcji",
"message.send.failed": "Nie udało się wysłać wiadomości",
"owner.transferred": "Własność przekazana pomyślnie"
} }

View file

@ -1563,13 +1563,19 @@ async function switchRoomContent(title, content, showRoomBar, icon = "", skipMob
roomsTopBarTransition.style.opacity = "0"; roomsTopBarTransition.style.opacity = "0";
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
let slideIn = false;
if (window.innerWidth <= 55 * rem && !skipMobileSlide) { if (window.innerWidth <= 55 * rem && !skipMobileSlide) {
if (title != "title.splash") //do not show splash on mobile if (title != "title.splash") //do not show splash on mobile
{ {
if (!mainScreen.classList.contains('mobile-content') && !mainScreen.classList.contains('mobile-details')) {
slideIn = true;
}
mainScreen.classList.remove('mobile-details'); mainScreen.classList.remove('mobile-details');
mainScreen.classList.add('mobile-content'); mainScreen.classList.add('mobile-content');
} }
} else { }
if (!slideIn) {
await delay(200); await delay(200);
} }
@ -1762,6 +1768,7 @@ function clearContextMenuStyles() {
} }
document.addEventListener("click", (e) => { document.addEventListener("click", (e) => {
if (touchContextMenuFired) return;
history.pushState({trap: true}, "", location.href); history.pushState({trap: true}, "", location.href);
if (fixedContextMenu.classList.contains("show")) { if (fixedContextMenu.classList.contains("show")) {
@ -1778,7 +1785,11 @@ document.addEventListener("click", (e) => {
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
if (window.innerWidth <= 55 * rem) { if (window.innerWidth <= 55 * rem) {
if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container') && !e.target.closest('.room-action-pin')) { if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container') && !e.target.closest('.room-action-pin')) {
mobileNavBack(); if (currentRoomContentTitle === "title.group.settings") {
openDm(window.currentSettingsGroupId, document.getElementById('group-settings-name').value, window.currentSettingsGroupId, 'groups');
} else {
mobileNavBack();
}
} }
} }
} }
@ -1848,6 +1859,7 @@ function logout() {
let isProfileOpen = false; let isProfileOpen = false;
async function openProfile(accountId) { async function openProfile(accountId) {
if (touchContextMenuFired) return;
if (fixedContextMenu.classList.contains("show")) { if (fixedContextMenu.classList.contains("show")) {
fixedContextMenu.classList.remove("show"); fixedContextMenu.classList.remove("show");
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
@ -1969,6 +1981,19 @@ async function openProfile(accountId) {
} }
} }
} }
let roleHtml = "";
if (window.currentDmId && window.currentRoomType && (window.currentRoomType === "groups" || window.currentRoomType === "dms" || window.currentRoomType === "spaces")) {
let payload = JSON.stringify({
string1: window.currentRoomType,
string2: window.currentDmId,
string3: accountId
});
let roleRes = await fetchEncrypted("room/role", payload);
if (roleRes && !roleRes.startsWith("error:")) {
let mappedRole = "role." + roleRes.replace(" ", "_");
roleHtml = `<div class="profile-role" style="opacity:0.7; font-size:0.9rem; margin-top:0.3rem;"><blah>${mappedRole}</blah></div>`;
}
}
popupInner.innerHTML = ` popupInner.innerHTML = `
${bannerHtml} ${bannerHtml}
@ -1979,6 +2004,7 @@ async function openProfile(accountId) {
<div> <div>
<div class="profile-name">${profileName}</div> <div class="profile-name">${profileName}</div>
<div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0].split(';')[0]})</div> <div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0].split(';')[0]})</div>
${roleHtml}
</div> </div>
${bioHtml} ${bioHtml}
${actionBtnHtml} ${actionBtnHtml}
@ -2423,6 +2449,11 @@ function setActiveSidebarIndicator(element, isTemporary = false) {
} }
function mobileNavBack() { function mobileNavBack() {
if (currentRoomContentTitle === "title.group.settings") {
openDm(window.currentSettingsGroupId, document.getElementById('group-settings-name').value, window.currentSettingsGroupId, 'groups');
return;
}
if (mainScreen.classList.contains('mobile-details')) { if (mainScreen.classList.contains('mobile-details')) {
mainScreen.classList.remove('mobile-details'); mainScreen.classList.remove('mobile-details');
mainScreen.classList.add('mobile-content'); mainScreen.classList.add('mobile-content');
@ -2512,6 +2543,7 @@ document.addEventListener('touchend', async (e) => {
} }
let elapsed = Date.now() - pressDate; //najgorszy fix swiata let elapsed = Date.now() - pressDate; //najgorszy fix swiata
if (elapsed < 100) { if (elapsed < 100) {
const remaining = 100 - elapsed; const remaining = 100 - elapsed;
await delay(remaining); await delay(remaining);
@ -2861,7 +2893,7 @@ async function generateGroupMembersHtml(groupId, groupName) {
let memberDisplayName = memberUsername.split(':')[0]; let memberDisplayName = memberUsername.split(':')[0];
html += ` html += `
<button class="room-entry" onclick="openProfile('${memberId}')" style="margin-bottom: 0;"> <button class="room-entry" onclick="openProfile('${memberId}')" oncontextmenu="handleMemberContextMenu(event, '${groupId}', '${memberId}')" style="margin-bottom: 0;">
<img src="${memberPfp}" class="room-pfp"> <img src="${memberPfp}" class="room-pfp">
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;"> <div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
<span class="room-name">${memberDisplayName}</span> <span class="room-name">${memberDisplayName}</span>
@ -2875,6 +2907,9 @@ async function generateGroupMembersHtml(groupId, groupName) {
} }
async function openDm(dmId, username, targetId, type = 'dms') { async function openDm(dmId, username, targetId, type = 'dms') {
if (touchContextMenuFired) {
return;
}
try { try {
showAction("action.dm.opening", "dmopen"); showAction("action.dm.opening", "dmopen");
@ -2894,8 +2929,10 @@ async function openDm(dmId, username, targetId, type = 'dms') {
if (infoPayload && !infoPayload.startsWith("error:")) { if (infoPayload && !infoPayload.startsWith("error:")) {
let info = JSON.parse(infoPayload); let info = JSON.parse(infoPayload);
window.currentGroupKeyIndex = info.string4 || "0"; window.currentGroupKeyIndex = info.string4 || "0";
window.currentGroupOwner = info.string7;
} else { } else {
window.currentGroupKeyIndex = "0"; window.currentGroupKeyIndex = "0";
window.currentGroupOwner = "";
} }
} }
@ -2972,6 +3009,16 @@ async function loadDmMessages(dmId, startOffset = "", isPrepend = false) {
let res = await fetchEncrypted(endpoint, payload); let res = await fetchEncrypted(endpoint, payload);
if (res && res.startsWith("error:")) { if (res && res.startsWith("error:")) {
if (res === "error:forbidden" || res === "error:not.a.member" || res === "error:group.not.found") {
if (dmMessagePollInterval) clearInterval(dmMessagePollInterval);
currentDmId = null;
showBlahNotification(res);
if (mainScreen.classList.contains('mobile-details')) {
mainScreen.classList.remove('mobile-details');
}
switchDetailsContent("title.details", `<div style="text-align: center; opacity: 0.5; padding: 1rem; margin-top: 2rem;"><blah>desc.no.chat.selected</blah></div>`);
return;
}
showBlahNotification(res); showBlahNotification(res);
return; return;
} }
@ -3070,7 +3117,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
try { try {
let atts = JSON.parse(msg.attachment); let atts = JSON.parse(msg.attachment);
if (atts.length > 0) { if (atts.length > 0) {
attachmentsHtml += `<div class="chat-message-attachments" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;">`; attachmentsHtml += `<div class="chat-message-attachments" data-raw="${escapeHtml(msg.attachment)}" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;">`;
for (let att of atts) { for (let att of atts) {
if (att.enc && att.enc.startsWith("ENC:")) { if (att.enc && att.enc.startsWith("ENC:")) {
try { try {
@ -3116,7 +3163,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
console.error("Failed to parse attachments", e); console.error("Failed to parse attachments", e);
} }
} else { } else {
attachmentsHtml += `<div class="chat-message-attachments" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;"> attachmentsHtml += `<div class="chat-message-attachments" data-raw="${escapeHtml(msg.attachment)}" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;">
<div style="padding: 0.5rem; background: rgba(0,0,0,0.2); border: var(--border-width) solid var(--border-color); border-radius: 0.5rem; font-size: 0.8rem; opacity: 0.7;">[${processBlah("attachment.legacy")}: ${escapeHtml(msg.attachment)}]</div> <div style="padding: 0.5rem; background: rgba(0,0,0,0.2); border: var(--border-width) solid var(--border-color); border-radius: 0.5rem; font-size: 0.8rem; opacity: 0.7;">[${processBlah("attachment.legacy")}: ${escapeHtml(msg.attachment)}]</div>
</div>`; </div>`;
} }
@ -3133,7 +3180,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
content = processBlah(content); content = processBlah(content);
if (msg.type && msg.type.startsWith("larp.info")) { if (msg.type && msg.type.startsWith("larp.info")) {
if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create" || msg.type === "larp.info.history") { if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create" || msg.type === "larp.info.history" || msg.type === "larp.info.kick" || msg.type === "larp.info.transfer_owner") {
let authorName = processBlah("title.unknown"); let authorName = processBlah("title.unknown");
if (msg.author && msg.author !== "0") { if (msg.author && msg.author !== "0") {
if (!dmUsernameCache[msg.author]) { if (!dmUsernameCache[msg.author]) {
@ -3157,6 +3204,8 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
else if (msg.type === "larp.info.leave") actionStr = "group.user.left.by"; else if (msg.type === "larp.info.leave") actionStr = "group.user.left.by";
else if (msg.type === "larp.info.create") actionStr = "group.created.by"; else if (msg.type === "larp.info.create") actionStr = "group.created.by";
else if (msg.type === "larp.info.history") actionStr = "group.history.changed.by"; else if (msg.type === "larp.info.history") actionStr = "group.history.changed.by";
else if (msg.type === "larp.info.kick") actionStr = "group.user.kicked.by";
else if (msg.type === "larp.info.transfer_owner") actionStr = "group.owner.transferred.by";
let clickableAuthor = `<span onclick="event.stopPropagation(); openProfile('${msg.author}')" style="cursor: pointer; font-weight: bold;">${authorName}</span>`; let clickableAuthor = `<span onclick="event.stopPropagation(); openProfile('${msg.author}')" style="cursor: pointer; font-weight: bold;">${authorName}</span>`;
@ -3166,6 +3215,18 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg); content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg);
} else if (msg.type === "larp.info.history") { } else if (msg.type === "larp.info.history") {
content = textStr.replace("{0}", clickableAuthor).replace("{1}", content); content = textStr.replace("{0}", clickableAuthor).replace("{1}", content);
} else if (msg.type === "larp.info.kick" || msg.type === "larp.info.transfer_owner") {
let targetUsername = "";
let targetId = "";
let parts = content.split(':');
if (parts.length >= 2 && parts[0].includes(';')) {
targetId = parts[0];
targetUsername = escapeHtml(parts.slice(1).join(':'));
} else {
targetUsername = escapeHtml(parts[0]);
}
let clickableTarget = targetId ? `<span onclick="event.stopPropagation(); openProfile('${targetId}')" style="font-weight: bold; cursor: pointer;">${targetUsername}</span>` : `<span style="font-weight: bold;">${targetUsername}</span>`;
content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableTarget);
} else { } else {
content = textStr.replace("{0}", clickableAuthor); content = textStr.replace("{0}", clickableAuthor);
} }
@ -3424,6 +3485,20 @@ async function renderMessages(messages, isPrepend = false) {
oldReactions.remove(); oldReactions.remove();
} }
let oldAttachments = existingChild.querySelector('.chat-message-attachments');
let newAttachments = newChild.querySelector('.chat-message-attachments');
if (newAttachments) {
if (oldAttachments) {
if (oldAttachments.getAttribute('data-raw') !== newAttachments.getAttribute('data-raw')) oldAttachments.innerHTML = newAttachments.innerHTML;
} else {
let contentNode = existingChild.querySelector('.chat-message-content');
if (oldReactions) contentNode.insertBefore(newAttachments, oldReactions);
else contentNode.appendChild(newAttachments);
}
} else if (oldAttachments) {
oldAttachments.remove();
}
let oldPfp = existingChild.querySelector('.chat-message-pfp'); let oldPfp = existingChild.querySelector('.chat-message-pfp');
let newPfp = newChild.querySelector('.chat-message-pfp'); let newPfp = newChild.querySelector('.chat-message-pfp');
if (oldPfp && newPfp && oldPfp.src !== newPfp.src) { if (oldPfp && newPfp && oldPfp.src !== newPfp.src) {
@ -3920,7 +3995,6 @@ let touchContextMenuFired = false;
function handleMessageContextMenu(e, msgId) { function handleMessageContextMenu(e, msgId) {
e.preventDefault(); e.preventDefault();
if (touchContextMenuFired) { if (touchContextMenuFired) {
touchContextMenuFired = false;
return; return;
} }
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
@ -5013,6 +5087,96 @@ if (typeof roomTopBar !== 'undefined' && roomTopBar) {
var currentContextMenuGroupId = null; var currentContextMenuGroupId = null;
function handleMemberContextMenu(e, groupId, memberId) {
if (window.currentRoomType !== 'groups') return;
e.preventDefault();
e.stopPropagation();
if (e && e.type === "contextmenu") {
touchContextMenuFired = true;
setTimeout(() => { touchContextMenuFired = false; }, 500);
}
let html = '';
let myIdStr = window.id + ";" + window.host;
if (window.currentGroupOwner === myIdStr && memberId !== myIdStr) {
html += `
<button onclick="kickFromGroup('${groupId}', '${memberId}')">
${ICONS.delete({ fill: "var(--big-red)" })}
<span class="blah" style="color: var(--big-red)">action.kick</span>
</button>
<button onclick="transferGroupOwnership('${groupId}', '${memberId}')">
${ICONS.profile()}
<span class="blah">action.transfer_owner</span>
</button>
`;
}
if (html !== '') {
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}, html);
}
}
async function kickFromGroup(groupId, memberId) {
if (fixedContextMenu.classList.contains("show")) {
fixedContextMenu.classList.remove("show");
}
showAction("action.processing", "kick.action");
let payload = JSON.stringify({
string1: groupId,
string2: memberId
});
let res = await fetchEncrypted("group/kick", payload);
clearAction("kick.action");
if (res && res.startsWith("success")) {
showBlahNotification("success:kicked");
showGroupMembers(groupId).then(html => {
let container = document.getElementById("right-sidebar-container");
if (container) {
container.innerHTML = html;
translate(container);
}
});
} else {
showBlahNotification(res || "error:something.wrong");
}
}
async function transferGroupOwnership(groupId, memberId) {
if (fixedContextMenu.classList.contains("show")) {
fixedContextMenu.classList.remove("show");
}
showAction("action.processing", "transfer.action");
let payload = JSON.stringify({
string1: groupId,
string2: memberId
});
let res = await fetchEncrypted("group/transferowner", payload);
clearAction("transfer.action");
if (res && res.startsWith("success")) {
window.currentGroupOwner = memberId;
showBlahNotification("success:owner.transferred");
showGroupMembers(groupId).then(html => {
let container = document.getElementById("right-sidebar-container");
if (container) {
container.innerHTML = html;
translate(container);
}
});
} else {
showBlahNotification(res || "error:something.wrong");
}
}
function handleGroupContextMenu(e, groupId) { function handleGroupContextMenu(e, groupId) {
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
@ -5020,6 +5184,10 @@ function handleGroupContextMenu(e, groupId) {
} }
if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); if (typeof clearContextMenuStyles === "function") clearContextMenuStyles();
currentContextMenuGroupId = groupId; currentContextMenuGroupId = groupId;
if (e && e.type === "contextmenu") {
touchContextMenuFired = true;
setTimeout(() => { touchContextMenuFired = false; }, 500);
}
let btn = e ? (e.currentTarget || e.target) : null; let btn = e ? (e.currentTarget || e.target) : null;
let rect; let rect;
@ -5062,6 +5230,8 @@ async function gotoGroupSettings(groupId) {
let infoStr = await fetchEncrypted("group/info", groupId); let infoStr = await fetchEncrypted("group/info", groupId);
if (!infoStr.startsWith("error:")) { if (!infoStr.startsWith("error:")) {
let info = JSON.parse(infoStr); let info = JSON.parse(infoStr);
window.currentGroupOwner = info.string7;
await switchRoomContent("title.group.settings", groupSettingsScreen, false); await switchRoomContent("title.group.settings", groupSettingsScreen, false);
document.getElementById('group-settings-name').value = info.string2; document.getElementById('group-settings-name').value = info.string2;
@ -5070,6 +5240,19 @@ async function gotoGroupSettings(groupId) {
document.getElementById('group-settings-avatar-preview').src = pfpUrl; document.getElementById('group-settings-avatar-preview').src = pfpUrl;
document.getElementById('group-settings-history').checked = (info.string6 === "true"); document.getElementById('group-settings-history').checked = (info.string6 === "true");
let myIdStr = window.id + ";" + window.host;
let historyCheckbox = document.getElementById('group-settings-history');
let historyContainer = historyCheckbox.closest('.setting-row');
if (historyContainer) {
if (window.currentGroupOwner !== myIdStr) {
historyContainer.style.display = 'none';
} else {
historyContainer.style.display = 'flex';
}
} else {
historyCheckbox.disabled = (window.currentGroupOwner !== myIdStr);
}
} else { } else {
showBlahNotification(infoStr); showBlahNotification(infoStr);
} }
@ -5120,7 +5303,13 @@ async function confirmLeaveGroup(groupId) {
let res = await fetchEncrypted("group/leave", payload); let res = await fetchEncrypted("group/leave", payload);
if (res === "success") { if (res === "success") {
if (currentDmId === groupId) { if (currentDmId === groupId) {
gotoHome(); currentDmId = null;
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
if (window.innerWidth <= 55 * rem) {
mobileNavBack();
} else {
gotoHome();
}
} }
refreshGroups(0, false); refreshGroups(0, false);
} else if (res.startsWith("error:")) { } else if (res.startsWith("error:")) {