Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/js/render/chatlist/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function generateChatlistStateHash() {
// Add chat states (including chat ID to capture order changes)
for (const chat of arrChats) {
const isGroup = chat.chat_type === 'MlsGroup';
const profile = !isGroup && chat.participants.length === 1 ? getProfile(chat.id) : null;
const profile = !isGroup ? getProfile(chat.id) : null;
const cLastMsg = chat.messages[chat.messages.length - 1];
const nUnread = computeRowBadgeCount(chat);
const activeTypers = chat.active_typers || [];
Expand Down
2 changes: 1 addition & 1 deletion src/js/render/chatlist/row.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
function renderChat(chat, primaryColor) {
// For groups, we don't have a profile, for DMs we do
const isGroup = chat.chat_type === 'MlsGroup';
const profile = !isGroup && chat.participants.length === 1 ? getProfile(chat.id) : null;
const profile = !isGroup ? getProfile(chat.id) : null;

// Muted DMs stay silent; muted groups still surface pings (mentions of
// you / admin @everyone). See `computeRowBadgeCount` for the policy.
Expand Down
175 changes: 134 additions & 41 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ const domProfileStatus = document.getElementById('profile-status');
let fProfileEditMode = false;
let objProfileEditSnapshot = {};
let strPendingProfileAvatarPath = null;
let strPendingProfileBannerPath = null;
const domProfileEditBtn = document.getElementById('profile-edit-btn');
const domProfileEditBar = document.getElementById('profile-edit-bar');
const domProfileEditCancelBtn = document.getElementById('profile-edit-cancel-btn');
Expand Down Expand Up @@ -4464,8 +4465,6 @@ function renderProfileTab(cProfile) {
domProfileAvatar.onerror = function() {
const placeholder = createPlaceholderAvatar(false, 175);
placeholder.classList.add('profile-avatar');
if (cProfile.mine) {
}
domProfileAvatar.replaceWith(placeholder);
domProfileAvatar = placeholder;
};
Expand Down Expand Up @@ -4588,9 +4587,7 @@ function renderProfileTab(cProfile) {
document.getElementById('profile-status').style.display = 'none';

// Configure other clickables
domProfileName.onclick = askForUsername;
domProfileName.classList.add('btn');
domProfileStatus.onclick = askForStatus;
domProfileStatus.classList.add('btn');
domProfileName.onclick = () => { if (fProfileEditMode) askForUsername(); };
domProfileStatus.onclick = () => { if (fProfileEditMode) askForStatus(); };
Expand Down Expand Up @@ -8130,13 +8127,15 @@ function updateProfileEditLabel() {
const nameChanged = nameInput?.value.trim() !== (objProfileEditSnapshot.name || '');
const statusChanged = statusInput?.value.trim() !== (objProfileEditSnapshot.status?.title ?? objProfileEditSnapshot.status ?? '');
const bioChanged = bioInput?.value.trim() !== (objProfileEditSnapshot.about || '');
const avatarChanged = strPendingProfileAvatarPath !== null;
const bannerChanged = strPendingProfileBannerPath !== null;

if (nameChanged || statusChanged || bioChanged) {
label.textContent = 'Unsaved changes made.';
label.style.opacity = '1';
if (nameChanged || statusChanged || bioChanged || avatarChanged || bannerChanged) {
label.textContent = 'Unsaved Changes Made';
label.style.opacity = '0.8';
} else {
label.textContent = 'Edit Mode is enabled.';
label.style.opacity = '0.6';
label.textContent = 'Edit Mode is Enabled';
label.style.opacity = '0.8';
}
}

Expand All @@ -8146,15 +8145,38 @@ function enterProfileEditMode() {
objProfileEditSnapshot = {
name: cProfile.name || '',
status: cProfile.status || '',
about: cProfile.about || ''
about: cProfile.about || '',
avatar: getProfileAvatarSrc(cProfile) || null,
banner: cProfile.banner || null
};
strPendingProfileAvatarPath = null;
strPendingProfileBannerPath = null;
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;
domProfileBanner.onclick = async () => {
if (!fProfileEditMode) return;
const { open } = window.__TAURI__.dialog;
const file = await open({
title: 'Choose Banner Image',
multiple: false,
directory: false,
filters: [{ name: 'Image', extensions: ['png', 'jpeg', 'jpg', 'gif', 'webp'] }]
});
if (!file) return;
strPendingProfileBannerPath = file;
updateProfileEditLabel();
if (domProfileBanner.tagName === 'DIV') {
const newBanner = document.createElement('img');
newBanner.className = domProfileBanner.className;
domProfileBanner.replaceWith(newBanner);
domProfileBanner = newBanner;
}
domProfileBanner.src = convertFileSrc(file);
};
document.getElementById('profile-edit-btn').style.display = 'none';
document.getElementById('profile-share-btn').style.display = 'none';
document.getElementById('profile-npub-label').style.display = 'none';
Expand Down Expand Up @@ -8182,14 +8204,13 @@ function enterProfileEditMode() {
});
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.classList.add('btn');
domProfileAvatar.onclick = async () => {
if (!fProfileEditMode) return;
const { open } = window.__TAURI__.dialog;
Expand All @@ -8201,30 +8222,32 @@ function enterProfileEditMode() {
});
if (!file) return;
strPendingProfileAvatarPath = file;
updateProfileEditLabel();
domProfileAvatar.src = convertFileSrc(file);
};

// Reset label to clean state on entry
updateProfileEditLabel();

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');
}
});
bannerContainer._editMoveHandler = (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');
}
};
bannerContainer.addEventListener('mousemove', bannerContainer._editMoveHandler);
}

