mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-11-04 01:32:21 +01:00
~100 keys, finishing stage 5. Catalog now 471 keys, pl at 100%. Covers the cleanup form's validation and progress messages, the protect/block/ ignore/approve button set, bulk approve and ignore with their partial-failure reports, the QR and URI add flows, and every toast in the file. Folded in the fourth and last copy of relative-time formatting. contacts.js had its own formatRelativeTime with more granularity than the shared helper — months and years instead of falling back to a date past a week. Rather than drop that, formatTimeAgo gained a `long` option that keeps counting, so a stale advert still reads "3 months ago" / "3 miesiące temu". dm.js's copy is the only one left and goes with stage 6. Two things worth recording about verification. I first checked these pages only for leaked catalog keys, which came back clean and told me nothing — the page was still full of English. Running the actual en/pl diff turned up the page heading, Back and Refresh, all four sort options, the "Last advert:" prefix, the activity tooltips, "Protect" in its second render site, and the hop counter. The lesson is that a key-leak check proves the keys you used are real, not that you used them. And the diff's raw output on this page is 2000 lines because every contact name and public key is legitimately identical in both languages. Filtering those out made the eight real misses obvious; the per-page exclusion lists exist for exactly this and the contacts entries now carry them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86 lines
3.6 KiB
HTML
86 lines
3.6 KiB
HTML
{% extends "contacts_base.html" %}
|
|
|
|
{% block title %}{{ t('contacts.existing.title') }} - mc-webui{% endblock %}
|
|
|
|
{% block page_content %}
|
|
<div id="existingPageContent" class="p-3">
|
|
<!-- Page Header -->
|
|
<div class="mb-3">
|
|
<h4 class="mb-2">
|
|
<i class="bi bi-person-lines-fill"></i> {{ t('contacts.existing.title') }}
|
|
<span class="badge counter-badge counter-ok rounded-pill" id="contactsCounter" style="display: none; font-size: 0.8rem;">0 / 350</span>
|
|
</h4>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="d-flex gap-2 mb-3">
|
|
<button class="btn btn-outline-secondary btn-sm" onclick="navigateTo('/contacts/manage');">
|
|
<i class="bi bi-arrow-left"></i> {{ t('common.back') }}
|
|
</button>
|
|
<button class="btn btn-outline-primary btn-sm" id="refreshExistingBtn">
|
|
<i class="bi bi-arrow-clockwise"></i> {{ t('common.refresh') }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Search Toolbar -->
|
|
<div class="search-toolbar">
|
|
<input type="text" class="form-control" id="searchInput" placeholder="{{ t('contacts.existing.search_ph') }}">
|
|
</div>
|
|
|
|
<!-- Filter and Sort Toolbar -->
|
|
<div class="filter-sort-toolbar">
|
|
<!-- Source Filter -->
|
|
<select class="form-select" id="sourceFilter">
|
|
<option value="ALL">{{ t('contacts.filter.all_sources') }}</option>
|
|
<option value="DEVICE">{{ t('contacts.filter.on_device') }}</option>
|
|
<option value="CACHE">{{ t('contacts.filter.cache_only') }}</option>
|
|
<option value="IGNORED">{{ t('contacts.filter.ignored') }}</option>
|
|
<option value="BLOCKED">{{ t('contacts.filter.blocked') }}</option>
|
|
</select>
|
|
|
|
<!-- Type Filter -->
|
|
<select class="form-select" id="typeFilter">
|
|
<option value="ALL">{{ t('contacts.filter.all_types') }}</option>
|
|
<option value="COM">COM</option>
|
|
<option value="REP">REP</option>
|
|
<option value="ROOM">ROOM</option>
|
|
<option value="SENS">SENS</option>
|
|
</select>
|
|
|
|
<!-- Sort Dropdown -->
|
|
<select class="form-select" id="sortSelect">
|
|
<option value="last_advert_desc">{{ t('contacts.sort.advert_desc') }}</option>
|
|
<option value="last_advert_asc">{{ t('contacts.sort.advert_asc') }}</option>
|
|
<option value="name_asc">{{ t('contacts.sort.name_asc') }}</option>
|
|
<option value="name_desc">{{ t('contacts.sort.name_desc') }}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div id="existingLoading" class="text-center py-5" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">{{ t('common.loading') }}</span>
|
|
</div>
|
|
<p class="mt-3 text-muted">{{ t('contacts.existing.loading') }}</p>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div id="existingEmpty" class="empty-state" style="display: none;">
|
|
<i class="bi bi-inbox"></i>
|
|
<p class="mb-0">{{ t('contacts.existing.none') }}</p>
|
|
<small class="text-muted">{{ t('contacts.existing.none_hint') }}</small>
|
|
</div>
|
|
|
|
<!-- Existing Contacts List (Full-screen) -->
|
|
<div id="existingList" class="contacts-list-fullscreen">
|
|
<!-- Dynamically populated by contacts.js -->
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div id="existingError" class="alert alert-danger" style="display: none;" role="alert">
|
|
<i class="bi bi-exclamation-triangle"></i>
|
|
<span id="existingErrorMessage">{{ t('contacts.existing.load_failed') }}</span>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|