diff --git a/android/app/src/main/assets/public/blah/en-cat.json b/android/app/src/main/assets/public/blah/en-cat.json index a5e5a89f..f06e9ea5 100644 --- a/android/app/src/main/assets/public/blah/en-cat.json +++ b/android/app/src/main/assets/public/blah/en-cat.json @@ -262,10 +262,5 @@ "action.leaving.group": "Leaving group, meow...", "action.key.rotating": "Rotating keys, meow...", "action.key.rotation": "Key rotation, meow", - "desc.leave.group.confirm": "Are you sure you want to leave this group, meow?", - "desc.key.rotation.you": "You will generate a new encryption key for this group, meow.", - "label.keys.to.encrypt": "keys to encrypt, meow", - "action.group.creating": "Creating group, meow...", - "title.group.settings": "Group settings, meow", - "title.leave.group": "Leave group, meow" + "desc.leave.group.confirm": "Are you sure you want to leave this group, meow?" } \ No newline at end of file diff --git a/android/app/src/main/assets/public/blah/en-us.json b/android/app/src/main/assets/public/blah/en-us.json index d9ccd071..effa6346 100644 --- a/android/app/src/main/assets/public/blah/en-us.json +++ b/android/app/src/main/assets/public/blah/en-us.json @@ -249,13 +249,6 @@ "group.user.joined": "Joined group", "prompt.invite.username": "Enter username to invite:", "desc.failed.load.members": "Failed to load members", - "title.group.settings": "Group Settings", - "title.group.name": "Group Name", - "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)", - "history.visible": "enabled", - "history.hidden": "disabled", - "group.history.changed.by": "{0} changed history visibility to: {1}", "prompt.are.you.sure": "Are you sure?", "title.unknown.group": "Unknown Group", "title.unknown": "Unknown", @@ -269,10 +262,5 @@ "action.leaving.group": "Leaving group...", "action.key.rotating": "Rotating keys...", "action.key.rotation": "Key rotation", - "desc.leave.group.confirm": "Are you sure you want to leave this group?", - "desc.key.rotation.you": "You will generate a new encryption key for this group.", - "label.keys.to.encrypt": "keys to encrypt", - "action.group.creating": "Creating group...", - "title.group.settings": "Group settings", - "title.leave.group": "Leave group" + "desc.leave.group.confirm": "Are you sure you want to leave this group?" } \ No newline at end of file diff --git a/android/app/src/main/assets/public/blah/pl-pl.json b/android/app/src/main/assets/public/blah/pl-pl.json index 1476621b..3a00a975 100644 --- a/android/app/src/main/assets/public/blah/pl-pl.json +++ b/android/app/src/main/assets/public/blah/pl-pl.json @@ -248,13 +248,6 @@ "group.user.joined": "Dołączono do grupy", "prompt.invite.username": "Podaj nazwę użytkownika, aby zaprosić:", "desc.failed.load.members": "Nie udało się załadować członków", - "title.group.settings": "Ustawienia grupy", - "title.group.name": "Nazwa grupy", - "title.group.history.visible": "Historia widoczna dla nowych", - "desc.group.history.visible": "Nowi członkowie grupy będą mogli zobaczyć starsze wiadomości (wyłącza rotację kluczy)", - "history.visible": "włączona", - "history.hidden": "wyłączona", - "group.history.changed.by": "{0} zmienił widoczność historii na: {1}", "prompt.are.you.sure": "Czy na pewno?", "title.unknown.group": "Nieznana grupa", "title.unknown": "Nieznany", @@ -269,10 +262,5 @@ "action.leaving.group": "Opuszczanie grupy...", "action.key.rotating": "Obracanie kluczy...", "action.key.rotation": "Rotacja kluczy", - "desc.leave.group.confirm": "Czy na pewno chcesz opuścić tę grupę?", - "desc.key.rotation.you": "Wygenerujesz nowy klucz szyfrujący dla tej grupy.", - "label.keys.to.encrypt": "kluczy do zaszyfrowania", - "action.group.creating": "Tworzenie grupy...", - "title.group.settings": "Ustawienia grupy", - "title.leave.group": "Opuść grupę" + "desc.leave.group.confirm": "Czy na pewno chcesz opuścić tę grupę?" } \ No newline at end of file diff --git a/android/app/src/main/assets/public/icons.js b/android/app/src/main/assets/public/icons.js index ae3b562c..06596908 100644 --- a/android/app/src/main/assets/public/icons.js +++ b/android/app/src/main/assets/public/icons.js @@ -30,10 +30,11 @@ const ICONS = { pin: (props) => ICONS._material(``, props), call: (props) => ICONS._material(``, props), account: (props) => ICONS._material(``, props), + settings: (props) => ICONS._material(``, props), profile: (props) => ICONS._material(``, props), replyAction: (props) => ICONS._material(``, props), react: (props) => ICONS._material(``, props), - settings: (props) => ICONS._material(``, props), + settingsProfile: (props) => ICONS._material(``, props), download: (props) => ICONS._feather(``, props), camera: (props) => ICONS._feather(``, props), diff --git a/android/app/src/main/assets/public/index.html b/android/app/src/main/assets/public/index.html index ace3372d..9156be42 100644 --- a/android/app/src/main/assets/public/index.html +++ b/android/app/src/main/assets/public/index.html @@ -256,12 +256,9 @@ const roomKey = randomRoomKey256Chars(); const selfWrapped = await wrapRoomKeyForSelf(roomKey); - let historyVisible = document.getElementById("creategroup-history") ? document.getElementById("creategroup-history").checked.toString() : "false"; - let payload = JSON.stringify({ string1: groupName, - string2: selfWrapped, - string3: historyVisible + string2: selfWrapped }); let res = await fetchEncrypted("group/create", payload); diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js index 3cd1e385..7a90b060 100644 --- a/android/app/src/main/assets/public/main.js +++ b/android/app/src/main/assets/public/main.js @@ -432,7 +432,7 @@ function showConfirmPopup(titleKey, descHtml, confirmActionKey, cancelActionKey, let popupContainer = document.getElementById("generic-popup-container"); let popupInner = document.getElementById("generic-popup-inner"); - let confirmClass = isDestructive ? "popup-btn-danger" : ""; + let confirmClass = isDestructive ? "popup-btn-danger" : "popup-btn-confirm"; genericPopupCancelCallback = onCancel; @@ -449,10 +449,10 @@ function showConfirmPopup(titleKey, descHtml, confirmActionKey, cancelActionKey, ${descHtml}
- -
@@ -474,7 +474,7 @@ function showPromptPopup(titleKey, descHtml, placeholderKey, confirmActionKey, c let popupContainer = document.getElementById("generic-popup-container"); let popupInner = document.getElementById("generic-popup-inner"); - let confirmClass = isDestructive ? "popup-btn-danger" : ""; + let confirmClass = isDestructive ? "popup-btn-danger" : "popup-btn-confirm"; genericPopupCancelCallback = onCancel; @@ -491,15 +491,12 @@ function showPromptPopup(titleKey, descHtml, placeholderKey, confirmActionKey, c
${descHtml}
-
- - -
+
- -
@@ -534,7 +531,7 @@ function closeRotationPopup() { async function showRotationPopup(groupId, originalMessagePayload) { pendingRotationMessagePayload = originalMessagePayload; - showAction("action.key.rotating", "key_rotation"); + showAction("action.key.rotation", "key_rotation"); try { let membersRaw = await fetchEncrypted("group/members", groupId); @@ -542,7 +539,7 @@ async function showRotationPopup(groupId, originalMessagePayload) { let members = JSON.parse(membersRaw); - let descHtml = `desc.key.rotation.you

${members.length} label.keys.to.encrypt`; + let descHtml = `desc.key.rotation.info

${members.length} label.members`; showConfirmPopup( "title.key.rotation", @@ -560,7 +557,6 @@ async function showRotationPopup(groupId, originalMessagePayload) { async function executeKeyRotation() { closeRotationPopup(); - clearAction("key_rotation"); showAction("action.key.rotating", "key_rotation_exec"); try { @@ -617,12 +613,29 @@ async function executeKeyRotation() { clearAction("key_rotation_exec"); showBlahNotification("success:key.rotated"); + // Retry sending the message! + if(pendingRotationMessagePayload) { + let p = pendingRotationMessagePayload; + p.string5 = newIndex; + let dmKeyBytes = await sha256Bytes(base64ToUint8(roomKey)); + p.string2 = await encryptAesGcmToBase64(p.rawPlainContent, dmKeyBytes); // we need to save rawPlainContent for retry + + showAction("action.message.sending", "msgsending"); + let resSend = await fetchEncrypted("group/message/send", JSON.stringify(p)); + clearAction("msgsending"); + + if(resSend && resSend.startsWith("error:")) { + showBlahNotification(resSend); + } else if(resSend && resSend.startsWith("success:")) { + loadDmMessages(currentDmId); + } + } pendingRotationMessagePayload = null; } catch(e) { clearAction("key_rotation_exec"); - showBlahNotification(e.message || e); - pendingRotationMessagePayload = null; + console.error(e); + showBlahNotification(e.message && e.message.startsWith("error:") ? e.message : "error:something.wrong"); } } @@ -2128,7 +2141,7 @@ async function renderInvites(res, type) { let actions = ""; let desc = ""; - let displayName = username || onlyId; + let displayName = username.split(':')[0] || onlyId; if (type === "received") { desc = `:desc.invite.dm.received:${displayName};${onlyId}`; actions = ` @@ -2182,7 +2195,7 @@ async function renderInvites(res, type) { let actions = ""; let desc = ""; - let displayName = username || onlyId; + let displayName = username.split(':')[0] || onlyId; let val1 = `${displayName}`; let val2 = `${onlyId}`; @@ -2386,13 +2399,6 @@ function gotoJoinSpace() { } async function gotoHome(splash = true) { - if (dmMessagePollInterval) { - clearInterval(dmMessagePollInterval); - dmMessagePollInterval = null; - } - currentDmId = null; - window.currentRoomType = null; - if (roomsBarContainer) roomsBarContainer.style.display = ""; //show roombar setActiveSidebarIndicator(sidebarHomeIndicator); if (splash) { @@ -2585,8 +2591,6 @@ async function renderDms(res) { let safeTargetUsername = encodeURIComponent(targetUsername).replace(/'/g, "%27"); - let unreadBadge = `
`; - html += ` `; count++; @@ -2648,8 +2651,6 @@ async function renderGroups(res) { let safeGroupName = encodeURIComponent(groupName).replace(/'/g, "%27"); let pfp = await getGroupAvatarUrl(groupId, groupName); - let unreadBadge = `
`; - html += ` `; count++; @@ -2742,7 +2742,7 @@ async function inviteToGroupPrompt(groupId) { showPromptPopup( "action.invite", "", - "title.username", + "prompt.invite.username", "action.invite", "action.cancel", async (targetUsername) => { @@ -2762,63 +2762,9 @@ async function inviteToGroupPrompt(groupId) { try { showAction("action.invite.sending", "invite.action"); - let keysPayload = ""; - try { - let infoRes = await fetchEncrypted("group/info", groupId); - if (infoRes && !infoRes.startsWith("error:")) { - let info = JSON.parse(infoRes); - if (info.string6 === "true") { // historyVisible - let lastKeyIndex = parseInt(info.string4); - if (!isNaN(lastKeyIndex)) { - let publicKeysRaw = await fetchAsync(`${url}/user/key/getpublic?id=${targetId}`); - if (publicKeysRaw && publicKeysRaw.length > 10) { - let publicKeys = JSON.parse(publicKeysRaw); - let hybrid = await deriveHybridKeyToUser(publicKeys); - let myPublicKeysRaw = await fetchAsync(`${url}/user/key/getpublic?id=${window.id}`); - let keysDict = {}; - let allKeysSuccess = true; - - for (let i = 0; i <= lastKeyIndex; i++) { - let roomKey = await getGroupKey(groupId, i.toString()); - if (!roomKey) { - allKeysSuccess = false; - break; - } - let encForMember = await encryptAesGcmToBase64(roomKey, hybrid.aesKeyBytes); - let setupPayloadJson = JSON.stringify({ - v: 1, - alg: "hybrid-x25519-mlkem768", - ctMlKem: hybrid.ciphertextMlKemBase64, - enc: encForMember - }); - keysDict[i.toString()] = `SETUP:${myPublicKeysRaw};${setupPayloadJson}`; - } - - if (!allKeysSuccess) { - clearAction("invite.action"); - showBlahNotification("error:failed.to.prepare.keys"); - return; - } - keysPayload = JSON.stringify(keysDict); - } else { - clearAction("invite.action"); - showBlahNotification("error:failed.to.prepare.keys"); - return; - } - } - } - } - } catch (e) { - console.error("Error generating keys payload for invite", e); - clearAction("invite.action"); - showBlahNotification("error:failed.to.prepare.keys"); - return; - } - let payload = JSON.stringify({ string1: groupId, - string2: targetId, - string3: keysPayload + string2: targetId }); let res = await fetchEncrypted("group/invite", payload); @@ -2911,10 +2857,10 @@ async function openDm(dmId, username, targetId, type = 'dms') { let iconHtml = ""; if (type === 'dms') { let pfp = await getAvatarUrl(targetId, username); - iconHtml = ``; + iconHtml = ``; } else { let pfp = await getGroupAvatarUrl(dmId, username); - iconHtml = ``; + iconHtml = ``; } if (currentRoomsBarTitle !== "title.home") { @@ -3072,27 +3018,8 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { if (atts.length > 0) { attachmentsHtml += `
`; for (let att of atts) { - if (att.enc && att.enc.startsWith("ENC:")) { - try { - if (keyToUse) { - let keyBytes = await sha256Bytes(base64ToUint8(keyToUse)); - let decStr = await decryptAesGcmFromBase64(att.enc.substring(4), keyBytes); - let metadata = JSON.parse(decStr); - att.name = metadata.name || "unknown"; - att.type = metadata.type || "application/octet-stream"; - att.size = metadata.size || 0; - } else { - att.name = processBlah("error:messages.decrypt.failed"); - att.type = "application/octet-stream"; - } - } catch(e) { - att.name = processBlah("error:messages.decrypt.failed"); - att.type = "application/octet-stream"; - } - } - let attContainerId = `att-container-${msgId}-${att.id}`; - let isMedia = att.type && (att.type.startsWith("image/") || att.type.startsWith("video/")); + let isMedia = att.type.startsWith("image/") || att.type.startsWith("video/"); let sizeStr = att.size ? formatBytes(att.size) : ""; let displayName = truncateFilename(att.name, 25); let iconSvg = isMedia @@ -3133,7 +3060,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { content = processBlah(content); 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") { let authorName = processBlah("title.unknown"); if (msg.author && msg.author !== "0") { if (!dmUsernameCache[msg.author]) { @@ -3156,7 +3083,6 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { else if (msg.type === "larp.info.join") actionStr = "group.user.joined.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.history") actionStr = "group.history.changed.by"; let clickableAuthor = `${authorName}`; @@ -3164,8 +3090,6 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { if (msg.type.endsWith(".pin") || msg.type.endsWith(".unpin")) { let clickableMsg = `${processBlah('title.message.lowercase')}`; content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg); - } else if (msg.type === "larp.info.history") { - content = textStr.replace("{0}", clickableAuthor).replace("{1}", content); } else { content = textStr.replace("{0}", clickableAuthor); } @@ -3597,10 +3521,7 @@ async function downloadAndRenderAttachment(container, msgId, attId, type, name, } } catch (e) { console.error(e); - if (window.currentRoomType === 'groups' && window.groupKeysCache[currentDmId]) { - delete window.groupKeysCache[currentDmId][keyIndex]; - } - container.innerHTML = `
Error loading - click to retry
`; + container.innerHTML = `
Error loading
`; container.dataset.loading = "false"; } } @@ -3727,17 +3648,12 @@ async function sendMessage(sendbutton = document.getElementById("chat-sendmessag } if (decText && decText.startsWith("success:")) { - let metadataObj = { - type: file.type, - name: file.name, - size: file.size - }; - let encMetadata = "ENC:" + await encryptAesGcmToBase64(JSON.stringify(metadataObj), dmKeyBytes); - attachmentsPayload.push({ id: i, - staging_id: decText.split(":")[1], - enc: encMetadata + type: file.type, + name: file.name, + size: file.size, + staging_id: decText.split(":")[1] }); } else { throw new Error(decText && decText.startsWith("error:") ? decText : "error:message.send.failed"); @@ -3755,11 +3671,9 @@ async function sendMessage(sendbutton = document.getElementById("chat-sendmessag if (window.currentRoomType === 'groups') { msgPayload.string5 = window.currentGroupKeyIndex || "0"; + msgPayload.rawPlainContent = content; // for retry } - let savedContentForRestore = content; - let savedAttachmentsForRestore = [...currentDraftAttachments]; - input.value = ""; input.style.height = '2.5rem'; sendbutton.style.height = '2.5rem'; @@ -3779,13 +3693,7 @@ async function sendMessage(sendbutton = document.getElementById("chat-sendmessag if (res && res.startsWith("error:key.rotation.required") && window.currentRoomType === 'groups') { let pElem = document.getElementById(pendingMsgId); if (pElem) pElem.remove(); - - // Restore input and attachments for the user to retry manually - input.value = savedContentForRestore; - currentDraftAttachments = savedAttachmentsForRestore; - renderAttachmentsPreview(); - - showRotationPopup(currentDmId, null); + showRotationPopup(currentDmId, msgPayload); } else if (res && res.startsWith("error:")) { showBlahNotification(res); let pElem = document.getElementById(pendingMsgId); @@ -4374,26 +4282,17 @@ async function changePassword() { } async function deleteAccount() { - showConfirmPopup( - "title.delete.account", - "desc.delete.account

prompt.are.you.sure", - "action.delete.account", - "action.cancel", - async () => { - showAction("action.account.deleting", "accountdel"); - let res = await fetchPostEnc(`${url}/deleteaccount?id=${window.id}`, ""); - clearAction("accountdel"); - let text = res; - if (text.startsWith("success:")) { - alert(processBlah(text)); - logout(); - } else { - showBlahNotification(text); - } - }, - null, - true - ); + if (!confirm(processBlah("desc.delete.account") + "\n\n" + processBlah("prompt.are.you.sure"))) return; + showAction("action.account.deleting", "accountdel"); + let res = await fetchPostEnc(`${url}/deleteaccount?id=${window.id}`, ""); + clearAction("accountdel"); + let text = res; + if (text.startsWith("success:")) { + alert(processBlah(text)); + logout(); + } else { + showBlahNotification(text); + } } async function saveProfile() { @@ -4450,8 +4349,8 @@ function initCropper() { let zoomSlider = document.getElementById('cropper-zoom'); // Set aspect ratio - let targetWidth = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 494; - let targetHeight = cropperType === 'avatar' || cropperType === 'group-avatar' ? targetWidth : 200; + let targetWidth = cropperType === 'avatar' ? 512 : 494; + let targetHeight = cropperType === 'avatar' ? targetWidth : 200; let aspectRatio = targetWidth / targetHeight; container.style.height = 'auto'; @@ -4519,8 +4418,8 @@ async function confirmCropper() { let zoom = parseFloat(document.getElementById('cropper-zoom').value); let container = document.getElementById('cropper-viewport-container'); - let targetWidth = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 988; - let targetHeight = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 400; + let targetWidth = cropperType === 'avatar' ? 512 : 988; + let targetHeight = cropperType === 'avatar' ? 512 : 400; let scaleRatio = targetWidth / container.clientWidth; let drawX = cropperPosX * scaleRatio; let drawY = cropperPosY * scaleRatio; @@ -4691,20 +4590,10 @@ async function confirmCropper() { newSrc = 'data:image/webp;base64,' + b64; } - let payloadKey = (cropperType === 'avatar' || cropperType === 'group-avatar') ? 'larp.profile.pfp' : 'larp.profile.banner'; + let payloadKey = cropperType === 'avatar' ? 'larp.profile.pfp' : 'larp.profile.banner'; + let fileBytes = base64ToUint8(b64); showAction("action.uploading", "uploadmedia"); - let res; - if (cropperType === 'group-avatar') { - let payload = JSON.stringify({ - string1: window.currentSettingsGroupId, - string2: payloadKey, - string3: b64 - }); - res = await fetchEncrypted("group/storage/public/update", payload); - } else { - let fileBytes = base64ToUint8(b64); - res = await fetchEncryptedUpload(payloadKey, fileBytes); - } + let res = await fetchEncryptedUpload(payloadKey, fileBytes); clearAction("uploadmedia"); if (res && res.startsWith("error:")) { showBlahNotification(res); @@ -4714,14 +4603,8 @@ async function confirmCropper() { // Update all matching images on the page instantly document.querySelectorAll('img').forEach(img => { - if (cropperType === 'group-avatar') { - if (img.src.includes(`id=${window.currentSettingsGroupId}&e=${payloadKey}`) || img.id === 'group-settings-avatar-preview') { - img.src = newSrc; - } - } else { - if (img.src.includes(`id=${window.id}&e=${payloadKey}`) || img.id === `settings-${cropperType}-preview` || (cropperType === 'avatar' && (img.id === 'my-avatar-img' || img.id === 'sidebar-pfp'))) { - img.src = newSrc; - } + if (img.src.includes(`id=${window.id}&e=${payloadKey}`) || img.id === `settings-${cropperType}-preview` || (cropperType === 'avatar' && (img.id === 'my-avatar-img' || img.id === 'sidebar-pfp'))) { + img.src = newSrc; } }); } @@ -4790,11 +4673,6 @@ function updateDmListUI(roomId, isRead, type = 'dms', timestamp = null) { if (nameSpan) { nameSpan.style.opacity = isRead ? "0.8" : "1"; } - let badge = btn.querySelector('.unread-badge'); - if (badge) { - if (isRead) badge.classList.remove("show"); - else badge.classList.add("show"); - } if (timestamp) { container.prepend(btn); } @@ -4827,11 +4705,6 @@ function updateDmListUI(roomId, isRead, type = 'dms', timestamp = null) { if (nameSpan) { nameSpan.style.opacity = isRead ? "0.8" : "1"; } - let badge = btn.querySelector('.unread-badge'); - if (badge) { - if (isRead) badge.classList.remove("show"); - else badge.classList.add("show"); - } if (timestamp) { container.prepend(btn); } @@ -5054,48 +4927,9 @@ function handleGroupContextMenu(e, groupId) { fixedContextMenu.style.top = `${newTop}px`; } -async function gotoGroupSettings(groupId) { +function gotoGroupSettings(groupId) { if (fixedContextMenu) fixedContextMenu.classList.remove("show"); - - window.currentSettingsGroupId = groupId; - - let infoStr = await fetchEncrypted("group/info", groupId); - if (!infoStr.startsWith("error:")) { - let info = JSON.parse(infoStr); - await switchRoomContent("title.group.settings", groupSettingsScreen, false); - - document.getElementById('group-settings-name').value = info.string2; - - let pfpUrl = await getGroupAvatarUrl(groupId, info.string2); - document.getElementById('group-settings-avatar-preview').src = pfpUrl; - - document.getElementById('group-settings-history').checked = (info.string6 === "true"); - } else { - showBlahNotification(infoStr); - } -} - -async function saveGroupSettings() { - let groupId = window.currentSettingsGroupId; - let name = document.getElementById('group-settings-name').value; - let historyVisible = document.getElementById('group-settings-history').checked.toString(); - - let payload = JSON.stringify({ - string1: groupId, - string2: name, - string3: historyVisible - }); - - showAction("action.saving", "savegroup"); - let res = await fetchEncrypted("group/settings/update", payload); - clearAction("savegroup"); - - if (res === "success") { - showBlahNotification("success:group.settings.saved"); - refreshGroups(0, false); - } else { - showBlahNotification(res); - } + // Not implemented yet } function leaveGroupPrompt(groupId) { diff --git a/android/app/src/main/assets/public/screens.js b/android/app/src/main/assets/public/screens.js index 02580348..8f1f7e04 100644 --- a/android/app/src/main/assets/public/screens.js +++ b/android/app/src/main/assets/public/screens.js @@ -31,18 +31,8 @@ var createGroupScreen = `
- -
-
- title.group.history.visible - desc.group.history.visible -
- -
- +
+
@@ -221,7 +211,7 @@ var profileContextMenu = ` title.profile + `; @@ -378,36 +368,6 @@ var profileSettings = ` `; -var groupSettingsScreen = ` - -`; var appearanceSettings = `
diff --git a/android/app/src/main/assets/public/style.css b/android/app/src/main/assets/public/style.css index 94ec00ed..d9c8260e 100644 --- a/android/app/src/main/assets/public/style.css +++ b/android/app/src/main/assets/public/style.css @@ -366,7 +366,6 @@ herotitle { gap: 0.2rem; width: 90%; max-width: 30rem; - margin: 0 auto; } .input-group label { font-size: 0.75rem; @@ -879,27 +878,35 @@ sidebarelement:has(.icon-button:active) indicator:not(.active) { height: auto; max-height: 85vh; } -button.profile-action-btn.popup-action-btn { - flex: 1; - display: flex; - justify-content: center; - margin-top: 0; -} -button.profile-action-btn.popup-btn-cancel { +.popup-btn-cancel { background: rgba(255, 255, 255, 0.05); color: var(--text-color); border: var(--border-width) solid var(--light-border-color); + flex: 1; + justify-content: center; } -button.profile-action-btn.popup-btn-cancel:hover { +.popup-btn-cancel:hover { background: rgba(255, 255, 255, 0.1); } -button.profile-action-btn.popup-btn-danger, button.submit-button.popup-btn-danger { - background: var(--big-red) !important; - color: white !important; +.popup-btn-confirm { + background: var(--text-color); + color: var(--main-bg-color); border: none; + flex: 1; + justify-content: center; } -button.profile-action-btn.popup-btn-danger:hover, button.submit-button.popup-btn-danger:hover { - filter: brightness(0.85); +.popup-btn-confirm:hover { + background: white; +} +.popup-btn-danger { + background: var(--big-red); + color: white; + border: none; + flex: 1; + justify-content: center; +} +.popup-btn-danger:hover { + background: #ff5252; } .profile-popup-container.show .profile-popup-content { transform: scale(1); @@ -1065,106 +1072,3 @@ body.hide-topbar-pin #roomdetailsbar roomtopbar .room-action-pin { display: flex body.hide-topbar-call roomcontent roomtopbar .room-action-call { display: none !important; } body.hide-topbar-call #roomdetailsbar roomtopbar .room-action-call { display: flex !important; } - -/* Toggle Switch */ -.toggle-switch { - position: relative; - display: inline-block; - width: 3.4rem; - height: 1.8rem; - margin: var(--button-margin); - flex-shrink: 0; - -} -label.toggle-switch { - opacity: 0.8; -} -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} -.toggle-slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(255, 255, 255, 0.1); - transition: .4s; - border-radius: 1.8rem; - border: var(--border-width) solid var(--light-border-color); -} -.toggle-slider:before { - position: absolute; - content: ""; - height: 1.2rem; - width: 1.2rem; - left: 0.25rem; - top: 50%; - transform: translateY(-50%); - background-color: var(--text-color); - transition: .4s; - border-radius: 50%; -} -input:checked + .toggle-slider { - background-color: var(--text-color) !important; - border-color: var(--text-color) !important; - -} -input:checked + .toggle-slider:before { - transform: translate(1.6rem, -50%); - background-color: var(--main-bg-color) !important; -} -.setting-row { - display: flex; - justify-content: space-between; - align-items: center; - width: auto; - box-sizing: border-box; - margin: var(--button-margin); - padding: 0.5rem; - border-radius: 0.8rem; - background-color: rgba(255, 255, 255, 0.02); -} -.setting-row:hover { - background-color: rgba(255, 255, 255, 0.04); -} -.setting-info { - display: flex; - flex-direction: column; - justify-content: center; -} -.setting-title { - font-size: 1.1rem; - font-weight: bold; -} -.setting-desc { - font-size: 0.85rem; - color: var(--darker-text-color); - margin-top: 0.2rem; -} - -.unread-badge { - width: 0.65rem; - height: 0.65rem; - min-width: 0.65rem; - min-height: 0.65rem; - background-color: var(--big-red); - border-radius: 50%; - flex-shrink: 0; - margin-right: 0.5rem; - transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); - transform: scale(0); -} - -.unread-badge.show { - transform: scale(1); - animation: badge-appear 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); -} - -@keyframes badge-appear { - 0% { transform: scale(0); } - 100% { transform: scale(1); } -} diff --git a/webroot/blah/en-cat.json b/webroot/blah/en-cat.json index a5e5a89f..f06e9ea5 100644 --- a/webroot/blah/en-cat.json +++ b/webroot/blah/en-cat.json @@ -262,10 +262,5 @@ "action.leaving.group": "Leaving group, meow...", "action.key.rotating": "Rotating keys, meow...", "action.key.rotation": "Key rotation, meow", - "desc.leave.group.confirm": "Are you sure you want to leave this group, meow?", - "desc.key.rotation.you": "You will generate a new encryption key for this group, meow.", - "label.keys.to.encrypt": "keys to encrypt, meow", - "action.group.creating": "Creating group, meow...", - "title.group.settings": "Group settings, meow", - "title.leave.group": "Leave group, meow" + "desc.leave.group.confirm": "Are you sure you want to leave this group, meow?" } \ No newline at end of file diff --git a/webroot/blah/en-us.json b/webroot/blah/en-us.json index d9ccd071..effa6346 100644 --- a/webroot/blah/en-us.json +++ b/webroot/blah/en-us.json @@ -249,13 +249,6 @@ "group.user.joined": "Joined group", "prompt.invite.username": "Enter username to invite:", "desc.failed.load.members": "Failed to load members", - "title.group.settings": "Group Settings", - "title.group.name": "Group Name", - "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)", - "history.visible": "enabled", - "history.hidden": "disabled", - "group.history.changed.by": "{0} changed history visibility to: {1}", "prompt.are.you.sure": "Are you sure?", "title.unknown.group": "Unknown Group", "title.unknown": "Unknown", @@ -269,10 +262,5 @@ "action.leaving.group": "Leaving group...", "action.key.rotating": "Rotating keys...", "action.key.rotation": "Key rotation", - "desc.leave.group.confirm": "Are you sure you want to leave this group?", - "desc.key.rotation.you": "You will generate a new encryption key for this group.", - "label.keys.to.encrypt": "keys to encrypt", - "action.group.creating": "Creating group...", - "title.group.settings": "Group settings", - "title.leave.group": "Leave group" + "desc.leave.group.confirm": "Are you sure you want to leave this group?" } \ No newline at end of file diff --git a/webroot/blah/pl-pl.json b/webroot/blah/pl-pl.json index 1476621b..3a00a975 100644 --- a/webroot/blah/pl-pl.json +++ b/webroot/blah/pl-pl.json @@ -248,13 +248,6 @@ "group.user.joined": "Dołączono do grupy", "prompt.invite.username": "Podaj nazwę użytkownika, aby zaprosić:", "desc.failed.load.members": "Nie udało się załadować członków", - "title.group.settings": "Ustawienia grupy", - "title.group.name": "Nazwa grupy", - "title.group.history.visible": "Historia widoczna dla nowych", - "desc.group.history.visible": "Nowi członkowie grupy będą mogli zobaczyć starsze wiadomości (wyłącza rotację kluczy)", - "history.visible": "włączona", - "history.hidden": "wyłączona", - "group.history.changed.by": "{0} zmienił widoczność historii na: {1}", "prompt.are.you.sure": "Czy na pewno?", "title.unknown.group": "Nieznana grupa", "title.unknown": "Nieznany", @@ -269,10 +262,5 @@ "action.leaving.group": "Opuszczanie grupy...", "action.key.rotating": "Obracanie kluczy...", "action.key.rotation": "Rotacja kluczy", - "desc.leave.group.confirm": "Czy na pewno chcesz opuścić tę grupę?", - "desc.key.rotation.you": "Wygenerujesz nowy klucz szyfrujący dla tej grupy.", - "label.keys.to.encrypt": "kluczy do zaszyfrowania", - "action.group.creating": "Tworzenie grupy...", - "title.group.settings": "Ustawienia grupy", - "title.leave.group": "Opuść grupę" + "desc.leave.group.confirm": "Czy na pewno chcesz opuścić tę grupę?" } \ No newline at end of file diff --git a/webroot/icons.js b/webroot/icons.js index ae3b562c..06596908 100644 --- a/webroot/icons.js +++ b/webroot/icons.js @@ -30,10 +30,11 @@ const ICONS = { pin: (props) => ICONS._material(``, props), call: (props) => ICONS._material(``, props), account: (props) => ICONS._material(``, props), + settings: (props) => ICONS._material(``, props), profile: (props) => ICONS._material(``, props), replyAction: (props) => ICONS._material(``, props), react: (props) => ICONS._material(``, props), - settings: (props) => ICONS._material(``, props), + settingsProfile: (props) => ICONS._material(``, props), download: (props) => ICONS._feather(``, props), camera: (props) => ICONS._feather(``, props), diff --git a/webroot/index.html b/webroot/index.html index ace3372d..9156be42 100644 --- a/webroot/index.html +++ b/webroot/index.html @@ -256,12 +256,9 @@ const roomKey = randomRoomKey256Chars(); const selfWrapped = await wrapRoomKeyForSelf(roomKey); - let historyVisible = document.getElementById("creategroup-history") ? document.getElementById("creategroup-history").checked.toString() : "false"; - let payload = JSON.stringify({ string1: groupName, - string2: selfWrapped, - string3: historyVisible + string2: selfWrapped }); let res = await fetchEncrypted("group/create", payload); diff --git a/webroot/main.js b/webroot/main.js index 3cd1e385..7a90b060 100644 --- a/webroot/main.js +++ b/webroot/main.js @@ -432,7 +432,7 @@ function showConfirmPopup(titleKey, descHtml, confirmActionKey, cancelActionKey, let popupContainer = document.getElementById("generic-popup-container"); let popupInner = document.getElementById("generic-popup-inner"); - let confirmClass = isDestructive ? "popup-btn-danger" : ""; + let confirmClass = isDestructive ? "popup-btn-danger" : "popup-btn-confirm"; genericPopupCancelCallback = onCancel; @@ -449,10 +449,10 @@ function showConfirmPopup(titleKey, descHtml, confirmActionKey, cancelActionKey, ${descHtml}
- -
@@ -474,7 +474,7 @@ function showPromptPopup(titleKey, descHtml, placeholderKey, confirmActionKey, c let popupContainer = document.getElementById("generic-popup-container"); let popupInner = document.getElementById("generic-popup-inner"); - let confirmClass = isDestructive ? "popup-btn-danger" : ""; + let confirmClass = isDestructive ? "popup-btn-danger" : "popup-btn-confirm"; genericPopupCancelCallback = onCancel; @@ -491,15 +491,12 @@ function showPromptPopup(titleKey, descHtml, placeholderKey, confirmActionKey, c
${descHtml}
-
- - -
+
- -
@@ -534,7 +531,7 @@ function closeRotationPopup() { async function showRotationPopup(groupId, originalMessagePayload) { pendingRotationMessagePayload = originalMessagePayload; - showAction("action.key.rotating", "key_rotation"); + showAction("action.key.rotation", "key_rotation"); try { let membersRaw = await fetchEncrypted("group/members", groupId); @@ -542,7 +539,7 @@ async function showRotationPopup(groupId, originalMessagePayload) { let members = JSON.parse(membersRaw); - let descHtml = `desc.key.rotation.you

${members.length} label.keys.to.encrypt`; + let descHtml = `desc.key.rotation.info

${members.length} label.members`; showConfirmPopup( "title.key.rotation", @@ -560,7 +557,6 @@ async function showRotationPopup(groupId, originalMessagePayload) { async function executeKeyRotation() { closeRotationPopup(); - clearAction("key_rotation"); showAction("action.key.rotating", "key_rotation_exec"); try { @@ -617,12 +613,29 @@ async function executeKeyRotation() { clearAction("key_rotation_exec"); showBlahNotification("success:key.rotated"); + // Retry sending the message! + if(pendingRotationMessagePayload) { + let p = pendingRotationMessagePayload; + p.string5 = newIndex; + let dmKeyBytes = await sha256Bytes(base64ToUint8(roomKey)); + p.string2 = await encryptAesGcmToBase64(p.rawPlainContent, dmKeyBytes); // we need to save rawPlainContent for retry + + showAction("action.message.sending", "msgsending"); + let resSend = await fetchEncrypted("group/message/send", JSON.stringify(p)); + clearAction("msgsending"); + + if(resSend && resSend.startsWith("error:")) { + showBlahNotification(resSend); + } else if(resSend && resSend.startsWith("success:")) { + loadDmMessages(currentDmId); + } + } pendingRotationMessagePayload = null; } catch(e) { clearAction("key_rotation_exec"); - showBlahNotification(e.message || e); - pendingRotationMessagePayload = null; + console.error(e); + showBlahNotification(e.message && e.message.startsWith("error:") ? e.message : "error:something.wrong"); } } @@ -2128,7 +2141,7 @@ async function renderInvites(res, type) { let actions = ""; let desc = ""; - let displayName = username || onlyId; + let displayName = username.split(':')[0] || onlyId; if (type === "received") { desc = `:desc.invite.dm.received:${displayName};${onlyId}`; actions = ` @@ -2182,7 +2195,7 @@ async function renderInvites(res, type) { let actions = ""; let desc = ""; - let displayName = username || onlyId; + let displayName = username.split(':')[0] || onlyId; let val1 = `${displayName}`; let val2 = `${onlyId}`; @@ -2386,13 +2399,6 @@ function gotoJoinSpace() { } async function gotoHome(splash = true) { - if (dmMessagePollInterval) { - clearInterval(dmMessagePollInterval); - dmMessagePollInterval = null; - } - currentDmId = null; - window.currentRoomType = null; - if (roomsBarContainer) roomsBarContainer.style.display = ""; //show roombar setActiveSidebarIndicator(sidebarHomeIndicator); if (splash) { @@ -2585,8 +2591,6 @@ async function renderDms(res) { let safeTargetUsername = encodeURIComponent(targetUsername).replace(/'/g, "%27"); - let unreadBadge = `
`; - html += ` `; count++; @@ -2648,8 +2651,6 @@ async function renderGroups(res) { let safeGroupName = encodeURIComponent(groupName).replace(/'/g, "%27"); let pfp = await getGroupAvatarUrl(groupId, groupName); - let unreadBadge = `
`; - html += ` `; count++; @@ -2742,7 +2742,7 @@ async function inviteToGroupPrompt(groupId) { showPromptPopup( "action.invite", "", - "title.username", + "prompt.invite.username", "action.invite", "action.cancel", async (targetUsername) => { @@ -2762,63 +2762,9 @@ async function inviteToGroupPrompt(groupId) { try { showAction("action.invite.sending", "invite.action"); - let keysPayload = ""; - try { - let infoRes = await fetchEncrypted("group/info", groupId); - if (infoRes && !infoRes.startsWith("error:")) { - let info = JSON.parse(infoRes); - if (info.string6 === "true") { // historyVisible - let lastKeyIndex = parseInt(info.string4); - if (!isNaN(lastKeyIndex)) { - let publicKeysRaw = await fetchAsync(`${url}/user/key/getpublic?id=${targetId}`); - if (publicKeysRaw && publicKeysRaw.length > 10) { - let publicKeys = JSON.parse(publicKeysRaw); - let hybrid = await deriveHybridKeyToUser(publicKeys); - let myPublicKeysRaw = await fetchAsync(`${url}/user/key/getpublic?id=${window.id}`); - let keysDict = {}; - let allKeysSuccess = true; - - for (let i = 0; i <= lastKeyIndex; i++) { - let roomKey = await getGroupKey(groupId, i.toString()); - if (!roomKey) { - allKeysSuccess = false; - break; - } - let encForMember = await encryptAesGcmToBase64(roomKey, hybrid.aesKeyBytes); - let setupPayloadJson = JSON.stringify({ - v: 1, - alg: "hybrid-x25519-mlkem768", - ctMlKem: hybrid.ciphertextMlKemBase64, - enc: encForMember - }); - keysDict[i.toString()] = `SETUP:${myPublicKeysRaw};${setupPayloadJson}`; - } - - if (!allKeysSuccess) { - clearAction("invite.action"); - showBlahNotification("error:failed.to.prepare.keys"); - return; - } - keysPayload = JSON.stringify(keysDict); - } else { - clearAction("invite.action"); - showBlahNotification("error:failed.to.prepare.keys"); - return; - } - } - } - } - } catch (e) { - console.error("Error generating keys payload for invite", e); - clearAction("invite.action"); - showBlahNotification("error:failed.to.prepare.keys"); - return; - } - let payload = JSON.stringify({ string1: groupId, - string2: targetId, - string3: keysPayload + string2: targetId }); let res = await fetchEncrypted("group/invite", payload); @@ -2911,10 +2857,10 @@ async function openDm(dmId, username, targetId, type = 'dms') { let iconHtml = ""; if (type === 'dms') { let pfp = await getAvatarUrl(targetId, username); - iconHtml = ``; + iconHtml = ``; } else { let pfp = await getGroupAvatarUrl(dmId, username); - iconHtml = ``; + iconHtml = ``; } if (currentRoomsBarTitle !== "title.home") { @@ -3072,27 +3018,8 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { if (atts.length > 0) { attachmentsHtml += `
`; for (let att of atts) { - if (att.enc && att.enc.startsWith("ENC:")) { - try { - if (keyToUse) { - let keyBytes = await sha256Bytes(base64ToUint8(keyToUse)); - let decStr = await decryptAesGcmFromBase64(att.enc.substring(4), keyBytes); - let metadata = JSON.parse(decStr); - att.name = metadata.name || "unknown"; - att.type = metadata.type || "application/octet-stream"; - att.size = metadata.size || 0; - } else { - att.name = processBlah("error:messages.decrypt.failed"); - att.type = "application/octet-stream"; - } - } catch(e) { - att.name = processBlah("error:messages.decrypt.failed"); - att.type = "application/octet-stream"; - } - } - let attContainerId = `att-container-${msgId}-${att.id}`; - let isMedia = att.type && (att.type.startsWith("image/") || att.type.startsWith("video/")); + let isMedia = att.type.startsWith("image/") || att.type.startsWith("video/"); let sizeStr = att.size ? formatBytes(att.size) : ""; let displayName = truncateFilename(att.name, 25); let iconSvg = isMedia @@ -3133,7 +3060,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { content = processBlah(content); 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") { let authorName = processBlah("title.unknown"); if (msg.author && msg.author !== "0") { if (!dmUsernameCache[msg.author]) { @@ -3156,7 +3083,6 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { else if (msg.type === "larp.info.join") actionStr = "group.user.joined.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.history") actionStr = "group.history.changed.by"; let clickableAuthor = `${authorName}`; @@ -3164,8 +3090,6 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { if (msg.type.endsWith(".pin") || msg.type.endsWith(".unpin")) { let clickableMsg = `${processBlah('title.message.lowercase')}`; content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg); - } else if (msg.type === "larp.info.history") { - content = textStr.replace("{0}", clickableAuthor).replace("{1}", content); } else { content = textStr.replace("{0}", clickableAuthor); } @@ -3597,10 +3521,7 @@ async function downloadAndRenderAttachment(container, msgId, attId, type, name, } } catch (e) { console.error(e); - if (window.currentRoomType === 'groups' && window.groupKeysCache[currentDmId]) { - delete window.groupKeysCache[currentDmId][keyIndex]; - } - container.innerHTML = `
Error loading - click to retry
`; + container.innerHTML = `
Error loading
`; container.dataset.loading = "false"; } } @@ -3727,17 +3648,12 @@ async function sendMessage(sendbutton = document.getElementById("chat-sendmessag } if (decText && decText.startsWith("success:")) { - let metadataObj = { - type: file.type, - name: file.name, - size: file.size - }; - let encMetadata = "ENC:" + await encryptAesGcmToBase64(JSON.stringify(metadataObj), dmKeyBytes); - attachmentsPayload.push({ id: i, - staging_id: decText.split(":")[1], - enc: encMetadata + type: file.type, + name: file.name, + size: file.size, + staging_id: decText.split(":")[1] }); } else { throw new Error(decText && decText.startsWith("error:") ? decText : "error:message.send.failed"); @@ -3755,11 +3671,9 @@ async function sendMessage(sendbutton = document.getElementById("chat-sendmessag if (window.currentRoomType === 'groups') { msgPayload.string5 = window.currentGroupKeyIndex || "0"; + msgPayload.rawPlainContent = content; // for retry } - let savedContentForRestore = content; - let savedAttachmentsForRestore = [...currentDraftAttachments]; - input.value = ""; input.style.height = '2.5rem'; sendbutton.style.height = '2.5rem'; @@ -3779,13 +3693,7 @@ async function sendMessage(sendbutton = document.getElementById("chat-sendmessag if (res && res.startsWith("error:key.rotation.required") && window.currentRoomType === 'groups') { let pElem = document.getElementById(pendingMsgId); if (pElem) pElem.remove(); - - // Restore input and attachments for the user to retry manually - input.value = savedContentForRestore; - currentDraftAttachments = savedAttachmentsForRestore; - renderAttachmentsPreview(); - - showRotationPopup(currentDmId, null); + showRotationPopup(currentDmId, msgPayload); } else if (res && res.startsWith("error:")) { showBlahNotification(res); let pElem = document.getElementById(pendingMsgId); @@ -4374,26 +4282,17 @@ async function changePassword() { } async function deleteAccount() { - showConfirmPopup( - "title.delete.account", - "desc.delete.account

prompt.are.you.sure", - "action.delete.account", - "action.cancel", - async () => { - showAction("action.account.deleting", "accountdel"); - let res = await fetchPostEnc(`${url}/deleteaccount?id=${window.id}`, ""); - clearAction("accountdel"); - let text = res; - if (text.startsWith("success:")) { - alert(processBlah(text)); - logout(); - } else { - showBlahNotification(text); - } - }, - null, - true - ); + if (!confirm(processBlah("desc.delete.account") + "\n\n" + processBlah("prompt.are.you.sure"))) return; + showAction("action.account.deleting", "accountdel"); + let res = await fetchPostEnc(`${url}/deleteaccount?id=${window.id}`, ""); + clearAction("accountdel"); + let text = res; + if (text.startsWith("success:")) { + alert(processBlah(text)); + logout(); + } else { + showBlahNotification(text); + } } async function saveProfile() { @@ -4450,8 +4349,8 @@ function initCropper() { let zoomSlider = document.getElementById('cropper-zoom'); // Set aspect ratio - let targetWidth = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 494; - let targetHeight = cropperType === 'avatar' || cropperType === 'group-avatar' ? targetWidth : 200; + let targetWidth = cropperType === 'avatar' ? 512 : 494; + let targetHeight = cropperType === 'avatar' ? targetWidth : 200; let aspectRatio = targetWidth / targetHeight; container.style.height = 'auto'; @@ -4519,8 +4418,8 @@ async function confirmCropper() { let zoom = parseFloat(document.getElementById('cropper-zoom').value); let container = document.getElementById('cropper-viewport-container'); - let targetWidth = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 988; - let targetHeight = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 400; + let targetWidth = cropperType === 'avatar' ? 512 : 988; + let targetHeight = cropperType === 'avatar' ? 512 : 400; let scaleRatio = targetWidth / container.clientWidth; let drawX = cropperPosX * scaleRatio; let drawY = cropperPosY * scaleRatio; @@ -4691,20 +4590,10 @@ async function confirmCropper() { newSrc = 'data:image/webp;base64,' + b64; } - let payloadKey = (cropperType === 'avatar' || cropperType === 'group-avatar') ? 'larp.profile.pfp' : 'larp.profile.banner'; + let payloadKey = cropperType === 'avatar' ? 'larp.profile.pfp' : 'larp.profile.banner'; + let fileBytes = base64ToUint8(b64); showAction("action.uploading", "uploadmedia"); - let res; - if (cropperType === 'group-avatar') { - let payload = JSON.stringify({ - string1: window.currentSettingsGroupId, - string2: payloadKey, - string3: b64 - }); - res = await fetchEncrypted("group/storage/public/update", payload); - } else { - let fileBytes = base64ToUint8(b64); - res = await fetchEncryptedUpload(payloadKey, fileBytes); - } + let res = await fetchEncryptedUpload(payloadKey, fileBytes); clearAction("uploadmedia"); if (res && res.startsWith("error:")) { showBlahNotification(res); @@ -4714,14 +4603,8 @@ async function confirmCropper() { // Update all matching images on the page instantly document.querySelectorAll('img').forEach(img => { - if (cropperType === 'group-avatar') { - if (img.src.includes(`id=${window.currentSettingsGroupId}&e=${payloadKey}`) || img.id === 'group-settings-avatar-preview') { - img.src = newSrc; - } - } else { - if (img.src.includes(`id=${window.id}&e=${payloadKey}`) || img.id === `settings-${cropperType}-preview` || (cropperType === 'avatar' && (img.id === 'my-avatar-img' || img.id === 'sidebar-pfp'))) { - img.src = newSrc; - } + if (img.src.includes(`id=${window.id}&e=${payloadKey}`) || img.id === `settings-${cropperType}-preview` || (cropperType === 'avatar' && (img.id === 'my-avatar-img' || img.id === 'sidebar-pfp'))) { + img.src = newSrc; } }); } @@ -4790,11 +4673,6 @@ function updateDmListUI(roomId, isRead, type = 'dms', timestamp = null) { if (nameSpan) { nameSpan.style.opacity = isRead ? "0.8" : "1"; } - let badge = btn.querySelector('.unread-badge'); - if (badge) { - if (isRead) badge.classList.remove("show"); - else badge.classList.add("show"); - } if (timestamp) { container.prepend(btn); } @@ -4827,11 +4705,6 @@ function updateDmListUI(roomId, isRead, type = 'dms', timestamp = null) { if (nameSpan) { nameSpan.style.opacity = isRead ? "0.8" : "1"; } - let badge = btn.querySelector('.unread-badge'); - if (badge) { - if (isRead) badge.classList.remove("show"); - else badge.classList.add("show"); - } if (timestamp) { container.prepend(btn); } @@ -5054,48 +4927,9 @@ function handleGroupContextMenu(e, groupId) { fixedContextMenu.style.top = `${newTop}px`; } -async function gotoGroupSettings(groupId) { +function gotoGroupSettings(groupId) { if (fixedContextMenu) fixedContextMenu.classList.remove("show"); - - window.currentSettingsGroupId = groupId; - - let infoStr = await fetchEncrypted("group/info", groupId); - if (!infoStr.startsWith("error:")) { - let info = JSON.parse(infoStr); - await switchRoomContent("title.group.settings", groupSettingsScreen, false); - - document.getElementById('group-settings-name').value = info.string2; - - let pfpUrl = await getGroupAvatarUrl(groupId, info.string2); - document.getElementById('group-settings-avatar-preview').src = pfpUrl; - - document.getElementById('group-settings-history').checked = (info.string6 === "true"); - } else { - showBlahNotification(infoStr); - } -} - -async function saveGroupSettings() { - let groupId = window.currentSettingsGroupId; - let name = document.getElementById('group-settings-name').value; - let historyVisible = document.getElementById('group-settings-history').checked.toString(); - - let payload = JSON.stringify({ - string1: groupId, - string2: name, - string3: historyVisible - }); - - showAction("action.saving", "savegroup"); - let res = await fetchEncrypted("group/settings/update", payload); - clearAction("savegroup"); - - if (res === "success") { - showBlahNotification("success:group.settings.saved"); - refreshGroups(0, false); - } else { - showBlahNotification(res); - } + // Not implemented yet } function leaveGroupPrompt(groupId) { diff --git a/webroot/screens.js b/webroot/screens.js index 02580348..8f1f7e04 100644 --- a/webroot/screens.js +++ b/webroot/screens.js @@ -31,18 +31,8 @@ var createGroupScreen = `
- -
-
- title.group.history.visible - desc.group.history.visible -
- -
- +
+
@@ -221,7 +211,7 @@ var profileContextMenu = ` title.profile + `; @@ -378,36 +368,6 @@ var profileSettings = ` `; -var groupSettingsScreen = ` - -`; var appearanceSettings = `
diff --git a/webroot/style.css b/webroot/style.css index 94ec00ed..d9c8260e 100644 --- a/webroot/style.css +++ b/webroot/style.css @@ -366,7 +366,6 @@ herotitle { gap: 0.2rem; width: 90%; max-width: 30rem; - margin: 0 auto; } .input-group label { font-size: 0.75rem; @@ -879,27 +878,35 @@ sidebarelement:has(.icon-button:active) indicator:not(.active) { height: auto; max-height: 85vh; } -button.profile-action-btn.popup-action-btn { - flex: 1; - display: flex; - justify-content: center; - margin-top: 0; -} -button.profile-action-btn.popup-btn-cancel { +.popup-btn-cancel { background: rgba(255, 255, 255, 0.05); color: var(--text-color); border: var(--border-width) solid var(--light-border-color); + flex: 1; + justify-content: center; } -button.profile-action-btn.popup-btn-cancel:hover { +.popup-btn-cancel:hover { background: rgba(255, 255, 255, 0.1); } -button.profile-action-btn.popup-btn-danger, button.submit-button.popup-btn-danger { - background: var(--big-red) !important; - color: white !important; +.popup-btn-confirm { + background: var(--text-color); + color: var(--main-bg-color); border: none; + flex: 1; + justify-content: center; } -button.profile-action-btn.popup-btn-danger:hover, button.submit-button.popup-btn-danger:hover { - filter: brightness(0.85); +.popup-btn-confirm:hover { + background: white; +} +.popup-btn-danger { + background: var(--big-red); + color: white; + border: none; + flex: 1; + justify-content: center; +} +.popup-btn-danger:hover { + background: #ff5252; } .profile-popup-container.show .profile-popup-content { transform: scale(1); @@ -1065,106 +1072,3 @@ body.hide-topbar-pin #roomdetailsbar roomtopbar .room-action-pin { display: flex body.hide-topbar-call roomcontent roomtopbar .room-action-call { display: none !important; } body.hide-topbar-call #roomdetailsbar roomtopbar .room-action-call { display: flex !important; } - -/* Toggle Switch */ -.toggle-switch { - position: relative; - display: inline-block; - width: 3.4rem; - height: 1.8rem; - margin: var(--button-margin); - flex-shrink: 0; - -} -label.toggle-switch { - opacity: 0.8; -} -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} -.toggle-slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(255, 255, 255, 0.1); - transition: .4s; - border-radius: 1.8rem; - border: var(--border-width) solid var(--light-border-color); -} -.toggle-slider:before { - position: absolute; - content: ""; - height: 1.2rem; - width: 1.2rem; - left: 0.25rem; - top: 50%; - transform: translateY(-50%); - background-color: var(--text-color); - transition: .4s; - border-radius: 50%; -} -input:checked + .toggle-slider { - background-color: var(--text-color) !important; - border-color: var(--text-color) !important; - -} -input:checked + .toggle-slider:before { - transform: translate(1.6rem, -50%); - background-color: var(--main-bg-color) !important; -} -.setting-row { - display: flex; - justify-content: space-between; - align-items: center; - width: auto; - box-sizing: border-box; - margin: var(--button-margin); - padding: 0.5rem; - border-radius: 0.8rem; - background-color: rgba(255, 255, 255, 0.02); -} -.setting-row:hover { - background-color: rgba(255, 255, 255, 0.04); -} -.setting-info { - display: flex; - flex-direction: column; - justify-content: center; -} -.setting-title { - font-size: 1.1rem; - font-weight: bold; -} -.setting-desc { - font-size: 0.85rem; - color: var(--darker-text-color); - margin-top: 0.2rem; -} - -.unread-badge { - width: 0.65rem; - height: 0.65rem; - min-width: 0.65rem; - min-height: 0.65rem; - background-color: var(--big-red); - border-radius: 50%; - flex-shrink: 0; - margin-right: 0.5rem; - transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); - transform: scale(0); -} - -.unread-badge.show { - transform: scale(1); - animation: badge-appear 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); -} - -@keyframes badge-appear { - 0% { transform: scale(0); } - 100% { transform: scale(1); } -}