mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-08-12 03:32:56 +02:00
refactor(dm): Show all contacts in DM dropdown selector
Changed DM approach from conditional button visibility to showing all available contacts directly in the DM page dropdown. This provides better UX and performance. Changes: - Reverted conditional DM button visibility in app.js (button always shows) - Removed contacts loading from main page (app.js) - Added loadContacts() function to dm.js to fetch contacts from API - Modified populateConversationSelector() to show: 1. Existing conversations (with history) first 2. Separator: "--- Available contacts ---" 3. All contacts from device who aren't in conversations yet - Users can now start new DM conversations with any contact - Updated README.md with new DM workflow description Benefits: - Simpler and more intuitive UX - Better performance (no checks on every message) - Users can proactively start conversations - Clear visibility of who's available for DM 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+3
-36
@@ -11,7 +11,6 @@ let currentChannelIdx = 0; // Current active channel (0 = Public)
|
||||
let availableChannels = []; // List of channels from API
|
||||
let lastSeenTimestamps = {}; // Track last seen message timestamp per channel
|
||||
let unreadCounts = {}; // Track unread message counts per channel
|
||||
let contactsList = []; // List of contacts from API (for DM button visibility)
|
||||
|
||||
// DM state (for badge updates on main page)
|
||||
let dmLastSeenTimestamps = {}; // Track last seen DM timestamp per conversation
|
||||
@@ -41,9 +40,6 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
// This ensures channels are available for checkForUpdates()
|
||||
await loadChannels();
|
||||
|
||||
// Load contacts list (for DM button visibility)
|
||||
loadContacts();
|
||||
|
||||
// Now load other data (can run in parallel)
|
||||
loadArchiveList();
|
||||
loadMessages();
|
||||
@@ -339,9 +335,6 @@ function createMessageElement(msg) {
|
||||
metaInfo += ` | Hops: ${msg.path_len}`;
|
||||
}
|
||||
|
||||
// Check if sender is in contacts (for DM button visibility)
|
||||
const senderInContacts = contactsList.includes(msg.sender);
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="message-header">
|
||||
<span class="message-sender">${escapeHtml(msg.sender)}</span>
|
||||
@@ -354,11 +347,9 @@ function createMessageElement(msg) {
|
||||
<button class="btn btn-outline-secondary btn-sm btn-reply" onclick="replyTo('${escapeHtml(msg.sender)}')">
|
||||
<i class="bi bi-reply"></i> Reply
|
||||
</button>
|
||||
${senderInContacts ? `
|
||||
<button class="btn btn-outline-secondary btn-sm btn-reply" onclick="startDmTo('${escapeHtml(msg.sender)}')">
|
||||
<i class="bi bi-envelope"></i> DM
|
||||
</button>
|
||||
` : ''}
|
||||
<button class="btn btn-outline-secondary btn-sm btn-reply" onclick="startDmTo('${escapeHtml(msg.sender)}')">
|
||||
<i class="bi bi-envelope"></i> DM
|
||||
</button>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
@@ -966,30 +957,6 @@ async function loadChannels() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load contacts list from device
|
||||
* This is used to determine if DM button should be shown for a sender
|
||||
*/
|
||||
async function loadContacts() {
|
||||
try {
|
||||
console.log('[loadContacts] Fetching contacts from API...');
|
||||
|
||||
const response = await fetch('/api/contacts');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
contactsList = data.contacts || [];
|
||||
console.log(`[loadContacts] Loaded ${contactsList.length} contacts:`, contactsList);
|
||||
} else {
|
||||
console.error('[loadContacts] Error loading contacts:', data.error);
|
||||
contactsList = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[loadContacts] Exception:', error.message || error);
|
||||
contactsList = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback: ensure Public channel exists in dropdown even if API fails
|
||||
*/
|
||||
|
||||
+90
-27
@@ -7,6 +7,7 @@
|
||||
let currentConversationId = null;
|
||||
let currentRecipient = null;
|
||||
let dmConversations = [];
|
||||
let contactsList = []; // List of all contacts from device
|
||||
let dmLastSeenTimestamps = {};
|
||||
let autoRefreshInterval = null;
|
||||
let lastMessageTimestamp = 0; // Track latest message timestamp for smart refresh
|
||||
@@ -113,19 +114,47 @@ function setupEventListeners() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load contacts from device
|
||||
*/
|
||||
async function loadContacts() {
|
||||
try {
|
||||
const response = await fetch('/api/contacts');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
contactsList = data.contacts || [];
|
||||
console.log(`[DM] Loaded ${contactsList.length} contacts:`, contactsList);
|
||||
} else {
|
||||
console.error('[DM] Failed to load contacts:', data.error);
|
||||
contactsList = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DM] Error loading contacts:', error);
|
||||
contactsList = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load conversations from API
|
||||
*/
|
||||
async function loadConversations() {
|
||||
try {
|
||||
const response = await fetch('/api/dm/conversations?days=7');
|
||||
const data = await response.json();
|
||||
// Load both conversations and contacts in parallel
|
||||
const [convResponse, _] = await Promise.all([
|
||||
fetch('/api/dm/conversations?days=7'),
|
||||
loadContacts()
|
||||
]);
|
||||
|
||||
if (data.success) {
|
||||
dmConversations = data.conversations || [];
|
||||
const convData = await convResponse.json();
|
||||
|
||||
if (convData.success) {
|
||||
dmConversations = convData.conversations || [];
|
||||
populateConversationSelector();
|
||||
} else {
|
||||
console.error('Failed to load conversations:', data.error);
|
||||
console.error('Failed to load conversations:', convData.error);
|
||||
// Still populate selector with just contacts
|
||||
populateConversationSelector();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading conversations:', error);
|
||||
@@ -134,39 +163,73 @@ async function loadConversations() {
|
||||
|
||||
/**
|
||||
* Populate the conversation selector dropdown
|
||||
* Shows both existing conversations and all contacts
|
||||
*/
|
||||
function populateConversationSelector() {
|
||||
const selector = document.getElementById('dmConversationSelector');
|
||||
if (!selector) return;
|
||||
|
||||
// Clear existing options (keep first placeholder)
|
||||
// Clear existing options
|
||||
selector.innerHTML = '<option value="">Select chat...</option>';
|
||||
|
||||
if (dmConversations.length === 0) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = 'No conversations yet';
|
||||
opt.disabled = true;
|
||||
selector.appendChild(opt);
|
||||
return;
|
||||
// Track which names are already in conversations
|
||||
const conversationNames = new Set();
|
||||
|
||||
// 1. Add existing conversations (with history)
|
||||
if (dmConversations.length > 0) {
|
||||
dmConversations.forEach(conv => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = conv.conversation_id;
|
||||
|
||||
// Show unread indicator
|
||||
const lastSeen = dmLastSeenTimestamps[conv.conversation_id] || 0;
|
||||
const isUnread = conv.last_message_timestamp > lastSeen;
|
||||
|
||||
let label = conv.display_name;
|
||||
if (isUnread) {
|
||||
label = `* ${label}`;
|
||||
}
|
||||
|
||||
opt.textContent = label;
|
||||
selector.appendChild(opt);
|
||||
|
||||
// Track this name
|
||||
conversationNames.add(conv.display_name);
|
||||
});
|
||||
}
|
||||
|
||||
dmConversations.forEach(conv => {
|
||||
// 2. Add separator if we have both conversations and contacts
|
||||
if (dmConversations.length > 0 && contactsList.length > 0) {
|
||||
const separator = document.createElement('option');
|
||||
separator.disabled = true;
|
||||
separator.textContent = '--- Available contacts ---';
|
||||
selector.appendChild(separator);
|
||||
}
|
||||
|
||||
// 3. Add all contacts from device (skip those already in conversations)
|
||||
if (contactsList.length > 0) {
|
||||
contactsList.forEach(contactName => {
|
||||
// Skip if already in conversations
|
||||
if (conversationNames.has(contactName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = document.createElement('option');
|
||||
// Create conversation_id as name_<contactName>
|
||||
opt.value = `name_${contactName}`;
|
||||
opt.textContent = contactName;
|
||||
selector.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
// Show message if no conversations and no contacts
|
||||
if (dmConversations.length === 0 && contactsList.length === 0) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = conv.conversation_id;
|
||||
|
||||
// Show unread indicator
|
||||
const lastSeen = dmLastSeenTimestamps[conv.conversation_id] || 0;
|
||||
const isUnread = conv.last_message_timestamp > lastSeen;
|
||||
|
||||
let label = conv.display_name;
|
||||
if (isUnread) {
|
||||
label = `* ${label}`;
|
||||
}
|
||||
|
||||
opt.textContent = label;
|
||||
opt.value = '';
|
||||
opt.textContent = 'No contacts available';
|
||||
opt.disabled = true;
|
||||
selector.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
// If we have a current conversation, select it
|
||||
if (currentConversationId) {
|
||||
|
||||
Reference in New Issue
Block a user