fix reactions, remove details button
All checks were successful
Android Build / publish (push) Successful in 55s
Linux Build / publish (push) Successful in 58s

This commit is contained in:
olcxja 2026-07-23 19:02:03 +02:00
commit 9968e7fd20
8 changed files with 128 additions and 126 deletions

View file

@ -47,12 +47,12 @@ const ICONS = {
chevronDown: (props) => ICONS._feather(`<polyline points="6 9 12 15 18 9"></polyline>`, props),
lockOpen: (props) => ICONS._feather(`<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path>`, props),
cornerUpLeft: (props) => ICONS._feather(`<polyline points="9 17 4 12 9 7"></polyline><path d="M20 18v-2a4 4 0 0 0-4-4H4"></path>`, props),
spinner: (props) => ICONS._create(`<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>`, { viewBox: "0 0 50 50", width: "24px", height: "24px", fill: "none", stroke: "none", className: "spinner" }, props),
spinner: (props) => ICONS._create(`<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>`, { viewBox: "0 0 50 50", width: "1.5rem", height: "1.5rem", fill: "none", stroke: "none", className: "spinner" }, props),
video: (props) => ICONS._feather(`<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>`, props),
file: (props) => ICONS._feather(`<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>`, props),
home: (props) => ICONS._create(`<path d="M1 6V15H6V11C6 9.89543 6.89543 9 8 9C9.10457 9 10 9.89543 10 11V15H15V6L8 0L1 6Z" stroke="currentColor" stroke-width="1.28" stroke-linejoin="round" stroke-linecap="round" transform="translate(0 0.5)"/>`, { viewBox: "-1 -1 18 18", width: "24", height: "24", fill: "none", stroke: "none" }, props),
home: (props) => ICONS._create(`<path d="M1 6V15H6V11C6 9.89543 6.89543 9 8 9C9.10457 9 10 9.89543 10 11V15H15V6L8 0L1 6Z" stroke="currentColor" stroke-width="1.28" stroke-linejoin="round" stroke-linecap="round" transform="translate(0 0.5)"/>`, { viewBox: "-1 -1 18 18", width: "1.5rem", height: "1.5rem", fill: "none", stroke: "none" }, props),
inbox: (props) => ICONS._material(`<path d="M216-144q-29 0-50.5-21.5T144-216v-528q0-29.7 21.5-50.85Q187-816 216-816h528q29.7 0 50.85 21.15Q816-773.7 816-744v528q0 29-21.15 50.5T744-144H216Zm0-72h528v-144H632q-23 43-63.5 69.5T480-264q-49 0-89.5-26T328-360H216v144Zm332-148q28-28 28-68h168v-312H216v312h168q0 40 28 68t68 28q40 0 68-28ZM216-216h528-528Z"/>`, props),
avatar: function(initials, color, size) {

View file

@ -1303,10 +1303,17 @@ async function switchRoomContent(title, content, showRoomBar, icon = "", skipMob
}
let detailsBtnHtml = showRoomBar ? detailsBtn : '';
let pinCallBtns = showRoomBar ? `${pinBtnHtml}${callBtnHtml}` : '';
roomsTopBarTransition.innerHTML = `${backBtnHtml}${icon}<space></space><inherit>${processBlah(title)}</inherit><div class="flex-spacer"></div>${pinCallBtns}${detailsBtnHtml}`;
let clickableContent = showRoomBar ?
`<button id="room-topbar-clickable" class="inherit" onclick="mobileNavDetails();" style="cursor: pointer; display: flex; align-items: center; border-radius: 0.65rem; background: transparent; outline: none; height: 100%; text-align: left;">
${icon ? icon + '<space></space>' : ''}<inherit>${processBlah(title)}</inherit>
</button>` :
`<inherit style="display: flex; align-items: center; height: 100%;">
${icon ? icon + '<space></space>' : ''}<inherit>${processBlah(title)}</inherit>
</inherit>`;
roomsTopBarTransition.innerHTML = `${backBtnHtml}${clickableContent}<div class="flex-spacer"></div>${pinCallBtns}`;
roomDetailsBar.style.display = showRoomBar ? "flex" : "none";
let parser = new DOMParser();
@ -1488,7 +1495,7 @@ document.addEventListener("click", (e) => {
if (mainScreen && mainScreen.classList.contains('mobile-details')) {
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
if (window.innerWidth <= 55 * rem) {
if (!roomDetailsBar.contains(e.target) && !e.target.closest('.mobile-nav-btn.right') && !e.target.closest('.profile-popup-container')) {
if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container')) {
mobileNavBack();
}
}
@ -1752,17 +1759,17 @@ async function renderInvites(res, type) {
desc = `:desc.invite.dm.received:${username};${onlyId}`;
actions = `
<button class="icon-button" style="background-color: var(--big-green);" onclick="acceptInvite('${id}')">
${ICONS.check({ fill: "#fff", width: "24", height: "24" })}
${ICONS.check({ fill: "#fff", width: "1.5rem", height: "1.5rem" })}
</button>
<button class="icon-button" style="background-color: var(--big-red);" onclick="declineInvite('${id}')">
${ICONS.close({ fill: "#fff", width: "24", height: "24" })}
${ICONS.close({ fill: "#fff", width: "1.5rem", height: "1.5rem" })}
</button>
`;
} else {
desc = `:desc.invite.dm.sent:${username};${onlyId}`;
actions = `
<button class="icon-button" style="background-color: var(--big-red);" onclick="revokeInvite('${id}')">
${ICONS.close({ fill: "#fff", width: "24", height: "24" })}
${ICONS.close({ fill: "#fff", width: "1.5rem", height: "1.5rem" })}
</button>
`;
}
@ -2062,11 +2069,12 @@ document.addEventListener('touchend', async (e) => {
}
}
let elapsed = Date.now() - pressDate; //nie pomoglo ale zostawie
let elapsed = Date.now() - pressDate; //najgorszy fix swiata
if (elapsed < 100) {
const remaining = 100 - elapsed;
await delay(remaining);
}
activeTouchButton.classList.remove('is-pressed');
activeTouchButton = null;
@ -2351,7 +2359,6 @@ async function renderMessages(messages, isPrepend = false) {
try {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
content = await decryptAesGcmFromBase64(content, dmKeyBytes);
if (reactions) reactions = await decryptAesGcmFromBase64(reactions, dmKeyBytes);
decrypted = true;
} catch (e) {
content = `<span style="color:var(--big-red)"><blah>error:messages.decrypt.failed</blah></span>`;
@ -2363,7 +2370,7 @@ async function renderMessages(messages, isPrepend = false) {
let isRedacted = msg.type === "larp.redacted";
let redactedStyle = isRedacted ? ` opacity: 0.5; font-style: italic;` : ``;
let redactedIcon = isRedacted ? `${ICONS.delete({ fill: "currentColor", width: "24", height: "1rem", style: "vertical-align: -0.165em; margin-right: 0.2rem;" })}` : ``;
let redactedIcon = isRedacted ? `${ICONS.delete({ fill: "currentColor", width: "1.5rem", height: "1rem", style: "vertical-align: -0.165em; margin-right: 0.2rem;" })}` : ``;
let attachmentsHtml = "";
if (msg.attachment && msg.attachment !== "") {
@ -2378,8 +2385,8 @@ async function renderMessages(messages, isPrepend = false) {
let sizeStr = att.size ? formatBytes(att.size) : "";
let displayName = truncateFilename(att.name, 25);
let iconSvg = isMedia
? `${ICONS.image({ width: "24", height: "24", stroke: "currentColor" })}`
: `${ICONS.download({ width: "24", height: "24", stroke: "currentColor" })}`;
? `${ICONS.image({ width: "1.5rem", height: "1.5rem", stroke: "currentColor" })}`
: `${ICONS.download({ width: "1.5rem", height: "1.5rem", stroke: "currentColor" })}`;
attachmentsHtml += `<div id="${attContainerId}" class="attachment-box" style="position: relative; min-width: 120px; max-width: 300px; min-height: 100px; max-height: 300px; background: rgba(0,0,0,0.2); border: 1px solid var(--border-color); border-radius: 8px; display: flex; align-items: center; justify-content: center; cursor: pointer; overflow: hidden;" onclick="downloadAndRenderAttachment('${msgId}', '${att.id}', '${escapeHtml(att.type)}', '${escapeHtml(att.name)}')">
<div style="padding: 1rem; text-align: center; color: var(--text-color);">
@ -2532,12 +2539,19 @@ async function renderMessages(messages, isPrepend = false) {
if (rxArr.length > 0) {
let rxCounts = {};
for (let rx of rxArr) {
let parts = rx.split(':');
if (parts.length < 2) continue;
let rxVal = parts.slice(1).join(':');
if (!rxCounts[rxVal]) rxCounts[rxVal] = {count: 0, me: false};
let rxVal = rx.r;
if (msg.key && msg.key !== "") {
try {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
rxVal = await decryptAesGcmFromBase64(rxVal, dmKeyBytes);
} catch(e) {}
}
if (!rxCounts[rxVal]) rxCounts[rxVal] = {count: 0, me: false, myReactionId: null};
rxCounts[rxVal].count++;
if (parts[0] === id) rxCounts[rxVal].me = true;
if (rx.u === id) {
rxCounts[rxVal].me = true;
rxCounts[rxVal].myReactionId = rx.id;
}
}
let rxKeys = Object.keys(rxCounts);
@ -3290,52 +3304,41 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
showNotification(processBlah("error.message.already.deleted"), "error");
return;
}
let existingReactions = [];
let myReactionId = null;
if (currentMsg && currentMsg.reactions) {
let rxStr = currentMsg.reactions;
if (currentMsg.key && currentMsg.key !== "") {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
try {
rxStr = await decryptAesGcmFromBase64(rxStr, dmKeyBytes);
} catch (e) {
}
}
try {
existingReactions = JSON.parse(rxStr);
let rxArr = JSON.parse(currentMsg.reactions);
for (let rx of rxArr) {
let rxVal = rx.r;
if (currentMsg.key && currentMsg.key !== "") {
try {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
rxVal = await decryptAesGcmFromBase64(rxVal, dmKeyBytes);
} catch(e) {}
}
if (rx.u === id && rxVal === reaction) {
myReactionId = rx.id;
break;
}
}
} catch (e) {
}
}
let targetEntry = id + ":" + reaction;
let index = existingReactions.indexOf(targetEntry);
if (index > -1) {
existingReactions.splice(index, 1);
} else {
existingReactions.push(targetEntry);
}
let isAdding = (myReactionId === null);
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
let encryptedReactions = await encryptAesGcmToBase64(JSON.stringify(existingReactions), dmKeyBytes);
let originalReactionsEncrypted = "";
if (currentMsg) {
originalReactionsEncrypted = currentMsg.reactions || "";
currentMsg.reactions = encryptedReactions;
renderMessages(loadedMessages);
}
let encryptedReaction = await encryptAesGcmToBase64(reaction, dmKeyBytes);
let endpoint = isAdding ? "dm/message/react/add" : "dm/message/react/remove";
let msgPayload = {
string1: currentDmId,
string2: msgId,
string3: encryptedReactions
string3: isAdding ? encryptedReaction : myReactionId
};
let res = await fetchEncrypted("dm/message/react", JSON.stringify(msgPayload));
let res = await fetchEncrypted(endpoint, JSON.stringify(msgPayload));
if (res && res.startsWith("error:")) {
showBlahNotification(res);
if (currentMsg) {
currentMsg.reactions = originalReactionsEncrypted;
renderMessages(loadedMessages);
}
}
} catch (e) {
showBlahNotification("error:message.react.failed");

View file

@ -190,14 +190,14 @@ var homeRoomBar = `
var inboxRoomBar = `
<div class="sidebar-section-header">
<button class="collapse-text-button" id="collapse-invites" onclick="gotoInvites()">
${ICONS.invites({ className: "chevron", width: "24" })}
${ICONS.invites({ className: "chevron", width: "1.5rem" })}
<span><blah>title.invites</blah></span>
</button>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="collapse-notifis" onclick="gotoNotifis()">
${ICONS.notifications({ className: "chevron", width: "24" })}
${ICONS.notifications({ className: "chevron", width: "1.5rem" })}
<span><blah>title.notifications</blah></span>
</button>
</div>
@ -266,27 +266,26 @@ var messageContextMenu = `
`;
//elements
var detailsBtn = `<button class="mobile-nav-btn right" onclick="mobileNavDetails()">${ICONS.menu({ width: "24", height: "24" })}</button>`;
var backBtnHtml = `<button class="mobile-nav-btn" onclick="mobileNavBack()">${ICONS.arrowBack({ width: "24", height: "24" })}</button>`;
var pinBtnHtml = `<button class="topbar-action-btn room-action-pin right">${ICONS.pin({ width: "24", height: "24" })}</button>`;
var callBtnHtml = `<button class="topbar-action-btn room-action-call right">${ICONS.call({ width: "24", height: "24" })}</button>`;
var backBtnHtml = `<button class="mobile-nav-btn" onclick="mobileNavBack()">${ICONS.arrowBack({ width: "1.5rem", height: "1.5rem" })}</button>`;
var pinBtnHtml = `<button class="topbar-action-btn room-action-pin right">${ICONS.pin({ width: "1.5rem", height: "1.5rem" })}</button>`;
var callBtnHtml = `<button class="topbar-action-btn room-action-call right">${ICONS.call({ width: "1.5rem", height: "1.5rem" })}</button>`;
var settingsBar = `
<div class="sidebar-section-header">
<button class="collapse-text-button" id="tab-settings-account" onclick="switchSettingsTab('account')">
${ICONS.account({ className: "chevron", width: "24" })}
${ICONS.account({ className: "chevron", width: "1.5rem" })}
<span><blah>title.account</blah></span>
</button>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="tab-settings-profile" onclick="switchSettingsTab('profile')">
${ICONS.account({ className: "chevron", width: "24" })}
${ICONS.account({ className: "chevron", width: "1.5rem" })}
<span><blah>title.profile</blah></span>
</button>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="tab-settings-appearance" onclick="switchSettingsTab('appearance')">
${ICONS.settings({ className: "chevron", width: "24" })}
${ICONS.settings({ className: "chevron", width: "1.5rem" })}
<span><blah>title.appearance</blah></span>
</button>
</div>

View file

@ -465,10 +465,6 @@ space{
background-color: rgba(255, 255, 255, 0.1);
transform: var(--press-scale);
}
.mobile-nav-btn.right {
margin-right: 0;
margin-left: 0.2rem;
}
.mobile-nav-btn svg {
width: 1.5rem;
height: 1.5rem;
@ -777,6 +773,9 @@ button:active, button.is-pressed, input:active, input.is-pressed, textarea:activ
background-color: rgba(255, 255, 255, 0.1);
transform: var(--press-scale);
}
#room-topbar-clickable {
transform: scale(1);
}
.invite-actions button:active {
filter: brightness(0.85);
}

View file

@ -47,12 +47,12 @@ const ICONS = {
chevronDown: (props) => ICONS._feather(`<polyline points="6 9 12 15 18 9"></polyline>`, props),
lockOpen: (props) => ICONS._feather(`<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path>`, props),
cornerUpLeft: (props) => ICONS._feather(`<polyline points="9 17 4 12 9 7"></polyline><path d="M20 18v-2a4 4 0 0 0-4-4H4"></path>`, props),
spinner: (props) => ICONS._create(`<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>`, { viewBox: "0 0 50 50", width: "24px", height: "24px", fill: "none", stroke: "none", className: "spinner" }, props),
spinner: (props) => ICONS._create(`<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>`, { viewBox: "0 0 50 50", width: "1.5rem", height: "1.5rem", fill: "none", stroke: "none", className: "spinner" }, props),
video: (props) => ICONS._feather(`<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>`, props),
file: (props) => ICONS._feather(`<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>`, props),
home: (props) => ICONS._create(`<path d="M1 6V15H6V11C6 9.89543 6.89543 9 8 9C9.10457 9 10 9.89543 10 11V15H15V6L8 0L1 6Z" stroke="currentColor" stroke-width="1.28" stroke-linejoin="round" stroke-linecap="round" transform="translate(0 0.5)"/>`, { viewBox: "-1 -1 18 18", width: "24", height: "24", fill: "none", stroke: "none" }, props),
home: (props) => ICONS._create(`<path d="M1 6V15H6V11C6 9.89543 6.89543 9 8 9C9.10457 9 10 9.89543 10 11V15H15V6L8 0L1 6Z" stroke="currentColor" stroke-width="1.28" stroke-linejoin="round" stroke-linecap="round" transform="translate(0 0.5)"/>`, { viewBox: "-1 -1 18 18", width: "1.5rem", height: "1.5rem", fill: "none", stroke: "none" }, props),
inbox: (props) => ICONS._material(`<path d="M216-144q-29 0-50.5-21.5T144-216v-528q0-29.7 21.5-50.85Q187-816 216-816h528q29.7 0 50.85 21.15Q816-773.7 816-744v528q0 29-21.15 50.5T744-144H216Zm0-72h528v-144H632q-23 43-63.5 69.5T480-264q-49 0-89.5-26T328-360H216v144Zm332-148q28-28 28-68h168v-312H216v312h168q0 40 28 68t68 28q40 0 68-28ZM216-216h528-528Z"/>`, props),
avatar: function(initials, color, size) {

View file

@ -1303,10 +1303,17 @@ async function switchRoomContent(title, content, showRoomBar, icon = "", skipMob
}
let detailsBtnHtml = showRoomBar ? detailsBtn : '';
let pinCallBtns = showRoomBar ? `${pinBtnHtml}${callBtnHtml}` : '';
roomsTopBarTransition.innerHTML = `${backBtnHtml}${icon}<space></space><inherit>${processBlah(title)}</inherit><div class="flex-spacer"></div>${pinCallBtns}${detailsBtnHtml}`;
let clickableContent = showRoomBar ?
`<button id="room-topbar-clickable" class="inherit" onclick="mobileNavDetails();" style="cursor: pointer; display: flex; align-items: center; border-radius: 0.65rem; background: transparent; outline: none; height: 100%; text-align: left;">
${icon ? icon + '<space></space>' : ''}<inherit>${processBlah(title)}</inherit>
</button>` :
`<inherit style="display: flex; align-items: center; height: 100%;">
${icon ? icon + '<space></space>' : ''}<inherit>${processBlah(title)}</inherit>
</inherit>`;
roomsTopBarTransition.innerHTML = `${backBtnHtml}${clickableContent}<div class="flex-spacer"></div>${pinCallBtns}`;
roomDetailsBar.style.display = showRoomBar ? "flex" : "none";
let parser = new DOMParser();
@ -1488,7 +1495,7 @@ document.addEventListener("click", (e) => {
if (mainScreen && mainScreen.classList.contains('mobile-details')) {
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
if (window.innerWidth <= 55 * rem) {
if (!roomDetailsBar.contains(e.target) && !e.target.closest('.mobile-nav-btn.right') && !e.target.closest('.profile-popup-container')) {
if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container')) {
mobileNavBack();
}
}
@ -1752,17 +1759,17 @@ async function renderInvites(res, type) {
desc = `:desc.invite.dm.received:${username};${onlyId}`;
actions = `
<button class="icon-button" style="background-color: var(--big-green);" onclick="acceptInvite('${id}')">
${ICONS.check({ fill: "#fff", width: "24", height: "24" })}
${ICONS.check({ fill: "#fff", width: "1.5rem", height: "1.5rem" })}
</button>
<button class="icon-button" style="background-color: var(--big-red);" onclick="declineInvite('${id}')">
${ICONS.close({ fill: "#fff", width: "24", height: "24" })}
${ICONS.close({ fill: "#fff", width: "1.5rem", height: "1.5rem" })}
</button>
`;
} else {
desc = `:desc.invite.dm.sent:${username};${onlyId}`;
actions = `
<button class="icon-button" style="background-color: var(--big-red);" onclick="revokeInvite('${id}')">
${ICONS.close({ fill: "#fff", width: "24", height: "24" })}
${ICONS.close({ fill: "#fff", width: "1.5rem", height: "1.5rem" })}
</button>
`;
}
@ -2062,11 +2069,12 @@ document.addEventListener('touchend', async (e) => {
}
}
let elapsed = Date.now() - pressDate; //nie pomoglo ale zostawie
let elapsed = Date.now() - pressDate; //najgorszy fix swiata
if (elapsed < 100) {
const remaining = 100 - elapsed;
await delay(remaining);
}
activeTouchButton.classList.remove('is-pressed');
activeTouchButton = null;
@ -2351,7 +2359,6 @@ async function renderMessages(messages, isPrepend = false) {
try {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
content = await decryptAesGcmFromBase64(content, dmKeyBytes);
if (reactions) reactions = await decryptAesGcmFromBase64(reactions, dmKeyBytes);
decrypted = true;
} catch (e) {
content = `<span style="color:var(--big-red)"><blah>error:messages.decrypt.failed</blah></span>`;
@ -2363,7 +2370,7 @@ async function renderMessages(messages, isPrepend = false) {
let isRedacted = msg.type === "larp.redacted";
let redactedStyle = isRedacted ? ` opacity: 0.5; font-style: italic;` : ``;
let redactedIcon = isRedacted ? `${ICONS.delete({ fill: "currentColor", width: "24", height: "1rem", style: "vertical-align: -0.165em; margin-right: 0.2rem;" })}` : ``;
let redactedIcon = isRedacted ? `${ICONS.delete({ fill: "currentColor", width: "1.5rem", height: "1rem", style: "vertical-align: -0.165em; margin-right: 0.2rem;" })}` : ``;
let attachmentsHtml = "";
if (msg.attachment && msg.attachment !== "") {
@ -2378,8 +2385,8 @@ async function renderMessages(messages, isPrepend = false) {
let sizeStr = att.size ? formatBytes(att.size) : "";
let displayName = truncateFilename(att.name, 25);
let iconSvg = isMedia
? `${ICONS.image({ width: "24", height: "24", stroke: "currentColor" })}`
: `${ICONS.download({ width: "24", height: "24", stroke: "currentColor" })}`;
? `${ICONS.image({ width: "1.5rem", height: "1.5rem", stroke: "currentColor" })}`
: `${ICONS.download({ width: "1.5rem", height: "1.5rem", stroke: "currentColor" })}`;
attachmentsHtml += `<div id="${attContainerId}" class="attachment-box" style="position: relative; min-width: 120px; max-width: 300px; min-height: 100px; max-height: 300px; background: rgba(0,0,0,0.2); border: 1px solid var(--border-color); border-radius: 8px; display: flex; align-items: center; justify-content: center; cursor: pointer; overflow: hidden;" onclick="downloadAndRenderAttachment('${msgId}', '${att.id}', '${escapeHtml(att.type)}', '${escapeHtml(att.name)}')">
<div style="padding: 1rem; text-align: center; color: var(--text-color);">
@ -2532,12 +2539,19 @@ async function renderMessages(messages, isPrepend = false) {
if (rxArr.length > 0) {
let rxCounts = {};
for (let rx of rxArr) {
let parts = rx.split(':');
if (parts.length < 2) continue;
let rxVal = parts.slice(1).join(':');
if (!rxCounts[rxVal]) rxCounts[rxVal] = {count: 0, me: false};
let rxVal = rx.r;
if (msg.key && msg.key !== "") {
try {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
rxVal = await decryptAesGcmFromBase64(rxVal, dmKeyBytes);
} catch(e) {}
}
if (!rxCounts[rxVal]) rxCounts[rxVal] = {count: 0, me: false, myReactionId: null};
rxCounts[rxVal].count++;
if (parts[0] === id) rxCounts[rxVal].me = true;
if (rx.u === id) {
rxCounts[rxVal].me = true;
rxCounts[rxVal].myReactionId = rx.id;
}
}
let rxKeys = Object.keys(rxCounts);
@ -3290,52 +3304,41 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
showNotification(processBlah("error.message.already.deleted"), "error");
return;
}
let existingReactions = [];
let myReactionId = null;
if (currentMsg && currentMsg.reactions) {
let rxStr = currentMsg.reactions;
if (currentMsg.key && currentMsg.key !== "") {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
try {
rxStr = await decryptAesGcmFromBase64(rxStr, dmKeyBytes);
} catch (e) {
}
}
try {
existingReactions = JSON.parse(rxStr);
let rxArr = JSON.parse(currentMsg.reactions);
for (let rx of rxArr) {
let rxVal = rx.r;
if (currentMsg.key && currentMsg.key !== "") {
try {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
rxVal = await decryptAesGcmFromBase64(rxVal, dmKeyBytes);
} catch(e) {}
}
if (rx.u === id && rxVal === reaction) {
myReactionId = rx.id;
break;
}
}
} catch (e) {
}
}
let targetEntry = id + ":" + reaction;
let index = existingReactions.indexOf(targetEntry);
if (index > -1) {
existingReactions.splice(index, 1);
} else {
existingReactions.push(targetEntry);
}
let isAdding = (myReactionId === null);
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
let encryptedReactions = await encryptAesGcmToBase64(JSON.stringify(existingReactions), dmKeyBytes);
let originalReactionsEncrypted = "";
if (currentMsg) {
originalReactionsEncrypted = currentMsg.reactions || "";
currentMsg.reactions = encryptedReactions;
renderMessages(loadedMessages);
}
let encryptedReaction = await encryptAesGcmToBase64(reaction, dmKeyBytes);
let endpoint = isAdding ? "dm/message/react/add" : "dm/message/react/remove";
let msgPayload = {
string1: currentDmId,
string2: msgId,
string3: encryptedReactions
string3: isAdding ? encryptedReaction : myReactionId
};
let res = await fetchEncrypted("dm/message/react", JSON.stringify(msgPayload));
let res = await fetchEncrypted(endpoint, JSON.stringify(msgPayload));
if (res && res.startsWith("error:")) {
showBlahNotification(res);
if (currentMsg) {
currentMsg.reactions = originalReactionsEncrypted;
renderMessages(loadedMessages);
}
}
} catch (e) {
showBlahNotification("error:message.react.failed");

View file

@ -190,14 +190,14 @@ var homeRoomBar = `
var inboxRoomBar = `
<div class="sidebar-section-header">
<button class="collapse-text-button" id="collapse-invites" onclick="gotoInvites()">
${ICONS.invites({ className: "chevron", width: "24" })}
${ICONS.invites({ className: "chevron", width: "1.5rem" })}
<span><blah>title.invites</blah></span>
</button>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="collapse-notifis" onclick="gotoNotifis()">
${ICONS.notifications({ className: "chevron", width: "24" })}
${ICONS.notifications({ className: "chevron", width: "1.5rem" })}
<span><blah>title.notifications</blah></span>
</button>
</div>
@ -266,27 +266,26 @@ var messageContextMenu = `
`;
//elements
var detailsBtn = `<button class="mobile-nav-btn right" onclick="mobileNavDetails()">${ICONS.menu({ width: "24", height: "24" })}</button>`;
var backBtnHtml = `<button class="mobile-nav-btn" onclick="mobileNavBack()">${ICONS.arrowBack({ width: "24", height: "24" })}</button>`;
var pinBtnHtml = `<button class="topbar-action-btn room-action-pin right">${ICONS.pin({ width: "24", height: "24" })}</button>`;
var callBtnHtml = `<button class="topbar-action-btn room-action-call right">${ICONS.call({ width: "24", height: "24" })}</button>`;
var backBtnHtml = `<button class="mobile-nav-btn" onclick="mobileNavBack()">${ICONS.arrowBack({ width: "1.5rem", height: "1.5rem" })}</button>`;
var pinBtnHtml = `<button class="topbar-action-btn room-action-pin right">${ICONS.pin({ width: "1.5rem", height: "1.5rem" })}</button>`;
var callBtnHtml = `<button class="topbar-action-btn room-action-call right">${ICONS.call({ width: "1.5rem", height: "1.5rem" })}</button>`;
var settingsBar = `
<div class="sidebar-section-header">
<button class="collapse-text-button" id="tab-settings-account" onclick="switchSettingsTab('account')">
${ICONS.account({ className: "chevron", width: "24" })}
${ICONS.account({ className: "chevron", width: "1.5rem" })}
<span><blah>title.account</blah></span>
</button>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="tab-settings-profile" onclick="switchSettingsTab('profile')">
${ICONS.account({ className: "chevron", width: "24" })}
${ICONS.account({ className: "chevron", width: "1.5rem" })}
<span><blah>title.profile</blah></span>
</button>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="tab-settings-appearance" onclick="switchSettingsTab('appearance')">
${ICONS.settings({ className: "chevron", width: "24" })}
${ICONS.settings({ className: "chevron", width: "1.5rem" })}
<span><blah>title.appearance</blah></span>
</button>
</div>

View file

@ -465,10 +465,6 @@ space{
background-color: rgba(255, 255, 255, 0.1);
transform: var(--press-scale);
}
.mobile-nav-btn.right {
margin-right: 0;
margin-left: 0.2rem;
}
.mobile-nav-btn svg {
width: 1.5rem;
height: 1.5rem;
@ -777,6 +773,9 @@ button:active, button.is-pressed, input:active, input.is-pressed, textarea:activ
background-color: rgba(255, 255, 255, 0.1);
transform: var(--press-scale);
}
#room-topbar-clickable {
transform: scale(1);
}
.invite-actions button:active {
filter: brightness(0.85);
}