add DM screen
This commit is contained in:
parent
a666cb9915
commit
be26703908
8 changed files with 386 additions and 8 deletions
|
|
@ -127,12 +127,12 @@
|
|||
|
||||
async function refreshDms() {
|
||||
try {
|
||||
|
||||
|
||||
showAction("action.dm.fetch", "dmrefresh");
|
||||
let res = await fetchEncrypted("user/dm/list");
|
||||
|
||||
clearAction("dmrefresh");
|
||||
if (typeof renderDms === 'function') {
|
||||
await renderDms(res);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
clearAction("dmrefresh");
|
||||
|
|
|
|||
|
|
@ -1234,8 +1234,11 @@ async function switchDetailsContent(title, content)
|
|||
function clickCollapseDms()
|
||||
{
|
||||
var collapseDmsBtn = document.getElementById("collapse-dms");
|
||||
|
||||
collapseDmsBtn.classList.toggle("collapsed");
|
||||
var dmsList = document.getElementById("dms-list");
|
||||
if (dmsList) {
|
||||
dmsList.style.display = collapseDmsBtn.classList.contains("collapsed") ? "none" : "";
|
||||
}
|
||||
}
|
||||
function clickCollapseGroups()
|
||||
{
|
||||
|
|
@ -1471,6 +1474,7 @@ async function acceptInvite(targetId) { //TODO: Implement key generation
|
|||
clearAction("invite.action");
|
||||
showBlahNotification(res || "success:invite.accepted");
|
||||
await loadInvites('received');
|
||||
await refreshDms();
|
||||
} catch (e) {
|
||||
clearAction("invite.action");
|
||||
showBlahNotification("error:something.wrong");
|
||||
|
|
@ -1679,4 +1683,98 @@ function handleMobileSwipe() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function renderDms(res) {
|
||||
let container = document.getElementById("dms-list");
|
||||
if (!container) return;
|
||||
|
||||
if (res && res !== "") {
|
||||
let parsed = JSON.parse(res);
|
||||
let dms = parsed.dms || {};
|
||||
let dmEntries = Object.entries(dms);
|
||||
|
||||
// Sort by timestamp descending
|
||||
dmEntries.sort((a,b) => (parseInt(b[1].string2) || 0) - (parseInt(a[1].string2) || 0));
|
||||
|
||||
let html = "";
|
||||
let count = 0;
|
||||
|
||||
for (let [dmId, dmData] of dmEntries) {
|
||||
let parts = dmId.split('_');
|
||||
let myId = `${id};${host}`;
|
||||
let targetId = parts[0] === myId ? parts[1] : parts[0];
|
||||
|
||||
let targetUsername = await fetchAsync(`${url}/idtoname?id=${targetId}`);
|
||||
if (!targetUsername || targetUsername.trim() === "" || targetUsername.startsWith("error:")) continue;
|
||||
|
||||
let pfp = await getAvatarUrl(targetId, targetUsername);
|
||||
let displayName = targetUsername.split(':')[0];
|
||||
|
||||
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%;">
|
||||
<span class="room-name">${displayName}</span>
|
||||
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${targetUsername}</span>
|
||||
</div>
|
||||
</button>
|
||||
`;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
container.innerHTML = html;
|
||||
container.classList.remove("empty");
|
||||
} else {
|
||||
container.innerHTML = `<p style="opacity: 0.5;"><blah>desc.no.dms</blah></p>`;
|
||||
container.classList.add("empty");
|
||||
let blahTags = container.getElementsByTagName("blah");
|
||||
for (let i = 0; i < blahTags.length; i++) {
|
||||
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
container.innerHTML = `<p style="opacity: 0.5;"><blah>desc.no.dms</blah></p>`;
|
||||
container.classList.add("empty");
|
||||
let blahTags = container.getElementsByTagName("blah");
|
||||
for (let i = 0; i < blahTags.length; i++) {
|
||||
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentDmId = null;
|
||||
var currentDmKey = null; // AES key for encrypting/decrypting messages
|
||||
|
||||
async function openDm(dmId, username, targetId) {
|
||||
try {
|
||||
showAction("action.dm.opening", "dmopen");
|
||||
|
||||
currentDmKey = await ensureDmRoomKey(dmId);
|
||||
if (!currentDmKey) {
|
||||
throw new Error("Missing DM room key");
|
||||
}
|
||||
currentDmId = dmId;
|
||||
|
||||
let pfp = await getAvatarUrl(targetId, username);
|
||||
let iconHtml = `<img src="${pfp}" style="width: 1.8rem; height: 1.8rem; border-radius: 0.4rem; margin-right: 0.5rem; object-fit: cover;">`;
|
||||
await switchRoomContent(username.split(':')[0], chatScreen, false, iconHtml);
|
||||
|
||||
let msgContainer = document.getElementById("chat-messages");
|
||||
if (msgContainer) {
|
||||
msgContainer.innerHTML = `<div style="text-align: center; opacity: 0.5; padding: 1rem;"><blah>desc.messages.encryption.active</blah></div>`;
|
||||
let blahTags = msgContainer.getElementsByTagName("blah");
|
||||
for (let i = 0; i < blahTags.length; i++) {
|
||||
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
||||
}
|
||||
}
|
||||
|
||||
setActiveRoombarItem(`dm-btn-${dmId}`);
|
||||
clearAction("dmopen");
|
||||
} catch (e) {
|
||||
clearAction("dmopen");
|
||||
console.error(e);
|
||||
showBlahNotification("error:dm.open.failed");
|
||||
}
|
||||
}
|
||||
|
|
@ -129,6 +129,20 @@ var invitesEntry = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
var chatScreen = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
|
||||
<div id="chat-messages" style="flex-grow: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<!-- messages go here -->
|
||||
</div>
|
||||
<div style="padding: 1rem; border-top: var(--border-width) solid var(--light-border-color); display: flex; gap: 0.5rem;">
|
||||
<input type="text" id="chat-input" class="forminput" style="flex-grow: 1; margin: 0;" placeholder="{blah(placeholder.message.input)}">
|
||||
<button class="submit-button" onclick="sendMessage()" style="margin: 0; padding: 0 1.5rem;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="var(--main-bg-color)" width="1.5rem"><path d="M120-160v-240l320-80-320-80v-240l760 320-760 320Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
//roombars
|
||||
var homeRoomBar = `
|
||||
<div class="sidebar-section-header">
|
||||
|
|
@ -147,6 +161,7 @@ var homeRoomBar = `
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="dms-list" class="list-container empty" style="padding: 0 0.5rem;"></div>
|
||||
|
||||
<div class="sidebar-section-header">
|
||||
<button class="collapse-text-button" id="collapse-groups" onclick="clickCollapseGroups()">
|
||||
|
|
|
|||
|
|
@ -571,6 +571,82 @@ space{
|
|||
height: 3.1rem;
|
||||
}
|
||||
|
||||
.room-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
padding: 0.5rem 0.8rem;
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.room-pfp {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 0.6rem;
|
||||
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
.room-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.05rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
.chat-message.with-avatar {
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.chat-message-pfp {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 0.6rem;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
opacity: 0; /* hidden by default for consecutive messages */
|
||||
}
|
||||
.chat-message.with-avatar .chat-message-pfp {
|
||||
opacity: 1;
|
||||
}
|
||||
.chat-message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.chat-message-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.2rem;
|
||||
display: none; /* hidden for consecutive */
|
||||
}
|
||||
.chat-message.with-avatar .chat-message-header {
|
||||
display: flex;
|
||||
}
|
||||
.chat-message-author {
|
||||
font-weight: bold;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.chat-message-timestamp {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.chat-message-text {
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
sidebarelement:hover indicator:not(.active) {
|
||||
content: "";
|
||||
|
|
|
|||
|
|
@ -127,12 +127,12 @@
|
|||
|
||||
async function refreshDms() {
|
||||
try {
|
||||
|
||||
|
||||
showAction("action.dm.fetch", "dmrefresh");
|
||||
let res = await fetchEncrypted("user/dm/list");
|
||||
|
||||
clearAction("dmrefresh");
|
||||
if (typeof renderDms === 'function') {
|
||||
await renderDms(res);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
clearAction("dmrefresh");
|
||||
|
|
|
|||
100
webroot/main.js
100
webroot/main.js
|
|
@ -1234,8 +1234,11 @@ async function switchDetailsContent(title, content)
|
|||
function clickCollapseDms()
|
||||
{
|
||||
var collapseDmsBtn = document.getElementById("collapse-dms");
|
||||
|
||||
collapseDmsBtn.classList.toggle("collapsed");
|
||||
var dmsList = document.getElementById("dms-list");
|
||||
if (dmsList) {
|
||||
dmsList.style.display = collapseDmsBtn.classList.contains("collapsed") ? "none" : "";
|
||||
}
|
||||
}
|
||||
function clickCollapseGroups()
|
||||
{
|
||||
|
|
@ -1471,6 +1474,7 @@ async function acceptInvite(targetId) { //TODO: Implement key generation
|
|||
clearAction("invite.action");
|
||||
showBlahNotification(res || "success:invite.accepted");
|
||||
await loadInvites('received');
|
||||
await refreshDms();
|
||||
} catch (e) {
|
||||
clearAction("invite.action");
|
||||
showBlahNotification("error:something.wrong");
|
||||
|
|
@ -1679,4 +1683,98 @@ function handleMobileSwipe() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function renderDms(res) {
|
||||
let container = document.getElementById("dms-list");
|
||||
if (!container) return;
|
||||
|
||||
if (res && res !== "") {
|
||||
let parsed = JSON.parse(res);
|
||||
let dms = parsed.dms || {};
|
||||
let dmEntries = Object.entries(dms);
|
||||
|
||||
// Sort by timestamp descending
|
||||
dmEntries.sort((a,b) => (parseInt(b[1].string2) || 0) - (parseInt(a[1].string2) || 0));
|
||||
|
||||
let html = "";
|
||||
let count = 0;
|
||||
|
||||
for (let [dmId, dmData] of dmEntries) {
|
||||
let parts = dmId.split('_');
|
||||
let myId = `${id};${host}`;
|
||||
let targetId = parts[0] === myId ? parts[1] : parts[0];
|
||||
|
||||
let targetUsername = await fetchAsync(`${url}/idtoname?id=${targetId}`);
|
||||
if (!targetUsername || targetUsername.trim() === "" || targetUsername.startsWith("error:")) continue;
|
||||
|
||||
let pfp = await getAvatarUrl(targetId, targetUsername);
|
||||
let displayName = targetUsername.split(':')[0];
|
||||
|
||||
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%;">
|
||||
<span class="room-name">${displayName}</span>
|
||||
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${targetUsername}</span>
|
||||
</div>
|
||||
</button>
|
||||
`;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
container.innerHTML = html;
|
||||
container.classList.remove("empty");
|
||||
} else {
|
||||
container.innerHTML = `<p style="opacity: 0.5;"><blah>desc.no.dms</blah></p>`;
|
||||
container.classList.add("empty");
|
||||
let blahTags = container.getElementsByTagName("blah");
|
||||
for (let i = 0; i < blahTags.length; i++) {
|
||||
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
container.innerHTML = `<p style="opacity: 0.5;"><blah>desc.no.dms</blah></p>`;
|
||||
container.classList.add("empty");
|
||||
let blahTags = container.getElementsByTagName("blah");
|
||||
for (let i = 0; i < blahTags.length; i++) {
|
||||
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentDmId = null;
|
||||
var currentDmKey = null; // AES key for encrypting/decrypting messages
|
||||
|
||||
async function openDm(dmId, username, targetId) {
|
||||
try {
|
||||
showAction("action.dm.opening", "dmopen");
|
||||
|
||||
currentDmKey = await ensureDmRoomKey(dmId);
|
||||
if (!currentDmKey) {
|
||||
throw new Error("Missing DM room key");
|
||||
}
|
||||
currentDmId = dmId;
|
||||
|
||||
let pfp = await getAvatarUrl(targetId, username);
|
||||
let iconHtml = `<img src="${pfp}" style="width: 1.8rem; height: 1.8rem; border-radius: 0.4rem; margin-right: 0.5rem; object-fit: cover;">`;
|
||||
await switchRoomContent(username.split(':')[0], chatScreen, false, iconHtml);
|
||||
|
||||
let msgContainer = document.getElementById("chat-messages");
|
||||
if (msgContainer) {
|
||||
msgContainer.innerHTML = `<div style="text-align: center; opacity: 0.5; padding: 1rem;"><blah>desc.messages.encryption.active</blah></div>`;
|
||||
let blahTags = msgContainer.getElementsByTagName("blah");
|
||||
for (let i = 0; i < blahTags.length; i++) {
|
||||
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
||||
}
|
||||
}
|
||||
|
||||
setActiveRoombarItem(`dm-btn-${dmId}`);
|
||||
clearAction("dmopen");
|
||||
} catch (e) {
|
||||
clearAction("dmopen");
|
||||
console.error(e);
|
||||
showBlahNotification("error:dm.open.failed");
|
||||
}
|
||||
}
|
||||
|
|
@ -129,6 +129,20 @@ var invitesEntry = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
var chatScreen = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
|
||||
<div id="chat-messages" style="flex-grow: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<!-- messages go here -->
|
||||
</div>
|
||||
<div style="padding: 1rem; border-top: var(--border-width) solid var(--light-border-color); display: flex; gap: 0.5rem;">
|
||||
<input type="text" id="chat-input" class="forminput" style="flex-grow: 1; margin: 0;" placeholder="{blah(placeholder.message.input)}">
|
||||
<button class="submit-button" onclick="sendMessage()" style="margin: 0; padding: 0 1.5rem;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="var(--main-bg-color)" width="1.5rem"><path d="M120-160v-240l320-80-320-80v-240l760 320-760 320Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
//roombars
|
||||
var homeRoomBar = `
|
||||
<div class="sidebar-section-header">
|
||||
|
|
@ -147,6 +161,7 @@ var homeRoomBar = `
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="dms-list" class="list-container empty" style="padding: 0 0.5rem;"></div>
|
||||
|
||||
<div class="sidebar-section-header">
|
||||
<button class="collapse-text-button" id="collapse-groups" onclick="clickCollapseGroups()">
|
||||
|
|
|
|||
|
|
@ -571,6 +571,82 @@ space{
|
|||
height: 3.1rem;
|
||||
}
|
||||
|
||||
.room-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
padding: 0.5rem 0.8rem;
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.room-pfp {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 0.6rem;
|
||||
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
.room-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.05rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
.chat-message.with-avatar {
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.chat-message-pfp {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 0.6rem;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
opacity: 0; /* hidden by default for consecutive messages */
|
||||
}
|
||||
.chat-message.with-avatar .chat-message-pfp {
|
||||
opacity: 1;
|
||||
}
|
||||
.chat-message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.chat-message-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.2rem;
|
||||
display: none; /* hidden for consecutive */
|
||||
}
|
||||
.chat-message.with-avatar .chat-message-header {
|
||||
display: flex;
|
||||
}
|
||||
.chat-message-author {
|
||||
font-weight: bold;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.chat-message-timestamp {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.chat-message-text {
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
sidebarelement:hover indicator:not(.active) {
|
||||
content: "";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue