add message read/unread render & fix profile display
All checks were successful
Android Build / publish (push) Successful in 48s
Linux Build / publish (push) Successful in 52s

This commit is contained in:
olcxja 2026-07-04 17:24:46 +02:00
commit 68ac0beedf
14 changed files with 290 additions and 38 deletions

View file

@ -147,8 +147,10 @@
"error:message.react.failed": "failed to add purr",
"info.sending.message": "sending...",
"placeholder.message.input": "meow...",
"larp.redacted": "this meow was hissed away.",
"placeholder.invitation.code": "invitation code meow",
"larp.redacted": "Meow meow was meowed.",
"message.already.deleted": "This meow has already been meowed.",
"rate.limit": "You are meowing too quickly. Please meow again later.",
"placeholder.invitation.code": "meow meow meow",
"info.messages.loading.older": "Loading older meowsages...",
"error:message.not.found": "Meowsage not found",
"title.profile": "cat profile",

View file

@ -145,6 +145,8 @@
"error:message.react.failed": "Failed to add reaction",
"info.sending.message": "Sending...",
"larp.redacted": "This message was deleted.",
"message.already.deleted": "This message has already been deleted.",
"rate.limit": "You are sending requests too quickly. Please try again later.",
"placeholder.invitation.code": "invitation code",
"info.messages.loading.older": "Loading older messages...",
"error:message.not.found": "Message not found",

View file

