diff --git a/app/static/js/app.js b/app/static/js/app.js
index 7f04e3e..591761b 100644
--- a/app/static/js/app.js
+++ b/app/static/js/app.js
@@ -23,6 +23,7 @@ let markersGroup = null;
let contactsGeoCache = {}; // { 'contactName': { lat, lon }, ... }
let contactsPubkeyMap = {}; // { 'contactName': 'full_pubkey', ... }
let blockedContactNames = new Set(); // Names of blocked contacts
+let protectedContactPubkeys = new Set(); // Pubkeys of protected contacts
let allContactsWithGps = []; // Device contacts for map filtering
let allCachedContactsWithGps = []; // Cache-only contacts for map
let _selfInfo = null; // Own device info (for map marker)
@@ -372,6 +373,23 @@ async function loadBlockedNames() {
}
}
+async function loadProtectedPubkeys() {
+ try {
+ const resp = await fetch('/api/contacts/protected');
+ const data = await resp.json();
+ if (data.success) {
+ protectedContactPubkeys = new Set((data.protected_contacts || []).map(pk => pk.toLowerCase()));
+ }
+ } catch (err) {
+ console.error('Error loading protected contacts:', err);
+ }
+}
+
+function isContactProtectedByName(senderName) {
+ const pubkey = contactsPubkeyMap[senderName];
+ return pubkey && protectedContactPubkeys.has(pubkey.toLowerCase());
+}
+
// Initialize on page load
/**
* Connect to SocketIO /chat namespace for real-time message updates
@@ -486,6 +504,7 @@ document.addEventListener('DOMContentLoaded', async function() {
const messagesPromise = loadMessages();
const geoCachePromise = loadContactsGeoCache(); // Non-blocking, Map buttons update when ready
const blockedPromise = loadBlockedNames(); // Non-blocking, for real-time filtering
+ const protectedPromise = loadProtectedPubkeys(); // Non-blocking, for disabling ignore/block on protected
// Also start archive list loading in parallel
loadArchiveList();
@@ -1187,14 +1206,16 @@ function createMessageElement(msg) {
` : ''}
- ${contactsPubkeyMap[msg.sender] ? `
+ ${contactsPubkeyMap[msg.sender] && !isContactProtectedByName(msg.sender) ? `
` : ''}
+ ${!isContactProtectedByName(msg.sender) ? `
+ ` : ''}
diff --git a/app/static/js/contacts.js b/app/static/js/contacts.js
index ce0f07b..0a31470 100644
--- a/app/static/js/contacts.js
+++ b/app/static/js/contacts.js
@@ -1082,12 +1082,15 @@ function updateProtectionUI(publicKey, isProtected, buttonEl) {
if (lockIcon) lockIcon.remove();
}
- // Enable/disable delete button
- const deleteBtn = cardEl.querySelector('.btn-outline-danger');
- if (deleteBtn) {
- deleteBtn.disabled = isProtected;
- deleteBtn.title = isProtected ? 'Cannot delete protected contact' : '';
- }
+ // Enable/disable delete, ignore, and block buttons based on protection
+ cardEl.querySelectorAll('button').forEach(btn => {
+ const icon = btn.querySelector('i');
+ if (!icon) return;
+ if (icon.classList.contains('bi-trash') || icon.classList.contains('bi-eye-slash') || icon.classList.contains('bi-slash-circle')) {
+ btn.disabled = isProtected;
+ btn.title = isProtected ? 'Protected contact' : '';
+ }
+ });
}
async function toggleContactIgnore(publicKey, ignored) {
@@ -2335,12 +2338,20 @@ function createExistingContactCard(contact, index) {
ignoreBtn.className = 'btn btn-sm btn-outline-secondary';
ignoreBtn.innerHTML = ' Ignore';
ignoreBtn.onclick = () => toggleContactIgnore(contact.public_key, true);
+ if (isProtected) {
+ ignoreBtn.disabled = true;
+ ignoreBtn.title = 'Cannot ignore protected contact';
+ }
actionsDiv.appendChild(ignoreBtn);
const blockBtn = document.createElement('button');
blockBtn.className = 'btn btn-sm btn-outline-danger';
blockBtn.innerHTML = ' Block';
blockBtn.onclick = () => toggleContactBlock(contact.public_key, true);
+ if (isProtected) {
+ blockBtn.disabled = true;
+ blockBtn.title = 'Cannot block protected contact';
+ }
actionsDiv.appendChild(blockBtn);
}