forked from iarv/mc-webui
- Add filter-utils.js with diacritic-insensitive search and text highlighting - Add FAB filter button (gray funnel icon) to channel chat and DM - Filter bar slides in as overlay at top of chat area - Real-time filtering with debounce (150ms) as user types - Matched text highlighted with yellow background - Support for Polish characters (wol matches wół) - Keyboard shortcuts: Ctrl+F to open, Escape to close - Match counter shows X / Y filtered messages - Filter persists during auto-refresh Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
192 lines
8.5 KiB
HTML
192 lines
8.5 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">
|
|
<select id="dmConversationSelector" class="form-select" title="Select conversation">
|
|
<option value="">Select chat...</option>
|
|
<!-- Conversations loaded dynamically via JavaScript -->
|
|
</select>
|
|
</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 Button for Filter -->
|
|
<div class="fab-container">
|
|
<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>
|