Files
mc-webui/app/templates/contacts-existing.html
MarekWoandClaude Opus 5 b1add0efc2 feat(i18n): translate contacts.js (stage 5b)
~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>
2026-07-31 19:29:36 +02:00

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 %}