function exitProfileEditMode(fCancel = false) {
Expand All @@ -8237,16 +8260,62 @@ function exitProfileEditMode(fCancel = false) {
document.getElementById('profile-npub-container').style.display = '';
document.getElementById('profile-badges').style.display = '';
document.getElementById('profile-edit-fields').style.display = 'none';
document.getElementById('profile-edit-btn').style.display = '';
document.getElementById('profile-share-btn').style.display = '';
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');

// Reset label back to clean state
const label = document.getElementById('profile-edit-mode-label');
if (label) {
label.textContent = 'Edit Mode is Enabled';
label.style.opacity = '0.8';
}

const cProfile = arrProfiles.find(a => a.mine);
if (cProfile) {
if (fCancel) {
cProfile.name = objProfileEditSnapshot.name;
cProfile.status = objProfileEditSnapshot.status;
cProfile.about = objProfileEditSnapshot.about;
// Revert avatar preview
if (strPendingProfileAvatarPath) {
strPendingProfileAvatarPath = null;
const originalSrc = objProfileEditSnapshot.avatar;
if (originalSrc) {
if (domProfileAvatar.tagName === 'DIV') {
const newAvatar = document.createElement('img');
newAvatar.className = domProfileAvatar.className;
domProfileAvatar.replaceWith(newAvatar);
domProfileAvatar = newAvatar;
}
domProfileAvatar.src = originalSrc;
} else {
const placeholder = createPlaceholderAvatar(false, 175);
placeholder.classList.add('profile-avatar');
domProfileAvatar.replaceWith(placeholder);
domProfileAvatar = placeholder;
}
}
// Revert banner preview
if (strPendingProfileBannerPath) {
strPendingProfileBannerPath = null;
const originalBannerSrc = objProfileEditSnapshot.banner;
if (originalBannerSrc) {
if (domProfileBanner.tagName === 'DIV') {
const newBanner = document.createElement('img');
newBanner.className = domProfileBanner.className;
domProfileBanner.replaceWith(newBanner);
domProfileBanner = newBanner;
}
domProfileBanner.src = originalBannerSrc;
} else {
domProfileBanner.src = '';
domProfileBanner.style.backgroundColor = 'rgb(27, 27, 27)';
}
}
} else {
const nameInput = document.querySelector('#profile-edit-name input');
const statusInput = document.querySelector('#profile-edit-status input');
Expand All @@ -8258,14 +8327,10 @@ function exitProfileEditMode(fCancel = false) {
const prevStatus = objProfileEditSnapshot.status?.title ?? objProfileEditSnapshot.status ?? '';
const prevAbout = objProfileEditSnapshot.about || '';

// Optimistic update so the UI reflects the save instantly.
cProfile.name = newName;
if (cProfile.status) cProfile.status.title = newStatus;
cProfile.about = newAbout;

// Persist to the network. Each field maps to its own backend
// call; only fire on actual changes to avoid pointless relay
// chatter and stray kind-0 / kind-30315 events.
const nameChanged = newName !== prevName;
const aboutChanged = newAbout !== prevAbout;
const statusChanged = newStatus !== prevStatus;
Expand All @@ -8274,7 +8339,7 @@ function exitProfileEditMode(fCancel = false) {
name: nameChanged ? newName : '',
avatar: '',
banner: '',
about: aboutChanged ? newAbout : '',
about: aboutChanged ? (newAbout.length > 0 ? newAbout : ' ') : '',
}).then(ok => {
if (!ok) popupConfirm('Profile Update Failed!', 'Failed to broadcast profile update to the network.', true, '', 'vector_warning.svg');
}).catch(e => popupConfirm('Profile Update Failed!', escapeHtml(String(e)), true, '', 'vector_warning.svg'));
Expand All @@ -8299,12 +8364,35 @@ function exitProfileEditMode(fCancel = false) {
.catch(e => popupConfirm('Avatar Upload Failed!', escapeHtml(String(e)), true, '', 'vector_warning.svg'));
strPendingProfileAvatarPath = null;
}
if (strPendingProfileBannerPath) {
invoke('upload_avatar', { filepath: strPendingProfileBannerPath, uploadType: 'banner' })
.then(bannerUrl => {
if (bannerUrl) {
invoke('update_profile', {
name: '',
avatar: '',
banner: bannerUrl,
about: '',
}).then(ok => {
if (!ok) popupConfirm('Banner Update Failed!', 'Failed to broadcast banner update to the network.', true, '', 'vector_warning.svg');
}).catch(e => popupConfirm('Banner Update Failed!', escapeHtml(String(e)), true, '', 'vector_warning.svg'));
}
})
.catch(e => popupConfirm('Banner Upload Failed!', escapeHtml(String(e)), true, '', 'vector_warning.svg'));
strPendingProfileBannerPath = null;
}

showToast('Profile Saved');
}
renderProfileTab(cProfile);
}
document.getElementById('profile-banner-container').classList.remove('avatar-hovered');
domProfileBanner.onclick = null;
const _bc = document.getElementById('profile-banner-container');
if (_bc._editMoveHandler) {
_bc.removeEventListener('mousemove', _bc._editMoveHandler);
_bc._editMoveHandler = null;
}
}

function editProfileDescription() {
Expand Down Expand Up @@ -8332,9 +8420,7 @@ function editProfileDescription() {
domProfileDescriptionEditor.onblur = null;

// If nothing was edited, don't change anything
if (!domProfileDescriptionEditor.value ||
domProfileDescriptionEditor.value === cProfile.about
) return;
if (domProfileDescriptionEditor.value === cProfile.about) return;

// Update the profile's about property
cProfile.about = domProfileDescriptionEditor.value;
Expand All @@ -8344,7 +8430,14 @@ function editProfileDescription() {
twemojify(domProfileDescription);

// Upload new About Me to Nostr
setAboutMe(cProfile.about);
invoke('update_profile', {
name: '',
avatar: '',
banner: '',
about: cProfile.about,
}).then(ok => {
if (!ok) popupConfirm('Bio Update Failed!', 'Failed to broadcast bio update to the network.', true, '', 'vector_warning.svg');
}).catch(e => popupConfirm('Bio Update Failed!', escapeHtml(String(e)), true, '', 'vector_warning.svg'));
};

// Resize it to match the content size (CSS cannot scale textareas based on content)
Expand Down
8 changes: 7 additions & 1 deletion src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1416,6 +1416,11 @@ html, body {
-moz-user-select: none;
}

#profile-edit-mode-label {
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
}

#profile-edit-cancel-btn .icon,
#profile-edit-cancel-btn span,
#profile-edit-save-btn .icon,
Expand Down Expand Up @@ -13761,6 +13766,7 @@ hr {
display: flex;
align-items: center;
text-align: center;
margin-top: 20px;
margin-bottom: 10px;
}

Expand Down Expand Up @@ -14030,7 +14036,7 @@ hr {
--dmsg-row-padding-y: 0px;
--dmsg-row-padding-x: 2px;
--dmsg-streak-gap: 16px;
--dmsg-continuation-gap: 2px;
--dmsg-continuation-gap: 3px;
--dmsg-gutter-width: 56px;
--dmsg-avatar-size: 40px;
--dmsg-author-color: #ffffff;
Expand Down
Loading