@ -157,9 +157,9 @@
}
}
async function refreshDms(waittime = 0) {
async function refreshDms(waittime = 0, showLoading = true) {
try {
showAction("action.dm.fetch", "dmrefresh");
if (showLoading) showAction("action.dm.fetch", "dmrefresh");
await delay(waittime);
if (window.cachedDms && typeof renderDms === 'function') {
await renderDms(window.cachedDms);
@ -169,10 +169,10 @@
if (typeof renderDms === 'function') {
await renderDms(res);
}
clearAction("dmrefresh");
if (showLoading) clearAction("dmrefresh");
}
catch (e) {
clearAction("dmrefresh");
if (showLoading) clearAction("dmrefresh");
showBlahNotification("error:dm.refresh.failed");
}
}

View file

@ -1533,6 +1533,13 @@ async function openProfile(accountId) {
try {
if (!accountId) accountId = window.id; //var fallback
if (accountId.endsWith(';' + host)) {
accountId = accountId.substring(0, accountId.length - host.length - 1);
} else if (accountId.endsWith(':' + host)) {
accountId = accountId.substring(0, accountId.length - host.length - 1);
}
let nameWithHost = await fetchAsync(`${url}/idtoname?id=${accountId}`);
if (nameWithHost.startsWith("error:")) nameWithHost = accountId; //api fallback
@ -1632,7 +1639,7 @@ async function openProfile(accountId) {
<div class="profile-info">
<div>
<div class="profile-name">${profileName}</div>
<div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0]})</div>
<div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0].split(';')[0]})</div>
</div>
${bioHtml}
${actionBtnHtml}
@ -2098,11 +2105,14 @@ async function renderDms(res) {
let pfp = await getAvatarUrl(targetId, targetUsername);
let displayName = targetUsername.split(':')[0];
let isRead = dmData.string3 === "true";
let nameStyle = isRead ? "opacity: 0.8;" : "opacity: 1;";
html += `
<button class="room-entry" id="dm-btn-${dmId}" onclick="openDm('${dmId}', '${targetUsername}', '${targetId}')">
<img src="${pfp}" class="room-pfp">
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
<span class="room-name">${displayName}</span>
<span class="room-name" style="${nameStyle}">${displayName}</span>
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${targetUsername} (${targetId.split(';')[0]})</span>
</div>
</button>
@ -2188,6 +2198,8 @@ async function openDm(dmId, username, targetId) {
try {
showAction("action.dm.opening", "dmopen");
markDmAsRead(dmId);
currentDmKey = await ensureDmRoomKey(dmId);
if (!currentDmKey) {
throw new Error("Missing DM room key");
@ -2484,13 +2496,21 @@ async function renderMessages(messages, isPrepend = false) {
container.innerHTML = html;
let targetScrollTop = 0;
if (wasAtBottom) {
container.scrollTop = container.scrollHeight;
targetScrollTop = container.scrollHeight;
} else if (isPrepend) {
container.scrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);
targetScrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);
} else {
container.scrollTop = oldScrollTop;
targetScrollTop = oldScrollTop;
}
ignoreScroll = true;
container.scrollTop = targetScrollTop;
lastChatScroll = container.scrollHeight - targetScrollTop - container.clientHeight;
if (lastChatScroll < 0) lastChatScroll = 0;
setTimeout(() => { ignoreScroll = false; }, 50);
}
var lastChatScroll = 0;
@ -2528,6 +2548,9 @@ function setupChatScrollListener() {
if (!ignoreScroll) {
isAdjusting = false;
lastChatScroll = container.scrollHeight - container.scrollTop - container.clientHeight;
if (lastChatScroll <= 10) {
handleDmInteraction();
}
}
if (container.scrollTop === 0 && !isLoadingOlderMessages && oldestLoadedMsgId !== null && oldestLoadedMsgId > 0) {
@ -2678,7 +2701,18 @@ function setupWebSocket() {
appWebSocket.onmessage = (event) => {
let data = event.data;
if (data.startsWith("dm_message:")) {
let msgDmId = data.substring("dm_message:".length);
let parts = data.split(":");
let msgDmId = parts[1];
let msgType = parts[2] || "unread";
if (msgType === "unread") {
updateDmListUI(msgDmId, false, Date.now().toString());
} else if (msgType === "read") {
updateDmListUI(msgDmId, true, Date.now().toString());
} else if (msgType === "update") {
// do nothing to UI sorting/read state
}
if (currentDmId === msgDmId) {
if (dmMessageLoadTimeout) clearTimeout(dmMessageLoadTimeout);
dmMessageLoadTimeout = setTimeout(() => {
@ -2709,12 +2743,21 @@ function handleMessageContextMenu(e, msgId) {
showFixedContextMenu({top: e.clientY, right: e.clientX, bottom: e.clientY, left: e.clientX}, messageContextMenu);
let delBtn = fixedContextMenu.querySelector("#context-delete-btn");
if (delBtn) {
if (loadedMessages[msgId] && loadedMessages[msgId].author !== id) {
delBtn.style.display = "none";
} else {
delBtn.style.display = "";
let reactBtn = fixedContextMenu.querySelector("#context-react-btn");
let msg = loadedMessages[msgId];
if (msg && msg.type === "larp.redacted") {
if (delBtn) delBtn.style.display = "none";
if (reactBtn) reactBtn.style.display = "none";
} else {
if (delBtn) {
if (msg && msg.author !== id) {
delBtn.style.display = "none";
} else {
delBtn.style.display = "";
}
}
if (reactBtn) reactBtn.style.display = "";
}
}
@ -2895,6 +2938,11 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
showAction("action.message.reacting", "msgreact");
try {
let currentMsg = loadedMessages[msgId];
if (currentMsg && currentMsg.type === "larp.redacted") {
clearAction("msgreact");
showNotification(processBlah("error.message.already.deleted"), "error");
return;
}
let existingReactions = [];
if (currentMsg && currentMsg.reactions) {
let rxStr = currentMsg.reactions;
@ -2922,7 +2970,9 @@ async function reactMessagePrompt(msgId, quickReaction = 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);
}
@ -2935,6 +2985,10 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
let res = await fetchEncrypted("dm/message/react", JSON.stringify(msgPayload));
if (res && res.startsWith("error:")) {
showBlahNotification(res);
if (currentMsg) {
currentMsg.reactions = originalReactionsEncrypted;
renderMessages(loadedMessages);
}
}
} catch (e) {
showBlahNotification("error:message.react.failed");
@ -3431,3 +3485,68 @@ if (container) {
document.addEventListener('mouseup', endDrag);
document.addEventListener('touchend', endDrag);
}
function updateDmListUI(dmId, isRead, timestamp = null) {
try {
if (!window.cachedDms) return;
let parsedDms = JSON.parse(window.cachedDms);
if (!parsedDms.dms || !parsedDms.dms[dmId]) return;
let changed = false;
if (parsedDms.dms[dmId].string3 !== (isRead ? "true" : "false")) {
parsedDms.dms[dmId].string3 = isRead ? "true" : "false";
changed = true;
}
if (timestamp && parsedDms.dms[dmId].string2 !== timestamp) {
parsedDms.dms[dmId].string2 = timestamp;
changed = true;
}
if (changed) {
window.cachedDms = JSON.stringify(parsedDms);
}
let btn = document.getElementById(`dm-btn-${dmId}`);
let container = document.getElementById("dms-list");
if (btn && container) {
let nameSpan = btn.querySelector('.room-name');
if (nameSpan) {
nameSpan.style.opacity = isRead ? "0.8" : "1";
}
if (timestamp) {
container.prepend(btn);
}
} else {
if (typeof refreshDms === 'function') refreshDms(0, false);
}
} catch (e) {
console.error(e);
if (typeof refreshDms === 'function') refreshDms(0, false);
}
}
function markDmAsRead(dmId) {
fetchEncrypted("dm/read", JSON.stringify({string1: dmId})).catch(()=>{});
updateDmListUI(dmId, true);
}
function handleDmInteraction() {
if (!currentDmId) return;
let container = document.getElementById("chat-messages");
if (!container) return;
if (lastChatScroll <= 10) {
try {
if (window.cachedDms) {
let parsedDms = JSON.parse(window.cachedDms);
if (parsedDms.dms && parsedDms.dms[currentDmId] && parsedDms.dms[currentDmId].string3 !== "true") {
markDmAsRead(currentDmId);
}
}
} catch (e) {}
}
}
document.addEventListener('click', handleDmInteraction);
document.addEventListener('keydown', handleDmInteraction);
document.addEventListener('touchstart', handleDmInteraction);

View file

@ -245,7 +245,7 @@ var messageContextMenu = `
<svg xmlns="http://www.w3.org/2000/svg" width="1.4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M760-200v-160q0-50-35-85t-85-35H273l144 144-57 56-240-240 240-240 57 56-144 144h367q83 0 141.5 58.5T840-360v160h-80Z"/></svg>
<span class="blah">title.reply.message</span>
</button>
<button onclick="openReactionPicker(event)">
<button id="context-react-btn" onclick="openReactionPicker(event)">
<svg xmlns="http://www.w3.org/2000/svg" width="1.4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M480-260q70 0 126.5-40.5T682-400H278q19 59 75.5 99.5T480-260Zm-160-220q25 0 42.5-17.5T380-540q0-25-17.5-42.5T320-600q-25 0-42.5 17.5T260-540q0 25 17.5 42.5T320-480Zm320 0q25 0 42.5-17.5T700-540q0-25-17.5-42.5T640-600q-25 0-42.5 17.5T580-540q0 25 17.5 42.5T640-480ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
<span class="blah">title.react.message</span>
</button>

View file

@ -195,6 +195,7 @@ roomcontent {
height: 100%;
flex-grow: 1;
min-width: 0;
border-right: var(--border-width) solid var(--light-border-color);
}
roomcontent2 {
@ -208,6 +209,7 @@ roomcontent2 {
sidebar {
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
width: calc(var(--icon-button-height) + (var(--button-margin) * 2) + var(--border-width));
@ -217,6 +219,7 @@ sidebar.second {
width: 17rem;
min-width: 17rem;
max-width: 17rem;
flex-shrink: 0;
}
.sidebar-section-header {
display: flex;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -147,8 +147,10 @@
"error:message.react.failed": "failed to add purr",
"info.sending.message": "sending...",
"placeholder.message.input": "meow...",
"larp.redacted": "this meow was hissed away.",
"placeholder.invitation.code": "invitation code meow",
"larp.redacted": "Meow meow was meowed.",
"message.already.deleted": "This meow has already been meowed.",
"rate.limit": "You are meowing too quickly. Please meow again later.",
"placeholder.invitation.code": "meow meow meow",
"info.messages.loading.older": "Loading older meowsages...",
"error:message.not.found": "Meowsage not found",
"title.profile": "cat profile",

View file

@ -145,6 +145,8 @@
"error:message.react.failed": "Failed to add reaction",
"info.sending.message": "Sending...",
"larp.redacted": "This message was deleted.",
"message.already.deleted": "This message has already been deleted.",
"rate.limit": "You are sending requests too quickly. Please try again later.",
"placeholder.invitation.code": "invitation code",
"info.messages.loading.older": "Loading older messages...",
"error:message.not.found": "Message not found",

View file

@ -157,9 +157,9 @@
}
}
async function refreshDms(waittime = 0) {
async function refreshDms(waittime = 0, showLoading = true) {
try {
showAction("action.dm.fetch", "dmrefresh");
if (showLoading) showAction("action.dm.fetch", "dmrefresh");
await delay(waittime);
if (window.cachedDms && typeof renderDms === 'function') {
await renderDms(window.cachedDms);
@ -169,10 +169,10 @@
if (typeof renderDms === 'function') {
await renderDms(res);
}
clearAction("dmrefresh");
if (showLoading) clearAction("dmrefresh");
}
catch (e) {
clearAction("dmrefresh");
if (showLoading) clearAction("dmrefresh");
showBlahNotification("error:dm.refresh.failed");
}
}

View file

@ -1533,6 +1533,13 @@ async function openProfile(accountId) {
try {
if (!accountId) accountId = window.id; //var fallback
if (accountId.endsWith(';' + host)) {
accountId = accountId.substring(0, accountId.length - host.length - 1);
} else if (accountId.endsWith(':' + host)) {
accountId = accountId.substring(0, accountId.length - host.length - 1);
}
let nameWithHost = await fetchAsync(`${url}/idtoname?id=${accountId}`);
if (nameWithHost.startsWith("error:")) nameWithHost = accountId; //api fallback
@ -1632,7 +1639,7 @@ async function openProfile(accountId) {
<div class="profile-info">
<div>
<div class="profile-name">${profileName}</div>
<div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0]})</div>
<div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0].split(';')[0]})</div>
</div>
${bioHtml}
${actionBtnHtml}
@ -2098,11 +2105,14 @@ async function renderDms(res) {
let pfp = await getAvatarUrl(targetId, targetUsername);
let displayName = targetUsername.split(':')[0];
let isRead = dmData.string3 === "true";
let nameStyle = isRead ? "opacity: 0.8;" : "opacity: 1;";
html += `
<button class="room-entry" id="dm-btn-${dmId}" onclick="openDm('${dmId}', '${targetUsername}', '${targetId}')">
<img src="${pfp}" class="room-pfp">
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
<span class="room-name">${displayName}</span>
<span class="room-name" style="${nameStyle}">${displayName}</span>
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${targetUsername} (${targetId.split(';')[0]})</span>
</div>
</button>
@ -2188,6 +2198,8 @@ async function openDm(dmId, username, targetId) {
try {
showAction("action.dm.opening", "dmopen");
markDmAsRead(dmId);
currentDmKey = await ensureDmRoomKey(dmId);
if (!currentDmKey) {
throw new Error("Missing DM room key");
@ -2484,13 +2496,21 @@ async function renderMessages(messages, isPrepend = false) {
container.innerHTML = html;
let targetScrollTop = 0;
if (wasAtBottom) {
container.scrollTop = container.scrollHeight;
targetScrollTop = container.scrollHeight;
} else if (isPrepend) {
container.scrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);
targetScrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);
} else {
container.scrollTop = oldScrollTop;
targetScrollTop = oldScrollTop;
}
ignoreScroll = true;
container.scrollTop = targetScrollTop;
lastChatScroll = container.scrollHeight - targetScrollTop - container.clientHeight;
if (lastChatScroll < 0) lastChatScroll = 0;
setTimeout(() => { ignoreScroll = false; }, 50);
}
var lastChatScroll = 0;
@ -2528,6 +2548,9 @@ function setupChatScrollListener() {
if (!ignoreScroll) {
isAdjusting = false;
lastChatScroll = container.scrollHeight - container.scrollTop - container.clientHeight;
if (lastChatScroll <= 10) {
handleDmInteraction();
}
}
if (container.scrollTop === 0 && !isLoadingOlderMessages && oldestLoadedMsgId !== null && oldestLoadedMsgId > 0) {
@ -2678,7 +2701,18 @@ function setupWebSocket() {
appWebSocket.onmessage = (event) => {
let data = event.data;
if (data.startsWith("dm_message:")) {
let msgDmId = data.substring("dm_message:".length);
let parts = data.split(":");
let msgDmId = parts[1];
let msgType = parts[2] || "unread";
if (msgType === "unread") {
updateDmListUI(msgDmId, false, Date.now().toString());
} else if (msgType === "read") {
updateDmListUI(msgDmId, true, Date.now().toString());
} else if (msgType === "update") {
// do nothing to UI sorting/read state
}
if (currentDmId === msgDmId) {
if (dmMessageLoadTimeout) clearTimeout(dmMessageLoadTimeout);
dmMessageLoadTimeout = setTimeout(() => {
@ -2709,12 +2743,21 @@ function handleMessageContextMenu(e, msgId) {
showFixedContextMenu({top: e.clientY, right: e.clientX, bottom: e.clientY, left: e.clientX}, messageContextMenu);
let delBtn = fixedContextMenu.querySelector("#context-delete-btn");
if (delBtn) {
if (loadedMessages[msgId] && loadedMessages[msgId].author !== id) {
delBtn.style.display = "none";
} else {
delBtn.style.display = "";
let reactBtn = fixedContextMenu.querySelector("#context-react-btn");
let msg = loadedMessages[msgId];
if (msg && msg.type === "larp.redacted") {
if (delBtn) delBtn.style.display = "none";
if (reactBtn) reactBtn.style.display = "none";
} else {
if (delBtn) {
if (msg && msg.author !== id) {
delBtn.style.display = "none";
} else {
delBtn.style.display = "";
}
}
if (reactBtn) reactBtn.style.display = "";
}
}
@ -2895,6 +2938,11 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
showAction("action.message.reacting", "msgreact");
try {
let currentMsg = loadedMessages[msgId];
if (currentMsg && currentMsg.type === "larp.redacted") {
clearAction("msgreact");
showNotification(processBlah("error.message.already.deleted"), "error");
return;
}
let existingReactions = [];
if (currentMsg && currentMsg.reactions) {
let rxStr = currentMsg.reactions;
@ -2922,7 +2970,9 @@ async function reactMessagePrompt(msgId, quickReaction = 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);
}
@ -2935,6 +2985,10 @@ async function reactMessagePrompt(msgId, quickReaction = null) {
let res = await fetchEncrypted("dm/message/react", JSON.stringify(msgPayload));
if (res && res.startsWith("error:")) {
showBlahNotification(res);
if (currentMsg) {
currentMsg.reactions = originalReactionsEncrypted;
renderMessages(loadedMessages);
}
}
} catch (e) {
showBlahNotification("error:message.react.failed");
@ -3431,3 +3485,68 @@ if (container) {
document.addEventListener('mouseup', endDrag);
document.addEventListener('touchend', endDrag);
}
function updateDmListUI(dmId, isRead, timestamp = null) {
try {
if (!window.cachedDms) return;
let parsedDms = JSON.parse(window.cachedDms);
if (!parsedDms.dms || !parsedDms.dms[dmId]) return;
let changed = false;
if (parsedDms.dms[dmId].string3 !== (isRead ? "true" : "false")) {
parsedDms.dms[dmId].string3 = isRead ? "true" : "false";
changed = true;
}
if (timestamp && parsedDms.dms[dmId].string2 !== timestamp) {
parsedDms.dms[dmId].string2 = timestamp;
changed = true;
}
if (changed) {
window.cachedDms = JSON.stringify(parsedDms);
}
let btn = document.getElementById(`dm-btn-${dmId}`);
let container = document.getElementById("dms-list");
if (btn && container) {
let nameSpan = btn.querySelector('.room-name');
if (nameSpan) {
nameSpan.style.opacity = isRead ? "0.8" : "1";
}
if (timestamp) {
container.prepend(btn);
}
} else {
if (typeof refreshDms === 'function') refreshDms(0, false);
}
} catch (e) {
console.error(e);
if (typeof refreshDms === 'function') refreshDms(0, false);
}
}
function markDmAsRead(dmId) {
fetchEncrypted("dm/read", JSON.stringify({string1: dmId})).catch(()=>{});
updateDmListUI(dmId, true);
}
function handleDmInteraction() {
if (!currentDmId) return;
let container = document.getElementById("chat-messages");
if (!container) return;
if (lastChatScroll <= 10) {
try {
if (window.cachedDms) {
let parsedDms = JSON.parse(window.cachedDms);
if (parsedDms.dms && parsedDms.dms[currentDmId] && parsedDms.dms[currentDmId].string3 !== "true") {
markDmAsRead(currentDmId);
}
}
} catch (e) {}
}
}
document.addEventListener('click', handleDmInteraction);
document.addEventListener('keydown', handleDmInteraction);
document.addEventListener('touchstart', handleDmInteraction);

View file

@ -245,7 +245,7 @@ var messageContextMenu = `
<svg xmlns="http://www.w3.org/2000/svg" width="1.4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M760-200v-160q0-50-35-85t-85-35H273l144 144-57 56-240-240 240-240 57 56-144 144h367q83 0 141.5 58.5T840-360v160h-80Z"/></svg>
<span class="blah">title.reply.message</span>
</button>
<button onclick="openReactionPicker(event)">
<button id="context-react-btn" onclick="openReactionPicker(event)">
<svg xmlns="http://www.w3.org/2000/svg" width="1.4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M480-260q70 0 126.5-40.5T682-400H278q19 59 75.5 99.5T480-260Zm-160-220q25 0 42.5-17.5T380-540q0-25-17.5-42.5T320-600q-25 0-42.5 17.5T260-540q0 25 17.5 42.5T320-480Zm320 0q25 0 42.5-17.5T700-540q0-25-17.5-42.5T640-600q-25 0-42.5 17.5T580-540q0 25 17.5 42.5T640-480ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
<span class="blah">title.react.message</span>
</button>

View file

@ -195,6 +195,7 @@ roomcontent {
height: 100%;
flex-grow: 1;
min-width: 0;
border-right: var(--border-width) solid var(--light-border-color);
}
roomcontent2 {
@ -208,6 +209,7 @@ roomcontent2 {
sidebar {
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
width: calc(var(--icon-button-height) + (var(--button-margin) * 2) + var(--border-width));
@ -217,6 +219,7 @@ sidebar.second {
width: 17rem;
min-width: 17rem;
max-width: 17rem;
flex-shrink: 0;
}
.sidebar-section-header {
display: flex;