better context menu reposition, add more profile actions
This commit is contained in:
parent
516bdf5186
commit
3f99389413
10 changed files with 260 additions and 60 deletions
|
|
@ -144,5 +144,11 @@
|
|||
"title.settings": "cat settings",
|
||||
"title.logout": "log out",
|
||||
"action.edit.profile": "edit cat profile",
|
||||
"action.open.dm": "open direct meowchat"
|
||||
"action.open.dm": "open direct meowchat",
|
||||
"action.invite.accept": "accept meow invite",
|
||||
"action.invite.cancel": "hiss away invite",
|
||||
"action.invite.send": "invite to meowchat",
|
||||
"action.profile.loading": "sniffing profile...",
|
||||
"error.profile.load.failed": "failed to sniff profile.",
|
||||
"unknown.author": "somecat"
|
||||
}
|
||||
|
|
@ -144,5 +144,11 @@
|
|||
"title.settings": "Settings",
|
||||
"title.logout": "Log out",
|
||||
"action.edit.profile": "Edit profile",
|
||||
"action.open.dm": "Open DM"
|
||||
"action.open.dm": "Open DM",
|
||||
"action.invite.accept": "Accept invite",
|
||||
"action.invite.cancel": "Cancel invite",
|
||||
"action.invite.send": "Invite to DM",
|
||||
"action.profile.loading": "Loading profile...",
|
||||
"error.profile.load.failed": "Failed to load profile.",
|
||||
"unknown.author": "Someone"
|
||||
}
|
||||
136
webroot/main.js
136
webroot/main.js
|
|
@ -1467,7 +1467,7 @@ async function openProfile(accountId) {
|
|||
let popupInner = document.getElementById("profile-popup-inner");
|
||||
|
||||
//show loading
|
||||
popupInner.innerHTML = '<div style="display: flex; justify-content: center; align-items: center; height: 100%;"><p>Loading profile...</p></div>';
|
||||
popupInner.innerHTML = `<div style="display: flex; justify-content: center; align-items: center; height: 100%;"><p>${processBlah('action.profile.loading')}</p></div>`;
|
||||
popupContainer.style.display = "flex";
|
||||
|
||||
//force reflow
|
||||
|
|
@ -1496,9 +1496,77 @@ async function openProfile(accountId) {
|
|||
let bioHtml = bio && bio.trim() !== "" ? `<div class="profile-bio">${bio.replace(/</g, "<").replace(/>/g, ">")}</div>` : "";
|
||||
|
||||
let isOwnProfile = accountId === id;
|
||||
let actionBtnHtml = isOwnProfile ?
|
||||
`<button class="profile-action-btn">Edit Profile</button>` :
|
||||
`<button class="profile-action-btn">Open DM</button>`;
|
||||
let actionBtnHtml = "";
|
||||
if (isOwnProfile) {
|
||||
actionBtnHtml = `<button class="profile-action-btn">${processBlah('action.edit.profile')}</button>`;
|
||||
} else {
|
||||
let hasDm = false;
|
||||
let dmRoomId = "";
|
||||
if (window.cachedDms) {
|
||||
try {
|
||||
let parsedDms = JSON.parse(window.cachedDms);
|
||||
if (parsedDms.dms) {
|
||||
for (let dmId in parsedDms.dms) {
|
||||
let parts = dmId.split('_');
|
||||
if (parts.length >= 2) {
|
||||
let accStandard = accountId.replace(':', ';');
|
||||
if (!accStandard.includes(';')) accStandard += ';' + host;
|
||||
|
||||
let p0Standard = parts[0].replace(':', ';');
|
||||
if (!p0Standard.includes(';')) p0Standard += ';' + host;
|
||||
|
||||
let p1Standard = parts[1].replace(':', ';');
|
||||
if (!p1Standard.includes(';')) p1Standard += ';' + host;
|
||||
|
||||
if (p0Standard === accStandard || p1Standard === accStandard) {
|
||||
hasDm = true;
|
||||
dmRoomId = dmId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e){}
|
||||
}
|
||||
|
||||
if (hasDm) {
|
||||
actionBtnHtml = `<button class="profile-action-btn" onclick="openDm('${dmRoomId}', '${profileName}', '${accountId}'); closeProfile();">${processBlah('action.open.dm')}</button>`;
|
||||
} else {
|
||||
let sentInvite = false;
|
||||
let receivedInvite = false;
|
||||
if (window.cachedInvites) {
|
||||
let checkInvite = (type) => {
|
||||
if (window.cachedInvites[type]) {
|
||||
try {
|
||||
let invites = JSON.parse(window.cachedInvites[type]);
|
||||
if (invites.dms) {
|
||||
for(let key in invites.dms) {
|
||||
let accStandard = accountId.replace(':', ';');
|
||||
if (!accStandard.includes(';')) accStandard += ';' + host;
|
||||
|
||||
let keyStandard = key.replace(':', ';');
|
||||
if (!keyStandard.includes(';')) keyStandard += ';' + host;
|
||||
|
||||
if (keyStandard === accStandard) return true;
|
||||
}
|
||||
}
|
||||
} catch(e){}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
sentInvite = checkInvite('sent');
|
||||
receivedInvite = checkInvite('received');
|
||||
}
|
||||
|
||||
if (receivedInvite) {
|
||||
actionBtnHtml = `<button class="profile-action-btn" onclick="acceptInvite('${accountId}'); closeProfile();">${processBlah('action.invite.accept')}</button>`;
|
||||
} else if (sentInvite) {
|
||||
actionBtnHtml = `<button class="profile-action-btn" onclick="revokeInvite('${accountId}'); closeProfile();">${processBlah('action.invite.cancel')}</button>`;
|
||||
} else {
|
||||
actionBtnHtml = `<button class="profile-action-btn" onclick="sendInviteTo('${accountId}'); closeProfile();">${processBlah('action.invite.send')}</button>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popupInner.innerHTML = `
|
||||
${bannerHtml}
|
||||
|
|
@ -1508,14 +1576,14 @@ async function openProfile(accountId) {
|
|||
<div class="profile-info">
|
||||
<div>
|
||||
<div class="profile-name">${profileName}</div>
|
||||
<div class="profile-id-host">${profileName}:${profileHost} (${accountId})</div>
|
||||
<div class="profile-id-host">${profileName}:${profileHost} (${accountId.split(':')[0]})</div>
|
||||
</div>
|
||||
${bioHtml}
|
||||
${actionBtnHtml}
|
||||
</div>
|
||||
`;
|
||||
} catch (e) {
|
||||
popupInner.innerHTML = '<div style="display: flex; justify-content: center; align-items: center; height: 100%;"><p>Failed to load profile.</p></div>';
|
||||
popupInner.innerHTML = `<div style="display: flex; justify-content: center; align-items: center; height: 100%;"><p>${processBlah('error.profile.load.failed')}</p></div>`;
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -1608,7 +1676,8 @@ async function renderInvites(res, type) {
|
|||
.replaceAll("{username}", username.split(':')[0])
|
||||
.replaceAll("{pfp}", pfp)
|
||||
.replaceAll("{desc}", desc)
|
||||
.replaceAll("{actions}", actions);
|
||||
.replaceAll("{actions}", actions)
|
||||
.replaceAll("{id}", id);
|
||||
|
||||
html += entry;
|
||||
}
|
||||
|
|
@ -1659,6 +1728,19 @@ async function switchInvitesTab(tab) {
|
|||
await loadInvites(tab);
|
||||
}
|
||||
|
||||
async function sendInviteTo(targetId) {
|
||||
try {
|
||||
showAction("action.invite.sending", "invite.action");
|
||||
let res = await fetchEncrypted("user/dm/invite", targetId);
|
||||
clearAction("invite.action");
|
||||
showBlahNotification(res || "success:invite.sent");
|
||||
if (typeof loadInvites === 'function') await loadInvites('sent');
|
||||
} catch (e) {
|
||||
clearAction("invite.action");
|
||||
showBlahNotification("error:something.wrong");
|
||||
}
|
||||
}
|
||||
|
||||
async function acceptInvite(targetId) { //TODO: Implement key generation
|
||||
try {
|
||||
showAction("action.invite.accepting", "invite.action");
|
||||
|
|
@ -1769,10 +1851,12 @@ function gotoJoinSpace() {
|
|||
switchRoomContent("title.join.space", joinSpaceScreen, false);
|
||||
}
|
||||
|
||||
async function gotoHome() {
|
||||
async function gotoHome(splash = true) {
|
||||
if (roomsBarContainer) roomsBarContainer.style.display = ""; //show roombar
|
||||
setActiveSidebarIndicator(sidebarHomeIndicator);
|
||||
switchRoomContent("title.splash", splashScreen, false);
|
||||
if (splash) {
|
||||
switchRoomContent("title.splash", splashScreen, false);
|
||||
}
|
||||
switchRoomsBar("title.home", homeRoomBar);
|
||||
setActiveRoombarItem(null);
|
||||
setupWebSocket();
|
||||
|
|
@ -2010,6 +2094,11 @@ async function openDm(dmId, username, targetId) {
|
|||
|
||||
let pfp = await getAvatarUrl(targetId, username);
|
||||
let iconHtml = `<img src="${pfp}" style="width: 2.3rem; border-radius: 0.65rem; margin-right: 0.38rem; object-fit: cover; border: var(--border-width) solid rgba(255, 255, 255, 0.2);">`;
|
||||
|
||||
if (currentRoomsBarTitle !== "title.home") {
|
||||
gotoHome(false);
|
||||
}
|
||||
|
||||
await switchRoomContent(username.split(':')[0], chatScreen, true, iconHtml);
|
||||
|
||||
let msgContainer = document.getElementById("chat-messages");
|
||||
|
|
@ -2155,7 +2244,7 @@ async function renderMessages(messages, isPrepend = false) {
|
|||
respondedAuthorId = parts[1];
|
||||
}
|
||||
|
||||
let replyAuthor = "Someone";
|
||||
let replyAuthor = processBlah('unknown.author');
|
||||
let needsAsyncFetch = false;
|
||||
|
||||
if (loadedMessages[respondedId] && loadedMessages[respondedId].author !== "0") {
|
||||
|
|
@ -2183,7 +2272,7 @@ async function renderMessages(messages, isPrepend = false) {
|
|||
window.replyIdCounter = (window.replyIdCounter || 0) + 1;
|
||||
if (window.replyIdCounter > 1000000) window.replyIdCounter = 0;
|
||||
randId = "reply-author-" + window.replyIdCounter;
|
||||
replyAuthor = "Someone";
|
||||
replyAuthor = processBlah('unknown.author');
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
let aId = respondedAuthorId;
|
||||
|
|
@ -2262,10 +2351,10 @@ async function renderMessages(messages, isPrepend = false) {
|
|||
|
||||
html += `
|
||||
<div class="chat-message ${extraClass}" data-msg-id="${msgId}" id="msg-${msgId}">
|
||||
${showAvatar ? `<img src="${pfp}" class="chat-message-pfp">` : `<div style="width: 2.5rem; flex-shrink: 0;"></div>`}
|
||||
${showAvatar ? `<img src="${pfp}" class="chat-message-pfp" onclick="openProfile('${msg.author}')" style="cursor: pointer;">` : `<div style="width: 2.5rem; flex-shrink: 0;"></div>`}
|
||||
<div class="chat-message-content" oncontextmenu="handleMessageContextMenu(event, '${msgId}')">
|
||||
${showAvatar ? `<div class="chat-message-header">
|
||||
<span class="chat-message-author">${authorName}</span>
|
||||
<span class="chat-message-author" onclick="openProfile('${msg.author}')" style="cursor: pointer;">${authorName}</span>
|
||||
<span class="chat-message-timestamp">${timeStr}</span>
|
||||
</div>` : ""}
|
||||
${repliedHtml}
|
||||
|
|
@ -2307,8 +2396,16 @@ window.visualViewport?.addEventListener("resize", async () => {
|
|||
|
||||
isAdjusting = true;
|
||||
while (isAdjusting) {
|
||||
//chat scroll fix
|
||||
ignoreScroll = true;
|
||||
container.scrollTop = container.scrollHeight - container.clientHeight - lastChatScroll;
|
||||
|
||||
//context menu fix
|
||||
let menuRect = fixedContextMenu.getBoundingClientRect();
|
||||
if (menuRect.bottom > window.innerHeight) {
|
||||
fixedContextMenu.style.top = `${Math.max(10, window.innerHeight - menuRect.height - 10)}px`;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
ignoreScroll = false;
|
||||
});
|
||||
|
|
@ -2585,7 +2682,7 @@ async function replyMessage(msgId) {
|
|||
let bar = document.getElementById("replying-bar");
|
||||
if (bar) {
|
||||
let msg = loadedMessages[msgId];
|
||||
let authorName = "Someone";
|
||||
let authorName = processBlah('unknown.author');
|
||||
if (msg && msg.author !== "0" && dmUsernameCache[msg.author]) {
|
||||
authorName = dmUsernameCache[msg.author].split(':')[0];
|
||||
}
|
||||
|
|
@ -2670,16 +2767,7 @@ function openReactionPicker(e) {
|
|||
let rect = fixedContextMenu.getBoundingClientRect();
|
||||
showFixedContextMenu({top: rect.top, right: rect.left, bottom: rect.top, left: rect.left}, reactionPickerMenuHtml);
|
||||
let searchInput = document.getElementById('reaction-picker-search');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('focus', () => { //TODO: use code from resize event
|
||||
setTimeout(() => {
|
||||
let menuRect = fixedContextMenu.getBoundingClientRect();
|
||||
if (menuRect.bottom > window.innerHeight) {
|
||||
fixedContextMenu.style.top = `${Math.max(10, window.innerHeight - menuRect.height - 10)}px`;
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function reactMessagePrompt(msgId, quickReaction = null) {
|
||||
|
|
|
|||
|
|
@ -117,9 +117,9 @@ var notifisScreen = `
|
|||
var invitesEntry = `
|
||||
<div class="invite-entry">
|
||||
<div class="invite-info">
|
||||
<img src="{pfp}" class="invite-pfp">
|
||||
<img src="{pfp}" class="invite-pfp" onclick="openProfile('{id}')" style="cursor: pointer;">
|
||||
<div class="invite-details">
|
||||
<span class="invite-name">{username}</span>
|
||||
<span class="invite-name" onclick="openProfile('{id}')" style="cursor: pointer;">{username}</span>
|
||||
<span class="invite-desc"><blah>{desc}</blah></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -811,14 +811,14 @@ sidebarelement:has(.icon-button:active) indicator:not(.active) {
|
|||
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
transform: scale(0.6);
|
||||
transition: transform 0.2s cubic-bezier(0.4, -0.3, 0.7, 1), opacity 0.2s ease;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.profile-popup-container.show .profile-popup-content {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
transition: transform 0.25s cubic-bezier(0.2, 1.15, 0.4, 1), opacity 0.25s ease;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.profile-popup-close-btn {
|
||||
position: absolute;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue