Files
mc-webui/app/templates/index.html
MarekWo ad478a8d47 feat(ui): Add @me filter button, DM filter push-down, and DM FAB toggle
- Add person icon button in filter bar that inserts the current device
  name into the search field, for filtering own messages
- DM filter bar already benefits from the CSS sibling push-down rule
  added in previous commit (same class names used)
- Add collapsible FAB toggle to DM view, same pattern as channel chat

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:05:47 +01:00

305 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}Chat - mc-webui{% endblock %}
{% block extra_head %}
<!-- Emoji Picker (local) -->
<script type="module" src="{{ url_for('static', filename='vendor/emoji-picker-element/index.js') }}"></script>
<style>
emoji-picker {
--emoji-size: 1.5rem;
--num-columns: 8;
}
.emoji-picker-container {
position: relative;
}
.emoji-picker-popup {
position: absolute;
bottom: 100%;
right: 0;
z-index: 1000;
margin-bottom: 0.5rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
border-radius: 0.5rem;
overflow: hidden;
}
.emoji-picker-popup.hidden {
display: none;
}
/* Mobile responsive adjustments */
@media (max-width: 576px) {
emoji-picker {
--emoji-size: 1.25rem;
--num-columns: 6;
}
.emoji-picker-popup {
right: auto;
left: 0;
width: 100%;
max-width: 100%;
}
}
/* Modal fullscreen - remove all margins and padding */
#dmModal .modal-dialog.modal-fullscreen,
#contactsModal .modal-dialog.modal-fullscreen,
#consoleModal .modal-dialog.modal-fullscreen {
margin: 0 !important;
width: 100vw !important;
max-width: 100vw !important;
height: 100vh !important;
max-height: 100vh !important;
}
#dmModal .modal-content,
#contactsModal .modal-content,
#consoleModal .modal-content {
border: none !important;
border-radius: 0 !important;
height: 100vh !important;
}
#dmModal .modal-body,
#contactsModal .modal-body,
#consoleModal .modal-body {
overflow: hidden !important;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid d-flex flex-column" style="height: 100%;">
<!-- Messages Container -->
<div class="row flex-grow-1 overflow-hidden" style="min-height: 0;">
<div class="col-12 position-relative" style="height: 100%;">
<!-- Filter bar overlay -->
<div id="filterBar" class="filter-bar">
<div class="filter-bar-inner">
<div class="filter-input-wrapper">
<input type="text" id="filterInput" class="filter-bar-input" placeholder="Filter messages..." autocomplete="off">
<!-- Filter mentions autocomplete popup -->
<div id="filterMentionsPopup" class="mentions-popup filter-mentions-popup hidden">
<div class="mentions-list" id="filterMentionsList"></div>
</div>
</div>
<button type="button" id="filterMeBtn" class="filter-bar-btn filter-bar-btn-me" title="Filter my messages">
<i class="bi bi-person-fill"></i>
</button>
<span id="filterMatchCount" class="filter-match-count"></span>
<button type="button" id="filterClearBtn" class="filter-bar-btn filter-bar-btn-clear" title="Clear">
<i class="bi bi-x"></i>
</button>
<button type="button" id="filterCloseBtn" class="filter-bar-btn filter-bar-btn-close" title="Close">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
<div id="messagesContainer" class="messages-container h-100 overflow-auto p-3">
<div id="messagesList">
<!-- Messages will be loaded here via JavaScript -->
<div class="text-center text-muted py-5">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Loading messages...</p>
</div>
</div>
</div>
<!-- Scroll to bottom button -->
<button id="scrollToBottomBtn" class="scroll-to-bottom-btn" title="Scroll to bottom">
<i class="bi bi-chevron-double-down"></i>
</button>
</div>
</div>
<!-- Send Message Form -->
<div class="row border-top bg-light">
<div class="col-12">
<form id="sendMessageForm" class="p-3">
<div class="emoji-picker-container">
<div class="input-group">
<textarea
id="messageInput"
class="form-control"
placeholder="Type a message..."
rows="2"
maxlength="500"
required
></textarea>
<button type="button" class="btn btn-outline-secondary" id="emojiBtn" title="Insert emoji">
<i class="bi bi-emoji-smile"></i>
</button>
<button type="submit" class="btn btn-primary px-4" id="sendBtn">
<i class="bi bi-send"></i>
</button>
</div>
<!-- Emoji picker popup (hidden by default) -->
<div id="emojiPickerPopup" class="emoji-picker-popup hidden"></div>
<!-- Mentions autocomplete popup (hidden by default) -->
<div id="mentionsPopup" class="mentions-popup hidden">
<div class="mentions-list" id="mentionsList"></div>
</div>
</div>
<div class="d-flex justify-content-end">
<small id="charCounter" class="text-muted">0 / 135</small>
</div>
</form>
</div>
</div>
<!-- Status Bar -->
<div class="row border-top">
<div class="col-12">
<div class="p-2 small text-muted d-flex justify-content-between align-items-center">
<span id="statusText">
<i class="bi bi-circle-fill text-secondary"></i> Connecting...
</span>
<span id="lastRefresh">Updated: Never</span>
</div>
</div>
</div>
</div>
<!-- Floating Action Buttons -->
<div class="fab-container" id="fabContainer">
<button class="fab fab-toggle" id="fabToggle" title="Hide buttons">
<i class="bi bi-chevron-right"></i>
</button>
<button class="fab fab-filter" id="filterFab" title="Filter Messages">
<i class="bi bi-funnel-fill"></i>
</button>
<button class="fab fab-dm" data-bs-toggle="modal" data-bs-target="#dmModal" title="Direct Messages">
<i class="bi bi-envelope-fill"></i>
</button>
<button class="fab fab-contacts" data-bs-toggle="modal" data-bs-target="#contactsModal" title="Contact Management">
<i class="bi bi-person-fill"></i>
</button>
</div>
<!-- DM Modal (Full Screen) -->
<div class="modal fade" id="dmModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title"><i class="bi bi-envelope"></i> Direct Messages</h5>
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i> Close
</button>
</div>
<div class="modal-body p-0">
<iframe id="dmFrame" src="/dm" style="width: 100%; height: 100%; border: none;"></iframe>
</div>
</div>
</div>
</div>
<!-- Contact Management Modal (Full Screen) -->
<div class="modal fade" id="contactsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title"><i class="bi bi-person-check"></i> Contact Management</h5>
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i> Close
</button>
</div>
<div class="modal-body p-0">
<iframe id="contactsFrame" src="/contacts/manage" style="width: 100%; height: 100%; border: none;"></iframe>
</div>
</div>
</div>
</div>
<!-- Console Modal (Full Screen) -->
<div class="modal fade" id="consoleModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content" style="background-color: #1a1a2e;">
<div class="modal-header" style="background-color: #16213e; border-bottom: 1px solid #0f3460;">
<h5 class="modal-title text-white"><i class="bi bi-terminal"></i> meshcli Console</h5>
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i> Close
</button>
</div>
<div class="modal-body p-0">
<iframe id="consoleFrame" src="/console" style="width: 100%; height: 100%; border: none;"></iframe>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
// Pass configuration from Flask to JavaScript
window.MC_CONFIG = {
deviceName: "{{ device_name }}"
};
// Reload iframe content when modals are opened to ensure fresh data
document.addEventListener('DOMContentLoaded', function() {
const dmModal = document.getElementById('dmModal');
const contactsModal = document.getElementById('contactsModal');
if (dmModal) {
dmModal.addEventListener('show.bs.modal', function () {
const dmFrame = document.getElementById('dmFrame');
if (dmFrame) {
dmFrame.src = dmFrame.src;
}
});
// Update DM badges when modal is closed (without full page reload)
dmModal.addEventListener('hidden.bs.modal', async function () {
try {
// Reload DM read status from server to sync with app.js automatic updates
// This ensures dmLastSeenTimestamps in app.js is current
if (typeof loadDmLastSeenTimestampsFromServer === 'function') {
await loadDmLastSeenTimestampsFromServer();
}
// Trigger DM badge update (now handled by app.js on FAB button)
if (typeof checkDmUpdates === 'function') {
await checkDmUpdates();
}
} catch (error) {
console.error('Error updating DM badges:', error);
}
});
}
if (contactsModal) {
contactsModal.addEventListener('show.bs.modal', function () {
const contactsFrame = document.getElementById('contactsFrame');
if (contactsFrame) {
contactsFrame.src = contactsFrame.src;
}
});
// Update pending contacts badge when modal is closed
contactsModal.addEventListener('hidden.bs.modal', async function () {
try {
// Trigger pending contacts badge update (handled by app.js on FAB button)
if (typeof updatePendingContactsBadge === 'function') {
await updatePendingContactsBadge();
}
} catch (error) {
console.error('Error updating pending contacts badge:', error);
}
});
}
// Console modal - reload iframe when opened to reset WebSocket connection
const consoleModal = document.getElementById('consoleModal');
if (consoleModal) {
consoleModal.addEventListener('show.bs.modal', function () {
const consoleFrame = document.getElementById('consoleFrame');
if (consoleFrame) {
consoleFrame.src = '/console';
}
});
}
});
</script>
{% endblock %}