{% extends "base.twig" %}
{% block title %}{{ t('ui.echomail.title', {}, locale, ['common']) }}{% if echoarea %} - {{ echoarea }}{% endif %} - {{ parent() }}{% endblock %}
{% block content %}
<!-- Echo area info bar: description + subscribe toggle + post (desktop only, visible only when an echo is selected) -->
<div id="echoInfoBar" class="d-none mb-3">
<div class="d-flex align-items-start justify-content-between gap-3 px-3 py-2 border rounded">
<div class="d-flex flex-column" style="min-width:0">
<strong id="echoTitle" class="text-warning small"></strong>
<small id="echoDescription" class="text-muted"></small>
</div>
<div class="d-flex flex-column gap-1 flex-shrink-0">
<button class="btn btn-sm" id="echoSubscribeBtn" onclick="toggleSubscription()"></button>
<div class="d-flex gap-1">
<button class="btn btn-sm btn-outline-primary" onclick="composeMessage('echomail')">
{{ t('ui.echomail.post_message', {}, locale, ['common']) }}
</button>
{% if ai_assistant_enabled %}
<button class="btn btn-sm btn-outline-secondary" onclick="openAiAssistant(typeof currentMessageId !== 'undefined' ? currentMessageId : null, 'echomail')">
<i class="fas fa-robot me-1"></i>{{ t('ui.ai_assistant.button', {}, locale, ['common']) }}
</button>
{% endif %}
</div>
{% if current_user.is_admin %}
<a class="btn btn-sm btn-outline-secondary d-none" id="echoEditAreaBtn" href="/echoareas">
<i class="fas fa-edit me-1"></i>{{ t('ui.echomail.edit_area', {}, locale, ['common']) }}
</a>
{% endif %}
</div>
</div>
</div>
<!-- Mobile Controls -->
<div class="d-lg-none mb-4">
<!-- Mobile Echo Areas Accordion -->
<div class="accordion" id="mobileControlsAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="echoAreasHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#echoAreasCollapse">
<i class="fas fa-list me-2"></i>
<span id="mobileAccordionText">
{% if echoarea %}
{{ t('ui.echomail.viewing_prefix', {}, locale, ['common']) }} {{ echoarea }}
{% else %}
{{ t('ui.echomail.viewing_all', {}, locale, ['common']) }}
{% endif %}
</span>
</button>
</h2>
<div id="echoAreasCollapse" class="accordion-collapse collapse" data-bs-parent="#mobileControlsAccordion">
<div class="accordion-body">
<div class="d-flex gap-2 mb-2">
<a href="/echolist" class="btn btn-sm btn-outline-primary flex-fill">
<i class="fa-solid fa-list me-1"></i> {{ t('ui.echomail.full_echo_list', {}, locale, ['common']) }}
</a>
<a href="/subscriptions" id="mobileManageBtn" class="btn btn-sm btn-outline-secondary flex-fill">
<i class="fa-solid fa-star me-1" id="mobileManageBtnIcon"></i> <span id="mobileManageBtnLabel">{{ t('ui.echomail.manage_subscriptions', {}, locale, ['common']) }}</span>
</a>
</div>
<ul class="nav nav-tabs nav-fill mb-0" id="mobileViewingTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active py-1 small" id="mobileAreaListTab"
data-bs-toggle="tab" data-bs-target="#mobileAreaListPane"
type="button" role="tab">
{{ t('ui.echomail.tab_area_list', {}, locale, ['common']) }}
</button>
</li>
{% if interests_enabled and has_interests %}
<li class="nav-item" role="presentation">
<button class="nav-link py-1 small" id="mobileInterestsTab"
data-bs-toggle="tab" data-bs-target="#mobileInterestsPane"
type="button" role="tab">
{{ t('ui.echomail.tab_interests', {}, locale, ['common']) }}
</button>
</li>
{% endif %}
</ul>
<div class="tab-content border border-top-0 rounded-bottom">
<div class="tab-pane fade show active p-0" id="mobileAreaListPane" role="tabpanel">
<div class="px-2 pt-2">
{% if interests_enabled and has_interests %}
<select class="form-select form-select-sm mb-2" id="mobileAreaListInterestFilter"
onchange="setAreaListInterestFilter(this.value ? parseInt(this.value) : null)">
<option value="">{{ t('ui.echomail.all_subscribed_areas', {}, locale, ['common']) }}</option>
</select>
{% endif %}
<input type="text" class="form-control form-control-sm" id="mobileEchoareaSearchInput"
placeholder="{{ t('ui.echomail.search_areas_placeholder', {}, locale, ['common']) }}" onkeyup="searchEchoareas(this.value)">
</div>
<div style="max-height: 268px; overflow-y: auto;">
<div id="mobileEchoareasList">
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin me-2"></i>
{{ t('ui.echomail.loading_areas', {}, locale, ['common']) }}
</div>
</div>
</div>
</div>
{% if interests_enabled and has_interests %}
<div class="tab-pane fade p-0" id="mobileInterestsPane" role="tabpanel">
<div style="max-height: 300px; overflow-y: auto;">
<div id="mobileInterestsList">
<div class="text-center text-muted p-3">
<i class="fas fa-spinner fa-spin me-2"></i>
{{ t('ui.echomail.loading_areas', {}, locale, ['common']) }}
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Mobile Search Accordion -->
<div class="accordion-item">
<h2 class="accordion-header" id="mobileSearchHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#mobileSearchCollapse" aria-expanded="false">
<i class="fas fa-search me-2"></i>
{{ t('ui.common.search_messages', {}, locale, ['common']) }}
</button>
</h2>
<div id="mobileSearchCollapse" class="accordion-collapse collapse" data-bs-parent="#mobileControlsAccordion">
<div class="accordion-body">
<div class="mb-3">
<input type="text" class="form-control" id="mobileSearchInput" placeholder="{{ t('ui.common.search_messages_placeholder', {}, locale, ['common']) }}">
</div>
<button class="btn btn-outline-primary w-100" onclick="searchMessagesFromMobile()">
<i class="fas fa-search"></i>
{{ t('ui.common.search', {}, locale, ['common']) }}
</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Messages Column -->
<div class="col-lg-9">
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0">
{% if echoarea %}
{{ echoarea }} Messages
{% else %}
{{ t('ui.echomail.recent_messages', {}, locale, ['common']) }}
{% endif %}
</h6>
<div class="d-flex align-items-center gap-2">
<div id="bulkActions" class="d-none">
<span class="me-2 text-muted">{{ t('ui.common.selected', {}, locale, ['common']) }}: <span id="selectedCount">0</span></span>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="markSelectedAsRead()" disabled>
<i class="fas fa-envelope-open"></i>
{{ t('ui.common.mark_as_read', {}, locale, ['common']) }}
</button>
{% if current_user.is_admin %}
<button type="button" class="btn btn-outline-danger btn-sm" onclick="deleteSelectedMessages()" disabled>
<i class="fas fa-trash"></i>
{{ t('ui.netmail.delete_selected', {}, locale, ['common']) }}
</button>
{% endif %}
</div>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-secondary" onclick="toggleSelectMode()" id="selectModeBtn">
<i class="fas fa-check-square"></i>
{{ t('ui.common.select', {}, locale, ['common']) }}
</button>
<button type="button" class="btn btn-outline-secondary" onclick="refreshMessages()">
<i class="fas fa-sync-alt"></i>
</button>
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-sort"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item sort-option" data-sort="date_desc" href="#" onclick="sortMessages('date_desc')">{{ t('ui.common.sort.newest_first', {}, locale, ['common']) }}</a></li>
<li><a class="dropdown-item sort-option" data-sort="date_asc" href="#" onclick="sortMessages('date_asc')">{{ t('ui.common.sort.oldest_first', {}, locale, ['common']) }}</a></li>
<li><a class="dropdown-item sort-option" data-sort="subject" href="#" onclick="sortMessages('subject')">{{ t('ui.common.sort.by_subject', {}, locale, ['common']) }}</a></li>
<li><a class="dropdown-item sort-option" data-sort="author" href="#" onclick="sortMessages('author')">{{ t('ui.common.sort.by_author', {}, locale, ['common']) }}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="toggleThreading()">
<i class="fas fa-project-diagram"></i>
<span id="threadingToggleText">{{ t('ui.common.threading.show_threaded', {}, locale, ['common']) }}</span>
</a></li>
</ul>
</div>
</div>
</div>
<!-- Read/Unread Filter Tabs -->
<ul class="nav nav-tabs card-header-tabs mt-2">
<li class="nav-item">
<a class="nav-link active" href="#" onclick="setFilter('all')" id="allTab">
<i class="fas fa-list"></i> {{ t('ui.common.all_messages', {}, locale, ['common']) }}
<span class="badge bg-secondary ms-1" id="allCount">0</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="setFilter('unread')" id="unreadTab">
<i class="fas fa-envelope"></i> {{ t('ui.common.unread', {}, locale, ['common']) }}
<span class="badge bg-primary ms-1" id="unreadCount">0</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="setFilter('read')" id="readTab">
<i class="far fa-envelope-open"></i> {{ t('ui.common.read', {}, locale, ['common']) }}
<span class="badge bg-success ms-1" id="readCount">0</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="setFilter('tome')" id="toMeTab">
<i class="fas fa-user"></i> {{ t('ui.echomail.to_me', {}, locale, ['common']) }}
<span class="badge bg-info ms-1" id="toMeCount">0</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="setFilter('saved')" id="savedTab">
<i class="fas fa-bookmark"></i> {{ t('ui.echomail.saved_items', {}, locale, ['common']) }}
<span class="badge bg-warning ms-1" id="savedCount">0</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="setFilter('drafts')" id="draftsTab">
<i class="fas fa-edit"></i> {{ t('ui.common.drafts', {}, locale, ['common']) }}
<span class="badge bg-secondary ms-1" id="draftsCount">0</span>
</a>
</li>
</ul>
</div>
<div class="card-body p-0">
<div id="messagesContainer">
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin me-2"></i>
{{ t('ui.common.loading_messages', {}, locale, ['common']) }}
</div>
</div>
</div>
<div class="card-footer">
<nav id="pagination">
<!-- Pagination will be inserted here -->
</nav>
</div>
</div>
</div>
<!-- Echo Areas Sidebar - Hidden on mobile -->
<div class="col-lg-3 d-none d-lg-block">
<div class="card">
<div class="card-header p-0">
<div class="d-flex justify-content-between align-items-center px-3 pt-2 pb-1">
<h6 class="mb-0">{{ t('ui.echomail.echo_areas', {}, locale, ['common']) }}</h6>
<div class="d-flex gap-2">
<a href="/echolist" class="" title="{{ t('ui.echomail.full_echo_list', {}, locale, ['common']) }}">
<i class="fa-solid fa-list"></i>
</a>
<a href="/subscriptions" class="" id="desktopManageLink" title="{{ t('ui.echomail.manage_subscriptions', {}, locale, ['common']) }}">
<i class="fa-solid fa-wrench"></i>
</a>
</div>
</div>
{% if interests_enabled and has_interests %}
<ul class="nav nav-tabs nav-fill px-2 mb-0" id="desktopViewingTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active py-1 small" id="desktopAreaListTab"
data-bs-toggle="tab" data-bs-target="#desktopAreaListPane"
type="button" role="tab">
{{ t('ui.echomail.tab_area_list', {}, locale, ['common']) }}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link py-1 small" id="desktopInterestsTab"
data-bs-toggle="tab" data-bs-target="#desktopInterestsPane"
type="button" role="tab">
{{ t('ui.echomail.tab_interests', {}, locale, ['common']) }}
</button>
</li>
</ul>
{% endif %}
</div>
<div class="card-body p-0">
{% if interests_enabled and has_interests %}
<div class="tab-content">
<div class="tab-pane fade show active p-0" id="desktopAreaListPane" role="tabpanel">
<div class="px-2 pt-2">
<select class="form-select form-select-sm mb-2" id="areaListInterestFilter"
onchange="setAreaListInterestFilter(this.value ? parseInt(this.value) : null)">
<option value="">{{ t('ui.echomail.all_subscribed_areas', {}, locale, ['common']) }}</option>
</select>
<input type="text" class="form-control form-control-sm" id="echoareaSearchInput"
placeholder="{{ t('ui.echomail.search_areas_placeholder', {}, locale, ['common']) }}" onkeyup="searchEchoareas(this.value)">
</div>
<div style="max-height: 268px; overflow-y: auto;">
<div id="echoareasList">
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin me-2"></i>
{{ t('ui.echomail.loading_areas', {}, locale, ['common']) }}
</div>
</div>
</div>
</div>
<div class="tab-pane fade p-0" id="desktopInterestsPane" role="tabpanel">
<div style="max-height: 300px; overflow-y: auto;">
<div id="desktopInterestsList">
<div class="text-center text-muted p-3">
<i class="fas fa-spinner fa-spin me-2"></i>
{{ t('ui.echomail.loading_areas', {}, locale, ['common']) }}
</div>
</div>
</div>
</div>
</div>
{% else %}
<div class="px-2 pt-2">
<input type="text" class="form-control form-control-sm" id="echoareaSearchInput"
placeholder="{{ t('ui.echomail.search_areas_placeholder', {}, locale, ['common']) }}" onkeyup="searchEchoareas(this.value)">
</div>
<div style="max-height: 268px; overflow-y: auto;">
<div id="echoareasList">
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin me-2"></i>
{{ t('ui.echomail.loading_areas', {}, locale, ['common']) }}
</div>
</div>
</div>
{% endif %}
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h6 class="mb-0">{{ t('ui.common.search_messages', {}, locale, ['common']) }}</h6>
</div>
<div class="card-body">
<div class="mb-3">
<input type="text" class="form-control form-control-sm" id="searchInput" placeholder="{{ t('ui.common.search_placeholder', {}, locale, ['common']) }}">
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-primary btn-sm flex-fill" onclick="searchMessages()">
<i class="fas fa-search"></i>
{{ t('ui.common.search', {}, locale, ['common']) }}
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="openAdvancedSearch()" title="{{ t('ui.common.advanced_search', {}, locale, ['common']) }}">
<i class="fas fa-sliders-h"></i>
</button>
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h6 class="mb-0">{{ t('ui.common.statistics', {}, locale, ['common']) }}</h6>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-6">{{ t('ui.common.total', {}, locale, ['common']) }}:</dt>
<dd class="col-6" id="totalMessages">-</dd>
<dt class="col-6">{{ t('ui.common.unread', {}, locale, ['common']) }}:</dt>
<dd class="col-6" id="unreadMessages">-</dd>
<dt class="col-6">{{ t('ui.common.recent', {}, locale, ['common']) }}:</dt>
<dd class="col-6" id="recentMessages">-</dd>
<dt class="col-6">{{ t('ui.echomail.areas', {}, locale, ['common']) }}:</dt>
<dd class="col-6" id="totalAreas">-</dd>
</dl>
</div>
</div>
</div>
</div>
<!-- Manage Subscriptions / Interests Button -->
<div class="text-center mt-4 mb-4">
<a href="/subscriptions" id="desktopBottomManageBtn" class="btn btn-outline-primary">
<i class="fa-solid fa-wrench me-2"></i>
<span id="desktopBottomManageBtnLabel">{{ t('ui.echomail.manage_subscriptions', {}, locale, ['common']) }}</span>
</a>
</div>
<div id="messageContextMenu" class="dropdown-menu" style="display:none; position:absolute; z-index:1080;">
<button type="button" class="dropdown-item" onclick="replyFromContextMenu()">
<i class="fas fa-reply me-2"></i>{{ t('ui.common.reply', {}, 'common') }}
</button>
<button type="button" class="dropdown-item" onclick="repostFromContextMenu()">
<i class="fas fa-share me-2"></i>{{ t('ui.common.repost', {}, 'common') }}
</button>
<button type="button" class="dropdown-item" onclick="viewConversationFromContextMenu()">
<i class="fas fa-comments me-2"></i>{{ t('ui.common.view_conversation', {}, locale, ['common']) }}
</button>
<button type="button" class="dropdown-item" id="messageContextMenuSaveAction" onclick="toggleSaveFromContextMenu()">
<i class="fas fa-bookmark me-2"></i><span id="messageContextMenuSaveLabel">{{ t('ui.common.save_for_later', {}, locale, ['common']) }}</span>
</button>
<button type="button" class="dropdown-item" onclick="downloadMessageFromContextMenu()">
<i class="fas fa-download me-2"></i>{{ t('ui.common.download_message', {}, locale, ['common']) }}
</button>
<button type="button" class="dropdown-item" onclick="shareMessageFromContextMenu()">
<i class="fas fa-share-alt me-2"></i>{{ t('ui.common.share', {}, locale, ['common']) }}
</button>
<button type="button" class="dropdown-item" onclick="forwardByNetmailFromContextMenu()">
<i class="fas fa-envelope me-2"></i>{{ t('ui.common.forward_by_netmail', {}, 'common') }}
</button>
{% if current_user.email %}
<button type="button" class="dropdown-item" onclick="forwardMessageToEmailFromContextMenu()">
<i class="fas fa-envelope me-2"></i>{{ t('ui.common.forward_to_me_by_email', {}, locale, ['common']) }}
</button>
{% endif %}
<button type="button" class="dropdown-item" onclick="openIgnoreMessageModalFromContextMenu()">
<i class="fas fa-eye-slash me-2"></i>{{ t('ui.echomail.ignore.open_action', {}, locale, ['common']) }}
</button>
</div>
<div class="modal fade" id="ignoreMessageModal" tabindex="-1" aria-labelledby="ignoreMessageModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form id="ignoreMessageForm">
<div class="modal-header">
<h5 class="modal-title" id="ignoreMessageModalLabel">{{ t('ui.echomail.ignore.modal_title', {}, locale, ['common']) }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ t('ui.common.close', {}, locale, ['common']) }}"></button>
</div>
<div class="modal-body">
<p class="text-muted small mb-3">{{ t('ui.echomail.ignore.help', {}, locale, ['common']) }}</p>
<div class="mb-3">
<label for="ignoreMessageSenderDisplay" class="form-label">{{ t('ui.echomail.ignore.sender_label', {}, locale, ['common']) }}</label>
<input type="text" class="form-control" id="ignoreMessageSenderDisplay" maxlength="255" readonly>
<input type="hidden" id="ignoreMessageSender" name="sender_name">
<input type="hidden" id="ignoreMessageSenderAddress" name="sender_address">
</div>
<div class="mb-0">
<label for="ignoreMessageSubjectContains" class="form-label">{{ t('ui.echomail.ignore.subject_contains_label', {}, locale, ['common']) }}</label>
<input type="text" class="form-control" id="ignoreMessageSubjectContains" name="subject_contains" maxlength="255">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('ui.common.cancel', {}, locale, ['common']) }}</button>
<button type="submit" class="btn btn-warning" id="ignoreMessageSubmitButton">
<i class="fas fa-eye-slash me-1"></i>{{ t('ui.echomail.ignore.save_button', {}, locale, ['common']) }}
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Message View Modal -->
<div class="modal fade" id="messageModal" tabindex="-1">
<div class="modal-dialog modal-lg modal-message-reader{% if appearance.message_reader.scrollable_body %} modal-dialog-scrollable message-reader-scrollable{% endif %}">
<div class="modal-content">
<div class="modal-header">
<div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm" id="prevMessageBtn" onclick="navigateMessage(-1)" title="{{ t('ui.common.previous_message', {}, locale, ['common']) }}">
<i class="fas fa-chevron-left"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="nextMessageBtn" onclick="navigateMessage(1)" title="{{ t('ui.common.next_message', {}, locale, ['common']) }}">
<i class="fas fa-chevron-right"></i>
</button>
{% if current_user.is_admin %}
<button type="button" class="btn btn-outline-secondary btn-sm" id="editMessageButton" onclick="openEditMessage()" title="{{ t('ui.common.edit', {}, locale, ['common']) }}">
<i class="fas fa-pencil-alt"></i>
</button>
{% endif %}
<button type="button" class="btn btn-outline-secondary btn-sm" id="toggleHeaders" onclick="toggleKludgeLines()" title="{{ t('ui.common.show_kludge_lines', {}, locale, ['common']) }}">
<i class="fas fa-code" id="toggleIcon"></i>
</button>
{% if ai_assistant_enabled %}
<button type="button" class="btn btn-outline-secondary btn-sm" id="modalAiAssistantButton" onclick="openAiAssistantFromMessageModal()" title="{{ t('ui.ai_assistant.button', {}, locale, ['common']) }}">
<i class="fas fa-robot"></i>
</button>
{% endif %}
<button type="button" class="btn btn-outline-secondary btn-sm" id="printMessageButton" onclick="printMessage()" title="{{ t('ui.common.print', {}, locale, ['common']) }}">
<i class="fas fa-print"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="toggleKeyboardHelp()" title="{{ t('ui.echomail.shortcuts.title', {}, locale, ['common']) }}">
<i class="fas fa-keyboard"></i>
</button>
</div>
<h5 class="modal-title" id="messageSubject">{{ t('ui.common.message', {}, locale, ['common']) }}</h5>
<button type="button" class="btn btn-outline-secondary btn-sm me-2" id="fullscreenToggle" onclick="toggleModalFullscreen()" title="{{ t('ui.common.toggle_fullscreen', {}, locale, ['common']) }}">
<i class="fas fa-expand"></i>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="messageContent">
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin me-2"></i>
{{ t('ui.common.loading_message', {}, locale, ['common']) }}
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-compact-label" data-bs-dismiss="modal">
<i class="fas fa-times"></i>
<span class="btn-text">{{ t('ui.common.close', {}, locale, ['common']) }}</span>
</button>
<div class="btn-group">
<button type="button" class="btn btn-outline-warning btn-compact-label" id="modalSaveButton">
<i class="fas fa-bookmark"></i>
<span class="btn-text" id="modalSaveText">{{ t('ui.common.save', {}, locale, ['common']) }}</span>
</button>
<button type="button" class="btn btn-outline-info btn-compact-label" id="shareButton">
<i class="fas fa-share-alt"></i>
<span class="btn-text">{{ t('ui.common.share', {}, locale, ['common']) }}</span>
</button>
<button type="button" class="btn btn-fidonet btn-compact-label" id="replyButton">
<i class="fas fa-reply"></i>
<span class="btn-text">{{ t('ui.common.reply', {}, locale, ['common']) }}</span>
</button>
<button type="button" class="btn btn-outline-primary btn-compact-label" id="repostButton">
<i class="fas fa-share"></i>
<span class="btn-text">{{ t('ui.common.repost', {}, locale, ['common']) }}</span>
</button>
</div>
</div>
{# Keyboard shortcuts help overlay ? covers the modal body area #}
<div id="keyboardHelpOverlay" style="display:none; position:absolute; inset:0; z-index:20; background:rgba(0,0,0,0.82); padding:2rem; overflow-y:auto;">
<div class="mx-auto" style="max-width:480px;">
<h5 class="text-white mb-3"><i class="fas fa-keyboard me-2"></i>{{ t('ui.echomail.shortcuts.title', {}, locale, ['common']) }}</h5>
<table class="table table-sm table-dark table-borderless mb-3">
<tbody>
<tr>
<td class="text-center" style="width:120px;">
<kbd>←</kbd> <kbd>→</kbd>
</td>
<td>{{ t('ui.echomail.shortcuts.prev_next', {}, locale, ['common']) }}</td>
</tr>
<tr>
<td class="text-center"><kbd>A</kbd></td>
<td>{{ t('ui.echomail.shortcuts.cycle_viewer_mode', {}, locale, ['common']) }}</td>
</tr>
<tr>
<td class="text-center"><kbd>D</kbd></td>
<td>{{ t('ui.echomail.shortcuts.download', {}, locale, ['common']) }}</td>
</tr>
<tr>
<td class="text-center"><kbd>F</kbd></td>
<td>{{ t('ui.echomail.shortcuts.fullscreen', {}, locale, ['common']) }}</td>
</tr>
<tr>
<td class="text-center"><kbd>?</kbd> <kbd>H</kbd></td>
<td>{{ t('ui.echomail.shortcuts.help', {}, locale, ['common']) }}</td>
</tr>
<tr>
<td class="text-center"><kbd>Esc</kbd></td>
<td>{{ t('ui.echomail.shortcuts.close', {}, locale, ['common']) }}</td>
</tr>
</tbody>
</table>
<p class="text-muted small mb-0">{{ t('ui.echomail.shortcuts.dismiss', {}, locale, ['common']) }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- Share Modal -->
<div class="modal fade share-message-modal" id="shareModal" tabindex="-1">
<div class="modal-dialog share-message-modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ t('ui.echomail.share_message', {}, locale, ['common']) }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
{{ t('ui.echomail.share_message_help', {}, locale, ['common']) }}
</div>
<!-- Other users' share links, populated by JS when shares exist -->
<div id="otherSharesSection" class="d-none mb-3">
<div class="alert alert-secondary py-2">
<strong><i class="fas fa-link me-1"></i>{{ t('ui.echomail.shares.existing_links', {}, locale, ['common']) }}</strong>
<div id="otherSharesList" class="mt-2"></div>
</div>
<hr class="my-2">
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="publicShare" checked>
<label class="form-check-label" for="publicShare">
<strong>{{ t('ui.echomail.allow_anonymous_access', {}, locale, ['common']) }}</strong>
<br><small class="text-muted">{{ t('ui.echomail.allow_anonymous_access_help', {}, locale, ['common']) }}</small>
</label>
</div>
<div class="mb-3" id="shareDescriptionSection">
<label for="shareDescription" class="form-label">{{ t('ui.share.description_label', {}, locale, ['common']) }}</label>
<div class="input-group">
<textarea class="form-control" id="shareDescription" rows="5" placeholder="{{ t('ui.share.description_placeholder', {}, locale, ['common']) }}"></textarea>
{% if ai_share_summary_enabled %}
<button class="btn btn-outline-secondary" type="button" id="aiShareSummaryBtn" title="{{ t('ui.share.ai_summary_button_title', {}, locale, ['common']) }}">
<i class="fas fa-robot" id="aiShareSummaryIcon"></i>
</button>
{% endif %}
</div>
</div>
<div class="mb-3" id="shareExpirySection">
<label for="shareExpiry" class="form-label">{{ t('ui.echomail.link_expires_after', {}, locale, ['common']) }}</label>
<select class="form-select" id="shareExpiry">
<option value="" selected>{{ t('ui.echomail.never_expires', {}, locale, ['common']) }}</option>
<option value="1">{{ t('ui.common.time.1_hour', {}, locale, ['common']) }}</option>
<option value="24">{{ t('ui.common.time.24_hours', {}, locale, ['common']) }}</option>
<option value="168">{{ t('ui.common.time.1_week', {}, locale, ['common']) }}</option>
<option value="720">{{ t('ui.common.time.30_days', {}, locale, ['common']) }}</option>
</select>
</div>
<div id="shareResult" class="d-none">
<label class="form-label">{{ t('ui.common.share_url', {}, locale, ['common']) }}</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="shareUrl" readonly>
<button class="btn btn-outline-secondary" type="button" onclick="copyShareUrl()">
<i class="fas fa-copy"></i>
{{ t('ui.common.copy', {}, locale, ['common']) }}
</button>
</div>
<div id="shareExpiryInfo" class="alert alert-info mb-3" style="display: none;">
<i class="fas fa-clock me-2"></i>
<span id="shareExpiryText"></span>
</div>
<div id="shareAccessInfo" class="alert alert-secondary mb-3" style="display: none;">
<i class="fas fa-chart-bar me-2"></i>
<span id="shareAccessText"></span>
</div>
<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
{{ t('ui.echomail.share_link_created_success', {}, locale, ['common']) }}
</div>
<div class="mb-2">
<label class="form-label">{{ t('ui.share.og_image_label', {}, locale, ['common']) }}</label>
<div class="form-text text-muted mb-2">{{ t('ui.share.og_image_help', {}, locale, ['common']) }}</div>
<div id="shareOgImagePreview" class="mb-2 d-none">
<img id="shareOgImageThumb" src="" alt="Preview" class="img-thumbnail" style="max-height:120px;">
</div>
<div id="shareOgImageUploadControls" class="d-flex gap-2 align-items-center flex-wrap">
<input type="file" id="shareOgImageInput" accept="image/*" class="form-control form-control-sm" style="max-width:220px;">
<button type="button" class="btn btn-sm btn-outline-secondary" id="shareOgImageUploadBtn">
<i class="fas fa-upload me-1"></i>{{ t('ui.share.og_image_upload_btn', {}, locale, ['common']) }}
</button>
</div>
<div id="shareOgImageRemoveControls" class="d-none">
<button type="button" class="btn btn-sm btn-outline-danger" id="shareOgImageRemoveBtn">
<i class="fas fa-trash me-1"></i>{{ t('ui.share.og_image_remove_btn', {}, locale, ['common']) }}
</button>
</div>
</div>
</div>
<div id="shareError" class="d-none">
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
<span id="shareErrorMessage"></span>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('ui.common.close', {}, locale, ['common']) }}</button>
<button type="button" class="btn btn-primary" id="createShareBtn">
<i class="fas fa-share-alt"></i>
{{ t('ui.echomail.create_share_link', {}, locale, ['common']) }}
</button>
<button type="button" class="btn btn-outline-primary d-none" id="friendlyUrlBtn" title="Generate a readable URL based on the echo area and subject">
<i class="fas fa-link"></i>
{{ t('ui.echomail.get_friendly_url', {}, locale, ['common']) }}
</button>
<button type="button" class="btn btn-danger d-none" id="revokeShareBtn">
<i class="fas fa-trash"></i>
{{ t('ui.echomail.revoke_share', {}, locale, ['common']) }}
</button>
</div>
</div>
</div>
</div>
<!-- Advanced Search Modal -->
<div class="modal fade" id="advancedSearchModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-sliders-h me-2"></i>{{ t('ui.common.advanced_search.title', {}, locale, ['common']) }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-muted small mb-3">{{ t('ui.common.advanced_search.help', {}, locale, ['common']) }}</p>
<div class="mb-3">
<label for="advSearchFromName" class="form-label">{{ t('ui.common.advanced_search.from_name', {}, locale, ['common']) }}</label>
<input type="text" class="form-control" id="advSearchFromName" placeholder="{{ t('ui.common.advanced_search.from_name_placeholder', {}, locale, ['common']) }}">
</div>
<div class="mb-3">
<label for="advSearchSubject" class="form-label">{{ t('ui.common.advanced_search.subject', {}, locale, ['common']) }}</label>
<input type="text" class="form-control" id="advSearchSubject" placeholder="{{ t('ui.common.advanced_search.subject_placeholder', {}, locale, ['common']) }}">
</div>
<div class="mb-3">
<label for="advSearchBody" class="form-label">{{ t('ui.common.advanced_search.body', {}, locale, ['common']) }}</label>
<input type="text" class="form-control" id="advSearchBody" placeholder="{{ t('ui.common.advanced_search.body_placeholder', {}, locale, ['common']) }}">
</div>
<div class="mb-3">
<label for="advSearchMessageId" class="form-label">{{ t('ui.common.advanced_search.message_id', {}, locale, ['common']) }}</label>
<input type="text" class="form-control" id="advSearchMessageId" placeholder="{{ t('ui.common.advanced_search.message_id_placeholder', {}, locale, ['common']) }}">
</div>
<div class="mb-3">
<label class="form-label">{{ t('ui.common.advanced_search.date_range', {}, locale, ['common']) }}</label>
<div class="d-flex gap-2 align-items-center">
<input type="date" class="form-control" id="advSearchDateFrom">
<span class="text-muted flex-shrink-0">?</span>
<input type="date" class="form-control" id="advSearchDateTo">
</div>
</div>
<div id="advSearchError" class="alert alert-danger d-none"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('ui.common.cancel', {}, locale, ['common']) }}</button>
<button type="button" class="btn btn-primary" onclick="runAdvancedSearch()">
<i class="fas fa-search me-1"></i>{{ t('ui.common.search', {}, locale, ['common']) }}
</button>
</div>
</div>
</div>
</div>
{% if current_user.is_admin %}
<!-- Edit Message Modal (admin only) -->
<div class="modal fade" id="editMessageModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editMessageModalTitle"><i class="fas fa-pencil-alt me-2"></i>{{ t('ui.echomail.edit_message', {}, locale, ['common']) }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<table class="table table-sm table-borderless mb-3">
<tbody>
<tr>
<th class="text-muted small fw-normal ps-0" style="width:30%">{{ t('ui.common.db_id', {}, locale, ['common']) }}</th>
<td class="small font-monospace" id="editMsgDbId"></td>
</tr>
<tr>
<th class="text-muted small fw-normal ps-0">{{ t('ui.common.message_id', {}, locale, ['common']) }}</th>
<td class="small font-monospace" id="editMsgId"></td>
</tr>
<tr>
<th class="text-muted small fw-normal ps-0">{{ t('ui.common.date_label', {}, locale, ['common']) }}</th>
<td class="small" id="editMsgDate"></td>
</tr>
<tr>
<th class="text-muted small fw-normal ps-0">{{ t('ui.common.from_label', {}, locale, ['common']) }}</th>
<td class="small" id="editMsgFrom"></td>
</tr>
<tr>
<th class="text-muted small fw-normal ps-0">{{ t('ui.common.subject_label', {}, locale, ['common']) }}</th>
<td class="small" id="editMsgSubject"></td>
</tr>
</tbody>
</table>
<hr>
<div class="mb-3">
<label for="editArtFormat" class="form-label">{{ t('ui.echomail.art_format', {}, locale, ['common']) }}</label>
<select class="form-select" id="editArtFormat">
<option value="">{{ t('ui.echomail.art_format_auto', {}, locale, ['common']) }}</option>
<option value="plain">{{ t('ui.echomail.art_format_plain', {}, locale, ['common']) }}</option>
<option value="ansi">{{ t('ui.echomail.art_format_ansi', {}, locale, ['common']) }}</option>
<option value="amiga_ansi">{{ t('ui.echomail.art_format_amiga_ansi', {}, locale, ['common']) }}</option>
</select>
</div>
<div class="mb-3">
<label for="editCharset" class="form-label">{{ t('ui.echomail.message_charset', {}, locale, ['common']) }}</label>
<select class="form-select" id="editCharset">
{% for cs in supported_charsets %}
<option value="{{ cs.value }}">{{ cs.label }}</option>
{% endfor %}
</select>
</div>
<div id="editMessageError" class="alert alert-danger d-none"></div>
<div id="editMessageSuccess" class="alert alert-success d-none">
<i class="fas fa-check-circle me-2"></i>{{ t('ui.echomail.edit_message_saved', {}, locale, ['common']) }}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('ui.common.close', {}, locale, ['common']) }}</button>
<button type="button" class="btn btn-primary" id="saveEditMessageBtn" onclick="saveEditMessage()">
<i class="fas fa-save me-1"></i>{{ t('ui.common.save', {}, locale, ['common']) }}
</button>
</div>
</div>
</div>
</div>
{% endif %}
{% if ai_assistant_enabled %}
{% include 'partials/ai_assistant_modal.twig' %}
{% endif %}
{% endblock %}
{% block styles %}
<style>
/* Style for clickable from names in echomail */
.message-from a {
color: #0d6efd !important;
font-weight: 500;
border-bottom: 1px dotted #0d6efd;
transition: all 0.2s ease;
}
.message-from a:hover {
color: #0a58ca !important;
border-bottom: 1px solid #0a58ca;
text-decoration: none !important;
}
/* Ensure the link stands out from the cell background */
.message-row.read .message-from a {
color: #6c757d !important;
}
.message-row.read .message-from a:hover {
color: #495057 !important;
}
.message-row.unread .message-from a {
color: #0d6efd !important;
font-weight: 600;
}
.message-row.unread .message-from a:hover {
color: #0a58ca !important;
}
</style>
{% endblock %}
{% block scripts %}
<script>
// Twig-rendered variables
let currentEchoarea = {{ echoarea|default(null)|json_encode|raw }};
let domain = {{ domain|default(null)|json_encode|raw }};
window.currentUserRealName = {{ current_user.real_name|json_encode|raw }};
window.isAdmin = {{ current_user.is_admin ? 'true' : 'false' }};
window.echomailDateField = '{{ echomail_date_field|default("received") }}';
</script>
{% if interests_enabled %}
<script src="/js/ansisys.js"></script>
<script src="/js/sixel.js"></script>
<script src="/js/pcboard.js"></script>
<script src="/js/message-list-context-menu.js"></script>
<script src="/js/file-preview.js"></script>
{% endif %}
<script src="/js/echomail.js"></script>
<script src="/vendor/marked.min.js"></script>
{% if ai_assistant_enabled %}
<script src="/js/ai-assistant.js?v=1"></script>
{% endif %}
{% if interests_enabled and has_interests %}
<script>
$(document).ready(function() {
function setListOrInterestPreference(value) {
document.cookie = `p_listorinterest=${encodeURIComponent(value)}; path=/; max-age=31536000; SameSite=Lax`;
}
function getListOrInterestPreference() {
const match = document.cookie.match(/(?:^|;\s*)p_listorinterest=([^;]+)/);
if (!match) {
return null;
}
const value = decodeURIComponent(match[1] || '');
return (value === 'area' || value === 'interests') ? value : null;
}
function getListOrInterestPreferenceForTab(tabId) {
return tabId === 'mobileInterestsTab' || tabId === 'desktopInterestsTab' ? 'interests' : 'area';
}
function activatePreferredTab(preference) {
const mobileTabId = preference === 'interests' ? 'mobileInterestsTab' : 'mobileAreaListTab';
const desktopTabId = preference === 'interests' ? 'desktopInterestsTab' : 'desktopAreaListTab';
const mobileTab = document.getElementById(mobileTabId);
const desktopTab = document.getElementById(desktopTabId);
if (mobileTab) {
bootstrap.Tab.getOrCreateInstance(mobileTab).show();
}
if (desktopTab) {
bootstrap.Tab.getOrCreateInstance(desktopTab).show();
}
}
// Load interests list immediately (Interests tab is active by default)
$.get('/api/interests')
.done(function(data) {
if (data && data.interests) {
const subscribedInterests = data.interests.filter(function(i) { return i.subscribed; });
displayMobileInterests(subscribedInterests);
populateAreaListInterestDropdowns(subscribedInterests);
// Auto-select interest from ?interest=slug URL param (e.g. returning from compose)
const urlInterestSlug = new URLSearchParams(window.location.search).get('interest');
if (urlInterestSlug) {
const match = data.interests.find(function(i) {
return i.slug === urlInterestSlug || String(i.id) === urlInterestSlug;
});
if (match) {
selectInterest(match.id, match.name, match.slug);
}
}
}
})
.fail(function() {
const errHtml = '<div class="text-center text-danger p-3">' +
uiT('ui.interests.load_failed', 'Failed to load interests') +
'</div>';
$('#mobileInterestsList').html(errHtml);
$('#desktopInterestsList').html(errHtml);
});
// Swap the mobile manage button label/link based on active tab
function updateMobileManageBtn(tabId) {
const btn = $('#mobileManageBtn');
const label = $('#mobileManageBtnLabel');
if (tabId === 'mobileInterestsTab') {
btn.attr('href', '/interests');
label.text(uiT('ui.echomail.manage_interests', 'Manage Interests'));
} else {
btn.attr('href', '/subscriptions');
label.text(uiT('ui.echomail.manage_subscriptions', 'Manage Subscriptions'));
}
}
// Swap the desktop manage link + bottom button based on active tab
function updateDesktopManageBtn(tabId) {
const link = $('#desktopManageLink');
const bottomBtn = $('#desktopBottomManageBtn');
const bottomLabel = $('#desktopBottomManageBtnLabel');
if (tabId === 'desktopInterestsTab') {
link.attr('href', '/interests').attr('title', uiT('ui.echomail.manage_interests', 'Manage Interests'));
bottomBtn.attr('href', '/interests');
bottomLabel.text(uiT('ui.echomail.manage_interests', 'Manage Interests'));
} else {
link.attr('href', '/subscriptions').attr('title', uiT('ui.echomail.manage_subscriptions', 'Manage Subscriptions'));
bottomBtn.attr('href', '/subscriptions');
bottomLabel.text(uiT('ui.echomail.manage_subscriptions', 'Manage Subscriptions'));
}
}
// Restore preferred tab, defaulting to area list unless the URL explicitly opens an interest.
const initialPreference = new URLSearchParams(window.location.search).get('interest')
? 'interests'
: (getListOrInterestPreference() || 'area');
activatePreferredTab(initialPreference);
updateMobileManageBtn(initialPreference === 'interests' ? 'mobileInterestsTab' : 'mobileAreaListTab');
updateDesktopManageBtn(initialPreference === 'interests' ? 'desktopInterestsTab' : 'desktopAreaListTab');
setListOrInterestPreference(initialPreference);
// Update on mobile tab switch
$('#mobileViewingTabs button[data-bs-toggle="tab"]').on('shown.bs.tab', function() {
const tabId = $(this).attr('id');
updateMobileManageBtn(tabId);
setListOrInterestPreference(getListOrInterestPreferenceForTab(tabId));
});
// Update on desktop tab switch
$('#desktopViewingTabs button[data-bs-toggle="tab"]').on('shown.bs.tab', function() {
const tabId = $(this).attr('id');
updateDesktopManageBtn(tabId);
setListOrInterestPreference(getListOrInterestPreferenceForTab(tabId));
});
});
</script>
{% endif %}
<style>
/* Read/Unread message styling */
.message-row.unread {
background-color: #f8f9fa;
}
/* Echoarea badge styling in subject column */
.message-subject .badge {
font-size: 0.75rem;
margin-bottom: 0.25rem;
}
.message-row.unread .message-from,
.message-row.unread .message-subject {
font-weight: 600;
}
.message-row.read {
opacity: 0.85;
}
/* Mobile-specific improvements */
@media (max-width: 991.98px) {
/* Mobile accordion styling */
#mobileEchoAreasAccordion .accordion-button {
font-weight: 500;
padding: 1rem;
background-color: #f8f9fa;
border: 2px solid #dee2e6;
text-align: left;
}
#mobileEchoAreasAccordion .accordion-button:not(.collapsed) {
background-color: #e7f3ff;
border-color: #0d6efd;
color: #0d6efd;
box-shadow: none;
}
#mobileEchoAreasAccordion .accordion-button:focus {
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
border-color: #0d6efd;
}
/* Mobile areas list styling */
#mobileEchoareasList .list-group-item {
border: none;
border-bottom: 1px solid #dee2e6;
padding: 0.75rem 1rem;
cursor: pointer;
}
#mobileEchoareasList .list-group-item:hover {
background-color: #f8f9fa;
}
#mobileEchoareasList .list-group-item.active {
background-color: #0d6efd;
border-color: #0d6efd;
}
/* Enhance message table readability on mobile */
.message-table {
/*font-size: 0.9rem;*/
font-size: 1em;
}
.message-table th,
.message-table td {
padding: 0.5rem;
vertical-align: middle;
}
}
/* Small mobile devices */
@media (max-width: 575.98px) {
.message-table {
font-size: 0.8rem;
}
.badge {
font-size: 0.7rem;
}
/* Keep all columns visible since echoarea is now integrated into subject */
/* Echoarea badge styling in message subject */
.message-subject .badge {
font-size: 0.6rem;
margin-bottom: 0.25rem;
}
}
/* Sharing modal styles */
.share-message-modal-dialog {
--bs-modal-width: 760px;
}
.modal-dialog .alert {
margin-bottom: 1rem;
}
.share-message-modal .modal-body .form-check {
border: 1px solid var(--border-color, #e9ecef);
border-radius: 0.375rem;
padding: 0.75rem;
background-color: var(--bg-secondary, var(--message-bg, #f8f9fa));
position: relative;
}
.share-message-modal .modal-body .form-check:hover {
background-color: var(--message-quote-bg, #e9ecef);
}
.share-message-modal .modal-body .form-check-input {
position: relative;
margin-left: 0;
margin-right: 0.5rem;
}
.share-message-modal .modal-body .form-check-label {
margin-left: 0;
}
.share-message-modal .modal-body .form-check-input:checked ~ .form-check-label {
color: #0d6efd;
}
.share-message-modal .modal-body .input-group {
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);
border-radius: 0.375rem;
}
.share-message-modal .modal-body .input-group .form-control {
font-family: 'Courier New', Monaco, Consolas, monospace;
font-size: 0.875rem;
background-color: var(--bg-secondary, var(--message-bg, #f8f9fa));
color: var(--text-color, #212529);
border: 1px solid var(--border-color, #dee2e6);
}
.share-message-modal .modal-body .input-group .form-control:focus {
background-color: var(--bg-secondary, var(--message-bg, #f8f9fa));
color: var(--text-color, #212529);
border-color: var(--fidonet-blue, #0d6efd);
}
.share-message-modal .modal-body .input-group .btn {
border-left: none;
}
.modal-footer .btn-group {
gap: 0.25rem;
}
.modal-footer .btn-group .btn {
border-radius: 0.375rem !important;
}
#shareButton {
position: relative;
}
#shareButton:hover {
background-color: #17a2b8;
border-color: #17a2b8;
color: white;
}
/* Share result styling */
#shareResult .alert-success {
border: 1px solid #d1e7dd;
background-color: #d1e7dd;
color: #0f5132;
}
#shareResult .alert-success .fas {
color: #0f5132;
}
/* Share error styling */
#shareError .alert-danger {
border: 1px solid #f5c2c7;
background-color: #f8d7da;
color: #842029;
}
#shareError .alert-danger .fas {
color: #842029;
}
/* Loading states */
.btn .fa-spinner {
animation: fa-spin 1s infinite linear;
}
/* Responsive modal adjustments */
@media (max-width: 576px) {
.share-message-modal-dialog {
--bs-modal-width: calc(100vw - 1rem);
}
.modal-dialog {
margin: 0.5rem;
}
.modal-body {
padding: 1rem;
}
.modal-body .input-group .form-control {
font-size: 0.75rem;
}
.modal-footer {
flex-direction: row;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: center;
}
.modal-footer .btn-group {
width: auto;
flex-direction: row;
}
.modal-footer .btn {
width: auto;
}
.btn-compact-label {
padding: 0.4rem 0.55rem;
font-size: 0.85rem;
}
.btn-compact-label .btn-text {
display: none;
}
}
/* Threading styles with colored left borders */
.thread-root {
border-left: 3px solid #0d6efd;
background-color: rgba(13, 110, 253, 0.05);
}
.thread-reply {
background-color: rgba(108, 117, 125, 0.02);
}
.thread-level-1 {
border-left: 2px solid #6c757d;
background-color: rgba(108, 117, 125, 0.05);
}
.thread-level-2 {
border-left: 2px solid #28a745;
background-color: rgba(40, 167, 69, 0.05);
}
.thread-level-3 {
border-left: 2px solid #ffc107;
background-color: rgba(255, 193, 7, 0.05);
}
.thread-level-4, .thread-level-5, .thread-level-6, .thread-level-7, .thread-level-8, .thread-level-9 {
border-left: 2px solid #dc3545;
background-color: rgba(220, 53, 69, 0.05);
}
/* Thread root styling enhancement */
.thread-root .message-subject {
font-weight: 500;
}
/* Reply badge styling */
.thread-root .badge.bg-secondary {
background-color: #0d6efd !important;
font-size: 0.7em;
}
/* Hover effects for threads */
.thread-root:hover {
background-color: rgba(13, 110, 253, 0.1) !important;
}
.thread-reply:hover {
background-color: rgba(108, 117, 125, 0.1) !important;
}
/* Reply indentation handled inline per thread level */
/* Mobile threading adjustments */
@media (max-width: 991.98px) {
.thread-level-1, .thread-level-2, .thread-level-3,
.thread-level-4, .thread-level-5, .thread-level-6,
.thread-level-7, .thread-level-8, .thread-level-9 {
border-left-width: 1px;
}
.thread-root {
border-left-width: 2px;
}
}
@media (max-width: 575.98px) {
}
/* Green text for messages addressed to current user */
.text-green {
color: #198754 !important;
}
/* Search term highlighting */
.search-highlight {
background-color: #ffff00 !important;
color: #000000 !important;
font-weight: 600;
padding: 0.1em 0.2em;
border-radius: 0.2em;
box-shadow: 0 0 0 1px #cccc00;
}
/* Ensure highlighting works in different contexts */
.message-formatted .search-highlight,
.message-preformatted .search-highlight,
.quote-line .search-highlight,
.message-line .search-highlight,
.message-signature .search-highlight {
background-color: #ffff00 !important;
color: #000000 !important;
font-weight: 600;
padding: 0.1em 0.2em;
border-radius: 0.2em;
box-shadow: 0 0 0 1px #cccc00;
}
/* Dark mode support for search highlighting */
@media (prefers-color-scheme: dark) {
.search-highlight,
.message-formatted .search-highlight,
.message-preformatted .search-highlight,
.quote-line .search-highlight,
.message-line .search-highlight,
.message-signature .search-highlight {
background-color: #ffff66 !important;
color: #000000 !important;
box-shadow: 0 0 0 1px #cccc00;
}
}
/* Threading icon styling */
.message-from .fa-reply {
color: #6c757d;
font-size: 0.85em;
margin-right: 0.25rem;
}
/* Enhanced threading icon visibility */
.thread-reply .message-from .fa-reply {
color: #0d6efd;
}
/* Message modal navigation buttons */
.modal-header .btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
min-width: 32px;
}
.modal-header .btn-sm:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.modal-header .btn-sm i {
font-size: 0.75rem;
}
/* Modal header layout improvements */
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header .d-flex {
flex-shrink: 0;
}
.modal-title {
flex: 1;
text-align: center;
margin: 0 1rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Mobile modal navigation adjustments */
@media (max-width: 576px) {
.modal-header .btn-sm {
padding: 0.2rem 0.4rem;
font-size: 0.8rem;
min-width: 28px;
}
.modal-header .btn-sm i {
font-size: 0.7rem;
}
.modal-title {
font-size: 1rem;
margin: 0 0.5rem;
}
}
/* Touch/swipe improvements */
.modal-dialog {
touch-action: pan-y; /* Allow vertical scrolling but enable horizontal swipe detection */
}
.modal-content {
overflow: hidden; /* Prevent content from extending beyond modal bounds during swipes */
}
/* Add a subtle hint for swipe functionality on touch devices */
@media (pointer: coarse) {
.modal-header::after {
content: "? Swipe to navigate ?";
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
font-size: 0.7rem;
color: #6c757d;
opacity: 0.7;
white-space: nowrap;
pointer-events: none;
z-index: 1;
background: rgba(255, 255, 255, 0.9);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
animation: fadeInOut 4s ease-in-out;
}
}
@keyframes fadeInOut {
0%, 15%, 85%, 100% { opacity: 0; }
30%, 70% { opacity: 0.7; }
}
</style>
{% endblock %}
|