-
- Nickname
-
-
-
- Block
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
More
+
+
+ Nickname
+
+
+
+ Block
+
+
diff --git a/src/main.js b/src/main.js
index 794b198..baa68af 100644
--- a/src/main.js
+++ b/src/main.js
@@ -46,6 +46,12 @@ const domProfileHeaderAvatarContainer = document.getElementById('profile-header-
const domProfileName = document.getElementById('profile-name');
const domProfileStatus = document.getElementById('profile-status');
// Note: these are 'let' due to needing to use `.replaceWith` when hot-swapping profile elements
+let fProfileEditMode = false;
+let objProfileEditSnapshot = {};
+const domProfileEditBtn = document.getElementById('profile-edit-btn');
+const domProfileEditBar = document.getElementById('profile-edit-bar');
+const domProfileEditCancelBtn = document.getElementById('profile-edit-cancel-btn');
+const domProfileEditSaveBtn = document.getElementById('profile-edit-save-btn');
let domProfileBanner = document.getElementById('profile-banner');
let domProfileAvatar = document.getElementById('profile-avatar');
const domProfileNameSecondary = document.getElementById('profile-secondary-name');
@@ -4292,7 +4298,7 @@ async function setupRustListeners() {
_on('mls_error', (evt) => {
const { group_id, error } = evt.payload || {};
console.error('[MLS] Background operation failed:', group_id, error);
- showToast(error || 'Group operation failed');
+ showToast(error || 'Group Operation Failed');
});
// Listen for MLS initial sync completion after joining a group
@@ -4457,7 +4463,7 @@ async function setupRustListeners() {
// Listen for backend error toasts
_on('show_toast', (evt) => {
- showToast(evt.payload || 'An error occurred');
+ showToast(evt.payload || 'An Error Occurred');
});
// Listen for Attachment Download Progress events
@@ -5128,7 +5134,7 @@ async function setupRustListeners() {
// Listen for Mini App crashes (Android renderer process crash)
_on('miniapp_crashed', () => {
- showToast('Mini App crashed unexpectedly');
+ showToast('Mini App Crashed Unexpectedly');
});
await Promise.all(_p);
@@ -5996,7 +6002,7 @@ function renderProfileTab(cProfile) {
placeholder.classList.add('profile-banner');
if (cProfile.mine) {
placeholder.classList.add('btn');
- placeholder.onclick = askForBanner;
+ placeholder.onclick = null;
}
domProfileBanner.replaceWith(placeholder);
domProfileBanner = placeholder;
@@ -6010,8 +6016,7 @@ function renderProfileTab(cProfile) {
}
}
domProfileBanner.classList.add('profile-banner');
- domProfileBanner.onclick = cProfile.mine ? askForBanner : null;
- if (cProfile.mine) domProfileBanner.classList.add('btn');
+ domProfileBanner.onclick = null;
// Avatar - keep original structure but add click handler
const profileAvatarSrc = getProfileAvatarSrc(cProfile);
@@ -6027,8 +6032,6 @@ function renderProfileTab(cProfile) {
const placeholder = createPlaceholderAvatar(false, 175);
placeholder.classList.add('profile-avatar');
if (cProfile.mine) {
- placeholder.classList.add('btn');
- placeholder.onclick = askForAvatar;
}
domProfileAvatar.replaceWith(placeholder);
domProfileAvatar = placeholder;
@@ -6039,8 +6042,7 @@ function renderProfileTab(cProfile) {
domProfileAvatar = newAvatar;
}
domProfileAvatar.classList.add('profile-avatar');
- domProfileAvatar.onclick = cProfile.mine ? askForAvatar : null;
- if (cProfile.mine) domProfileAvatar.classList.add('btn');
+ domProfileAvatar.onclick = null;
// Secondary Display Name - use profile's npub as fallback
const strNamePlaceholder = cProfile.mine ? 'Set a Display Name' : (cProfile?.id ? cProfile.id.substring(0, 10) + '…' : '');
@@ -6094,9 +6096,9 @@ function renderProfileTab(cProfile) {
if (npub) {
// Copy the full profile URL for easy sharing
navigator.clipboard.writeText(npub).then(() => {
- showToast('Copied!');
+ showToast('Copied Profile Link');
}).catch(() => {
- showToast('Failed to copy');
+ showToast('Failed to Copy');
const copyBtn = e.target.closest('#profile-npub-copy');
if (copyBtn) {
copyBtn.innerHTML = '';
@@ -6114,11 +6116,11 @@ function renderProfileTab(cProfile) {
domProfileOptions.style.display = 'none';
// Show edit buttons and set their click handlers
- document.querySelector('.profile-avatar-edit').style.display = 'flex';
- document.querySelector('.profile-avatar-edit').onclick = askForAvatar;
document.querySelector('.profile-banner-edit').style.display = 'flex';
- document.querySelector('.profile-banner-edit').onclick = askForBanner;
+ document.querySelector('.profile-banner-edit').onclick = enterProfileEditMode;
+ domProfileEditCancelBtn.onclick = () => exitProfileEditMode(true);
+ domProfileEditSaveBtn.onclick = () => exitProfileEditMode(false);
// Show Share button on own profile (top-right of banner)
const ownShareBtn = document.getElementById('profile-share-btn');
@@ -6133,7 +6135,7 @@ function renderProfileTab(cProfile) {
icon.classList.replace('icon-share', 'icon-check');
setTimeout(() => icon.classList.replace('icon-check', 'icon-share'), 2000);
}).catch(() => {
- showToast('Failed to copy profile link');
+ showToast('Failed to Copy Profile Link');
});
}
};
@@ -6157,11 +6159,11 @@ function renderProfileTab(cProfile) {
domProfileName.classList.add('btn');
domProfileStatus.onclick = askForStatus;
domProfileStatus.classList.add('btn');
- domProfileNameSecondary.onclick = askForUsername;
- domProfileNameSecondary.classList.add('btn');
- domProfileStatusSecondary.onclick = askForStatus;
- domProfileStatusSecondary.classList.add('btn');
- domProfileDescription.onclick = editProfileDescription;
+ domProfileName.onclick = () => { if (fProfileEditMode) askForUsername(); };
+ domProfileStatus.onclick = () => { if (fProfileEditMode) askForStatus(); };
+ domProfileNameSecondary.onclick = () => { if (fProfileEditMode) askForUsername(); };
+ domProfileStatusSecondary.onclick = () => { if (fProfileEditMode) askForStatus(); };
+ domProfileDescription.onclick = () => { if (fProfileEditMode) editProfileDescription(); };
domProfileDescription.classList.add('btn');
} else {
// Show Contact Options
@@ -6191,7 +6193,7 @@ function renderProfileTab(cProfile) {
icon.classList.replace('icon-share', 'icon-check');
setTimeout(() => icon.classList.replace('icon-check', 'icon-share'), 2000);
}).catch(() => {
- showToast('Failed to copy profile link');
+ showToast('Failed to Copy Profile Link');
});
}
};
@@ -6209,13 +6211,13 @@ function renderProfileTab(cProfile) {
domProfileMoreDropdown.style.display = 'none';
if (isBlocked) {
await invoke('unblock_user', { npub: cProfile.id });
- showToast('User unblocked');
+ showToast('User Unblocked');
renderChatlist();
} else {
const confirmed = await popupConfirm('Block User', 'Are you sure you want to block this user? You will no longer receive DMs from them.');
if (!confirmed) return;
await invoke('block_user', { npub: cProfile.id });
- showToast('User blocked');
+ showToast('User Blocked');
renderChatlist();
}
};
@@ -6239,7 +6241,6 @@ function renderProfileTab(cProfile) {
};
// Hide edit buttons and own-profile share
- document.querySelector('.profile-avatar-edit').style.display = 'none';
document.querySelector('.profile-banner-edit').style.display = 'none';
document.getElementById('profile-share-btn').style.display = 'none';
@@ -9100,6 +9101,7 @@ function hideEditHistory() {
async function openChat(contact) {
// Display the Chat UI
navbarSelect('chat-btn');
+ if (fProfileEditMode) exitProfileEditMode(true);
domProfile.style.display = 'none';
domChatNew.style.display = 'none';
domChats.style.display = 'none';
@@ -9363,6 +9365,7 @@ async function closeChat() {
}
// Reset the chat UI
+ if (fProfileEditMode) exitProfileEditMode(true);
domProfile.style.display = 'none';
domGroupOverview.style.display = 'none';
domSettingsBtn.style.display = '';
@@ -9417,6 +9420,12 @@ async function openProfile(cProfile) {
domChat.style.display = 'none'; // Hide the chat view when opening profile
domSettingsBtn.style.display = ''; // Ensure settings button is visible (may have been hidden by openChat)
+ // Scroll profile back to top
+ setTimeout(() => {
+ document.getElementById('profile')?.scrollTo(0, 0);
+ document.querySelector('.profile-content')?.scrollTo(0, 0);
+ }, 50);
+
// Render our own profile by default, but otherwise; the given one
if (!cProfile) {
cProfile = arrProfiles.find(a => a.mine);
@@ -9466,6 +9475,7 @@ async function openGroupOverview(chat) {
domChats.style.display = 'none';
domSettings.style.display = 'none';
domInvites.style.display = 'none';
+ if (fProfileEditMode) exitProfileEditMode(true);
domProfile.style.display = 'none';
domChat.style.display = 'none';
@@ -10352,6 +10362,7 @@ async function openInviteMemberToGroup(chat) {
async function openChatlist() {
navbarSelect('chat-btn');
domNavbar.style.display = '';
+ if (fProfileEditMode) exitProfileEditMode(true);
domProfile.style.display = 'none';
domSettings.style.display = 'none';
domInvites.style.display = 'none';
@@ -10382,6 +10393,7 @@ function openSettings() {
domSettings.style.display = '';
// Hide the other tabs
+ if (fProfileEditMode) exitProfileEditMode(true);
domProfile.style.display = 'none';
domChats.style.display = 'none';
domInvites.style.display = 'none';
@@ -10419,6 +10431,7 @@ async function openInvites() {
domInvites.style.display = '';
// Hide the other tabs
+ if (fProfileEditMode) exitProfileEditMode(true);
domProfile.style.display = 'none';
domChats.style.display = 'none';
domSettings.style.display = 'none';
@@ -10466,6 +10479,148 @@ async function openInvites() {
/**
* Edit the profile description inline
*/
+function updateProfileEditLabel() {
+ const cProfile = arrProfiles.find(a => a.mine);
+ if (!cProfile) return;
+ const nameInput = document.querySelector('#profile-edit-name input');
+ const statusInput = document.querySelector('#profile-edit-status input');
+ const bioInput = document.querySelector('#profile-edit-bio textarea');
+ const label = document.getElementById('profile-edit-mode-label');
+ if (!label) return;
+
+ const nameChanged = nameInput?.value.trim() !== (objProfileEditSnapshot.name || '');
+ const statusChanged = statusInput?.value.trim() !== (objProfileEditSnapshot.status?.title ?? objProfileEditSnapshot.status ?? '');
+ const bioChanged = bioInput?.value.trim() !== (objProfileEditSnapshot.about || '');
+
+ if (nameChanged || statusChanged || bioChanged) {
+ label.textContent = 'Unsaved changes made.';
+ label.style.opacity = '1';
+ } else {
+ label.textContent = 'Edit Mode is enabled.';
+ label.style.opacity = '0.6';
+ }
+}
+
+function enterProfileEditMode() {
+ const cProfile = arrProfiles.find(a => a.mine);
+ if (!cProfile) return;
+ objProfileEditSnapshot = {
+ name: cProfile.name || '',
+ status: cProfile.status || '',
+ about: cProfile.about || ''
+ };
+ fProfileEditMode = true;
+ domProfileEditBar.style.opacity = '0';
+ domProfileEditBar.style.display = 'flex';
+ setTimeout(() => domProfileEditBar.style.opacity = '1', 10);
+ domProfileBackBtn.style.display = 'none';
+ document.querySelector('.profile-header-info').style.display = 'none';
+ domProfileBanner.onclick = askForBanner;
+ document.getElementById('profile-edit-btn').style.display = 'none';
+ document.getElementById('profile-share-btn').style.display = 'none';
+ document.getElementById('profile-npub-label').style.display = 'none';
+ document.getElementById('profile-npub-container').style.display = 'none';
+ document.getElementById('profile-badges').style.display = 'none';
+ document.getElementById('profile-secondary-name').style.display = 'none';
+ document.getElementById('profile-secondary-status').style.display = 'none';
+ document.getElementById('profile-description').style.display = 'none';
+ const editName = document.getElementById('profile-edit-name');
+ const editStatus = document.getElementById('profile-edit-status');
+ const editBio = document.getElementById('profile-edit-bio');
+
+ editName.closest('.profile-edit-field-wrapper').style.position = 'relative';
+ editName.innerHTML = ``;
+ editStatus.innerHTML = ``;
+ editBio.innerHTML = ``;
+ const bioTextarea = editBio.querySelector('textarea');
+ setTimeout(() => {
+ bioTextarea.style.height = 'auto';
+ bioTextarea.style.height = bioTextarea.scrollHeight + 'px';
+ }, 10);
+ bioTextarea.addEventListener('input', () => {
+ bioTextarea.style.height = 'auto';
+ bioTextarea.style.height = bioTextarea.scrollHeight + 'px';
+ });
+ const nameInput = document.querySelector('#profile-edit-name input');
+ const statusInput = document.querySelector('#profile-edit-status input');
+
+ nameInput?.addEventListener('input', updateProfileEditLabel);
+
+ statusInput?.addEventListener('input', updateProfileEditLabel);
+ bioTextarea.addEventListener('input', updateProfileEditLabel);
+ document.getElementById('profile-edit-fields').style.display = 'flex';
+ document.getElementById('profile').classList.add('profile-edit-active');
+
+ domProfileAvatar.onclick = async () => {
+ if (!fProfileEditMode) return;
+ const { open } = window.__TAURI__.dialog;
+ const file = await open({
+ title: 'Choose Profile Picture',
+ multiple: false,
+ directory: false,
+ filters: [{ name: 'Image', extensions: ['png', 'jpeg', 'jpg', 'gif', 'webp'] }]
+ });
+ if (!file) return;
+ domProfileAvatar.src = convertFileSrc(file);
+ };
+
+ const bannerContainer = document.getElementById('profile-banner-container');
+ const avatarContainer = document.querySelector('.profile-avatar-container');
+
+ bannerContainer.addEventListener('mousemove', (e) => {
+ const bannerRect = bannerContainer.getBoundingClientRect();
+ const avatarRect = avatarContainer.getBoundingClientRect();
+
+ const inBanner = e.clientY <= bannerRect.top + 200;
+ const inAvatar = (
+ e.clientX >= avatarRect.left &&
+ e.clientX <= avatarRect.right &&
+ e.clientY >= avatarRect.top &&
+ e.clientY <= avatarRect.bottom
+ );
+
+ if (inAvatar || !inBanner) {
+ bannerContainer.classList.add('avatar-hovered');
+ } else {
+ bannerContainer.classList.remove('avatar-hovered');
+ }
+});
+}
+
+function exitProfileEditMode(fCancel = false) {
+ fProfileEditMode = false;
+ domProfileEditBar.style.opacity = '0';
+ setTimeout(() => domProfileEditBar.style.display = 'none', 250);
+ document.querySelector('.profile-header-info').style.display = '';
+ domProfileBackBtn.style.display = 'none';
+ document.getElementById('profile-npub-label').style.display = '';
+ document.getElementById('profile-npub-container').style.display = '';
+ document.getElementById('profile-badges').style.display = '';
+ document.getElementById('profile-edit-fields').style.display = 'none';
+ document.getElementById('profile-secondary-name').style.display = '';
+ document.getElementById('profile-secondary-status').style.display = '';
+ document.getElementById('profile-description').style.display = '';
+ document.getElementById('profile').classList.remove('profile-edit-active');
+ const cProfile = arrProfiles.find(a => a.mine);
+ if (cProfile) {
+ if (fCancel) {
+ cProfile.name = objProfileEditSnapshot.name;
+ cProfile.status = objProfileEditSnapshot.status;
+ cProfile.about = objProfileEditSnapshot.about;
+ } else {
+ const nameInput = document.querySelector('#profile-edit-name input');
+ const statusInput = document.querySelector('#profile-edit-status input');
+ const bioInput = document.querySelector('#profile-edit-bio textarea');
+ if (nameInput) cProfile.name = nameInput.value.trim();
+ if (statusInput && cProfile.status) cProfile.status.title = statusInput.value.trim();
+ if (bioInput) cProfile.about = bioInput.value.trim();
+ showToast('Profile Saved');
+ }
+ renderProfileTab(cProfile);
+ }
+ domProfileBanner.onclick = null;
+}
+
function editProfileDescription() {
// Get the current profile
const cProfile = arrProfiles.find(a => a.mine);
diff --git a/src/styles.css b/src/styles.css
index 9447065..8195dbc 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -184,6 +184,30 @@
mask-image: url("./icons/x.svg");
}
+.icon-edit-x {
+ -webkit-mask: url('./icons/edit-x.svg') no-repeat center;
+ mask: url('./icons/edit-x.svg') no-repeat center;
+ -webkit-mask-size: contain;
+ mask-size: contain;
+ background-color: #FC595C;
+ width: 16px;
+ height: 16px;
+ display: inline-block;
+ flex-shrink: 0;
+}
+
+.icon-save {
+ -webkit-mask: url('./icons/save.svg') no-repeat center;
+ mask: url('./icons/save.svg') no-repeat center;
+ -webkit-mask-size: contain;
+ mask-size: contain;
+ background-color: #33DB98;
+ width: 18px;
+ height: 18px;
+ display: inline-block;
+ flex-shrink: 0;
+}
+
.icon-chat-circle {
mask-image: url("./icons/chat-circle.svg");
}
@@ -689,6 +713,15 @@ html, body {
max-width: 60%;
}
+.profile-header #profile-name {
+ cursor: default !important;
+ opacity: 1 !important;
+}
+
+#profile-name-counter {
+ padding-left: 8px;
+}
+
.profile-header-name-row {
display: flex;
align-items: center;
@@ -745,9 +778,66 @@ html, body {
display: block;
}
+.profile-edit-active #profile-banner-container::before {
+ content: '';
+ position: absolute;
+ top: 70px;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 40px;
+ height: 40px;
+ -webkit-mask: url('./icons/add-photo.svg') no-repeat center;
+ mask: url('./icons/add-photo.svg') no-repeat center;
+ -webkit-mask-size: contain;
+ mask-size: contain;
+ background-color: rgba(255,255,255,0);
+ transition: background-color 0.2s ease;
+ z-index: 3;
+ pointer-events: none;
+}
+
+.profile-edit-active #profile-banner-container:hover::before {
+ background-color: #B2B2B2;
+}
+
+.profile-edit-active #profile-banner-container.avatar-hovered::before {
+ background-color: rgba(255,255,255,0) !important;
+}
+
+/* Banner Hover Overlay */
+#profile-banner-container::after {
+ content: '';
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ height: 200px;
+ background: rgba(255,255,255,0);
+ transition: background 0.2s ease;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.profile-edit-active #profile-banner-container {
+ cursor: pointer;
+}
+
+.profile-edit-active #profile-banner-container:hover::after {
+ background: #03030380;
+ cursor: pointer;
+}
+
+.profile-edit-active #profile-banner-container.avatar-hovered:hover::after {
+ background: rgba(255,255,255,0) !important;
+ cursor: default !important;
+}
+
+.profile-edit-active #profile-banner-container.avatar-hovered {
+ cursor: default !important;
+}
+
/* Avatar Container */
.profile-avatar-container {
position: relative;
+ z-index: 2;
width: 175px;
height: 175px;
margin: -87px auto 10px;
@@ -756,6 +846,51 @@ html, body {
background-color: #030303;
}
+.profile-edit-active .profile-avatar-container::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 40px;
+ height: 40px;
+ -webkit-mask: url('./icons/add-photo.svg') no-repeat center;
+ mask: url('./icons/add-photo.svg') no-repeat center;
+ -webkit-mask-size: contain;
+ mask-size: contain;
+ background-color: rgba(255,255,255,0);
+ transition: background-color 0.2s ease;
+ z-index: 3;
+ pointer-events: none;
+}
+
+.profile-edit-active .profile-avatar-container:hover::before {
+ background-color: #B2B2B2;
+}
+
+.profile-avatar-container::after {
+ content: '';
+ position: absolute;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(255,255,255,0);
+ border-radius: 50%;
+ transition: background 0.2s ease;
+ pointer-events: none;
+}
+
+.profile-edit-active .profile-avatar-container {
+ cursor: pointer;
+ pointer-events: all;
+}
+
+.profile-edit-active .profile-avatar-container:hover::after {
+ background: #03030380;
+}
+
+#profile-edit-fields input {
+ padding: 0 !important;
+}
+
/* Avatar Image */
.profile-avatar {
width: 100%;
@@ -838,16 +973,131 @@ html, body {
.profile-banner-edit {
position: absolute;
- bottom: 10px;
- right: 10px;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- background-color: rgba(0, 0, 0, 0.7);
- display: flex;
+ top: 10px;
+ left: -6px;
+ display: none;
+ margin: 0;
+ transform: scale(0.75);
+ transform-origin: top left;
+ opacity: 1;
+ flex-direction: column;
align-items: center;
justify-content: center;
+ text-align: center;
+ padding: 8px 10px;
+}
+
+#profile-edit-btn .navbar-icon {
+ width: 30px;
+ height: 30px;
+ position: relative;
+ flex-shrink: 0;
+ margin: 0;
+ padding: 0;
+ padding-top: 4px;
+}
+
+#profile-edit-cancel-btn .icon,
+#profile-edit-cancel-btn span,
+#profile-edit-save-btn .icon,
+#profile-edit-save-btn span {
+ position: relative !important;
+ top: 1px !important;
+ left: auto !important;
+ right: auto !important;
+ bottom: auto !important;
+ margin: 0 !important;
+ cursor: pointer !important;
+}
+
+#profile-edit-cancel-btn .icon-edit-x {
+ background-color: #FC595C !important;
+ width: 14px !important;
+ height: 14px !important;
+}
+
+#profile-edit-save-btn .icon-save {
+ background-color: #33DB98 !important;
+ width: 16px !important;
+ height: 16px !important;
+}
+
+#profile-edit-cancel-btn:hover,
+#profile-edit-save-btn:hover {
+ opacity: 0.75;
+ transition: opacity 0.2s ease;
+}
+
+#profile-edit-bar {
+ transition: opacity 0.25s ease;
+}
+
+#profile-edit-fields {
+ flex-direction: column;
+ gap: 12px;
+ width: 90%;
+ margin: 16px auto 0;
+}
+
+.profile-edit-label {
+ font-size: 11px;
+ color: rgba(255,255,255,0.4);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: 4px;
+ display: block;
+ margin-bottom: -4px !important;
+}
+
+.profile-edit-label-centered {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.profile-edit-field-wrapper {
+ background: rgba(255,255,255,0.05);
+ border: 1px solid rgba(255,255,255,0.12);
+ border-radius: 6px;
+ padding: 6px 14px;
+ width: 100%;
+ box-sizing: border-box;
cursor: pointer;
+ text-align: center;
+}
+
+.profile-edit-field-wrapper:hover {
+ border-color: rgba(255,255,255,0.25);
+}
+
+.profile-edit-field-wrapper:focus-within {
+ border-color: var(--primary-color);
+}
+
+.profile-edit-field-bio {
+ min-height: 80px;
+}
+
+.profile-edit-field-text {
+ margin: 0;
+ background: none;
+ border: none;
+ color: inherit;
+ font-size: inherit;
+ width: 100%;
+}
+
+#profile-edit-bio textarea::-webkit-scrollbar {
+ display: none;
+}
+
+#profile-edit-fields input,
+#profile-edit-fields textarea {
+ box-sizing: border-box;
+ width: 100% !important;
+ overflow: hidden;
+ padding: 0 !important;
+ text-align: center;
}
.profile-share-btn {