diff --git a/modules/web_viewer/templates/contacts.html b/modules/web_viewer/templates/contacts.html
index 248748b..f1c4bec 100644
--- a/modules/web_viewer/templates/contacts.html
+++ b/modules/web_viewer/templates/contacts.html
@@ -496,23 +496,23 @@
- Name
+ Name
|
- Device Type
+ Device Type
|
- Location
+ Location
|
- Distance
+ Distance
|
- SNR
+ SNR
|
- Hops
+ Hops
|
- Bytes/hop
+ Bytes/hop
|
- First Heard
+ First Heard
|
- Last Heard
+ Last Heard
|
- Adverts
+ Adverts
|
Actions |
@@ -863,9 +863,9 @@ class ModernContactsManager {
}
updateSortIcons() {
- // Remove all sort icons
- document.querySelectorAll('.sortable i').forEach(icon => {
- icon.className = 'fas fa-sort';
+ // Only touch dedicated sort chevrons — never the hops path-encoding (i) tip.
+ document.querySelectorAll('.sortable .sort-icon').forEach(icon => {
+ icon.className = 'fas fa-sort sort-icon';
});
// Remove active class from all headers
@@ -877,9 +877,11 @@ class ModernContactsManager {
const activeHeader = document.querySelector(`[data-sort="${this.sortColumn}"]`);
if (activeHeader) {
activeHeader.classList.add('sort-active');
- const icon = activeHeader.querySelector('i');
+ const icon = activeHeader.querySelector('.sort-icon');
if (icon) {
- icon.className = this.sortDirection === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down';
+ icon.className = this.sortDirection === 'asc'
+ ? 'fas fa-sort-up sort-icon'
+ : 'fas fa-sort-down sort-icon';
}
}
this.syncMobileSortSelect();
@@ -2723,18 +2725,18 @@ document.addEventListener('DOMContentLoaded', () => {
transition: all 0.2s ease;
}
-.sortable i {
+.sortable .sort-icon {
margin-left: 5px;
opacity: 0.5;
transition: opacity 0.2s ease;
font-size: 0.8em;
}
-.sortable:hover i {
+.sortable:hover .sort-icon {
opacity: 0.8;
}
-.sort-active i {
+.sort-active .sort-icon {
opacity: 1;
color: #0d6efd !important;
}
diff --git a/tests/js/contacts_manager.test.mjs b/tests/js/contacts_manager.test.mjs
index d21ec26..b66689a 100644
--- a/tests/js/contacts_manager.test.mjs
+++ b/tests/js/contacts_manager.test.mjs
@@ -223,6 +223,65 @@ test('a newer contacts request aborts the stale request', async () => {
assert.equal(signals[1].aborted, false);
});
+test('updateSortIcons leaves the hops info tip alone', () => {
+ const { context, Manager } = loadManagerClass();
+ const sortIcon = fakeElement({ className: 'fas fa-sort sort-icon' });
+ const infoIcon = fakeElement({ className: 'fas fa-info-circle' });
+ const hopsHeader = fakeElement({
+ classList: {
+ remove(name) {
+ this._removed = name;
+ },
+ add(name) {
+ this._added = name;
+ },
+ },
+ querySelector(selector) {
+ if (selector === '.sort-icon') return sortIcon;
+ if (selector === 'i') return sortIcon;
+ return null;
+ },
+ querySelectorAll(selector) {
+ if (selector === '.sort-icon') return [sortIcon];
+ if (selector === 'i') return [sortIcon, infoIcon];
+ return [];
+ },
+ });
+ const otherHeader = fakeElement({
+ classList: {
+ remove() {},
+ add() {},
+ },
+ querySelector() {
+ return null;
+ },
+ querySelectorAll() {
+ return [];
+ },
+ });
+
+ context.document.querySelectorAll = selector => {
+ if (selector === '.sortable .sort-icon') return [sortIcon];
+ if (selector === '.sortable i') return [sortIcon, infoIcon];
+ if (selector === '.sortable') return [hopsHeader, otherHeader];
+ return [];
+ };
+ context.document.querySelector = selector => {
+ if (selector === '[data-sort="hop_count"]') return hopsHeader;
+ return null;
+ };
+
+ const manager = bareManager(Manager);
+ manager.sortColumn = 'hop_count';
+ manager.sortDirection = 'asc';
+ manager.syncMobileSortSelect = () => {};
+ manager.updateSortIcons();
+
+ assert.equal(sortIcon.className, 'fas fa-sort-up sort-icon');
+ assert.equal(infoIcon.className, 'fas fa-info-circle');
+ assert.equal(hopsHeader.classList._added, 'sort-active');
+});
+
test('search is debounced before reloading the first page', () => {
const { context, elements, Manager } = loadManagerClass();
const requiredIds = [