Files
mc-webui/app/templates/dm.html
T
MarekWo c37a7d3b23 feat(dm): Auto-retry for undelivered DM messages
Implement automatic retry for DM messages when ACK is not received,
similar to the MeshCore mobile app's Auto Retry feature. The bridge
monitors for ACK after each send and retries up to 3 times, switching
to flood routing after 2 failed direct attempts via reset_path.

- Bridge: background retry engine with configurable max_attempts,
  flood_after; retry group tracking to prevent duplicate messages
- Bridge: enhanced ACK status checks retry groups so delivery is
  detected even if only a retry attempt's ACK arrives
- Backend: filter retry SENT_MSG duplicates from message list
- Frontend: extended ACK polling window, auto-retry toggle in DM bar

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 21:23:32 +01:00

201 lines
9.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Direct Messages - mc-webui</title>
<!-- Favicon -->
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='images/apple-touch-icon.png') }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='images/favicon-32x32.png') }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='images/favicon-16x16.png') }}">
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Bootstrap 5 CSS (local) -->
<link href="{{ url_for('static', filename='vendor/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
<!-- Bootstrap Icons (local) -->
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/bootstrap-icons/bootstrap-icons.css') }}">
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<!-- 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%;
}
}
</style>
</head>
<body>
<!-- Main Content -->
<main>
<div class="container-fluid d-flex flex-column" style="height: 100vh;">
<!-- Conversation Selector Bar -->
<div class="row border-bottom bg-light">
<div class="col-12 p-2">
<div class="d-flex align-items-center gap-2">
<select id="dmConversationSelector" class="form-select" title="Select conversation">
<option value="">Select chat...</option>
<!-- Conversations loaded dynamically via JavaScript -->
</select>
<div class="form-check form-switch flex-shrink-0" title="Auto Retry: resend DM if no ACK received">
<input class="form-check-input" type="checkbox" id="dmAutoRetryToggle" checked>
<label class="form-check-label small text-nowrap" for="dmAutoRetryToggle">Retry</label>
</div>
</div>
</div>
</div>
<!-- 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="dmFilterBar" class="filter-bar">
<div class="filter-bar-inner">
<input type="text" id="dmFilterInput" class="filter-bar-input" placeholder="Filter messages..." autocomplete="off">
<span id="dmFilterMatchCount" class="filter-match-count"></span>
<button type="button" id="dmFilterClearBtn" class="filter-bar-btn filter-bar-btn-clear" title="Clear">
<i class="bi bi-x"></i>
</button>
<button type="button" id="dmFilterCloseBtn" class="filter-bar-btn filter-bar-btn-close" title="Close">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
<div id="dmMessagesContainer" class="messages-container h-100 overflow-auto p-3">
<div id="dmMessagesList">
<!-- Placeholder shown when no conversation selected -->
<div class="dm-empty-state">
<i class="bi bi-envelope"></i>
<p class="mb-1">Select a conversation</p>
<small class="text-muted">Choose from the dropdown above or start a new chat from channel messages</small>
</div>
</div>
</div>
<!-- Scroll to bottom button -->
<button id="dmScrollToBottomBtn" 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="dmSendForm" class="p-3">
<div class="emoji-picker-container">
<div class="input-group">
<textarea
id="dmMessageInput"
class="form-control"
placeholder="Type a message..."
rows="2"
maxlength="500"
disabled
></textarea>
<button type="button" class="btn btn-outline-secondary" id="dmEmojiBtn" title="Insert emoji">
<i class="bi bi-emoji-smile"></i>
</button>
<button type="submit" class="btn btn-success px-4" id="dmSendBtn" disabled>
<i class="bi bi-send"></i>
</button>
</div>
<!-- Emoji picker popup (hidden by default) -->
<div id="dmEmojiPickerPopup" class="emoji-picker-popup hidden"></div>
</div>
<div class="d-flex justify-content-end">
<small class="text-muted"><span id="dmCharCounter">0</span> / 150</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="dmStatusText">
<i class="bi bi-circle-fill text-secondary"></i> Connecting...
</span>
<span id="dmLastRefresh">Updated: Never</span>
</div>
</div>
</div>
</div>
<!-- Floating Action Buttons -->
<div class="fab-container" id="dmFabContainer">
<button class="fab fab-toggle" id="dmFabToggle" title="Hide buttons">
<i class="bi bi-chevron-right"></i>
</button>
<button class="fab fab-filter" id="dmFilterFab" title="Filter Messages">
<i class="bi bi-funnel-fill"></i>
</button>
</div>
</main>
<!-- Toast container for notifications -->
<div class="toast-container position-fixed top-0 start-0 p-3">
<div id="notificationToast" class="toast" role="alert">
<div class="toast-header">
<strong class="me-auto">mc-webui</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body"></div>
</div>
</div>
<!-- Bootstrap 5 JS Bundle (local) -->
<script src="{{ url_for('static', filename='vendor/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<!-- Message Content Processing Utilities (must load before dm.js) -->
<script src="{{ url_for('static', filename='js/message-utils.js') }}"></script>
<!-- Filter Utilities (must load before dm.js) -->
<script src="{{ url_for('static', filename='js/filter-utils.js') }}"></script>
<!-- Custom JS -->
<script src="{{ url_for('static', filename='js/dm.js') }}"></script>
<script>
// Pass configuration from Flask to JavaScript
window.MC_CONFIG = {
deviceName: "{{ device_name }}",
initialConversation: "{{ initial_conversation or '' }}"
};
</script>
</body>
</html>