[Feature] Sort results table by headers (#58)

Related to #53

This PR adds sorting to the results table. This can be done by clicking
on the header of the table.

For narrow columns, the arrow indicating the ordering is underneath the
column title instead of next to the title - not sure how to solve that
elegantly without a minor redesign of the table :D


![image](https://github.com/user-attachments/assets/2d2f6d4f-681a-4900-b55a-67642cc3169f)
This commit is contained in:
FunnyPocketBook
2025-01-13 14:20:11 -05:00
committed by GitHub
parent 6f14534312
commit 39ac60eaab
3 changed files with 79 additions and 8 deletions
+4
View File
@@ -146,6 +146,10 @@ tbody tr:hover {
overflow: hidden;
}
th[data-sort="index"] {
min-width: 60px;
}
/* Loading Indicator */
.loading-indicator {
display: none;
+67
View File
@@ -85,6 +85,60 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
return element;
},
sortResultsTable(column, order = 'asc') {
const rows = Array.from(elements.resultsTableBody.querySelectorAll('tr'));
const headers = document.querySelectorAll('#results-table thead th');
const parseFileSize = (size) => {
const match = size.match(/([\d.]+)([KMGT]B)/i);
if (!match) return 0;
const [_, value, unit] = match;
const multiplier = { KB: 1, MB: 1024, GB: 1024 * 1024, TB: 1024 * 1024 * 1024 };
return parseFloat(value) * (multiplier[unit.toUpperCase()] || 1);
};
const parseTitle = (title) => {
return title.replace(/^(The)\s+/i, '').trim();
}
const columnName = headers[column].textContent.trim().toLowerCase();
const getCellValue = (row, column) => {
const cell = row.querySelector(`td:nth-child(${column + 1})`);
const text = cell ? cell.textContent.trim() : '';
if (columnName === 'size') return parseFileSize(text);
if (columnName === 'title') return parseTitle(text);
};
rows.sort((a, b) => {
const valA = getCellValue(a, column);
const valB = getCellValue(b, column);
if (!isNaN(valA) && !isNaN(valB)) {
return order === 'asc' ? valA - valB : valB - valA;
}
return order === 'asc'
? valA.localeCompare(valB)
: valB.localeCompare(valA);
});
elements.resultsTableBody.innerHTML = '';
rows.forEach(row => elements.resultsTableBody.appendChild(row));
headers.forEach(header => {
const icon = header.querySelector('.sort-icon');
if (icon) {
icon.removeAttribute('uk-icon');
}
});
const currentHeader = headers[column];
const icon = currentHeader.querySelector('.sort-icon');
if (icon) {
icon.setAttribute('uk-icon', `icon: ${order === 'asc' ? 'triangle-up' : 'triangle-down'}`);
}
}
};
@@ -435,6 +489,19 @@ document.addEventListener('DOMContentLoaded', () => {
modal.close();
}
});
function setupSorting() {
const headers = document.querySelectorAll('#results-table thead th');
headers.forEach((header, index) => {
let sortOrder = 'asc';
header.addEventListener('click', () => {
utils.sortResultsTable(index, sortOrder);
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
});
});
}
setupSorting();
}
// Initialize
+8 -8
View File
@@ -47,15 +47,15 @@
<table id="results-table" class="uk-table uk-table-hover uk-table-divider" role="grid">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" data-sort="index"># <span class="sort-icon" uk-icon></span></th>
<th scope="col">Preview</th>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Publisher</th>
<th scope="col">Year</th>
<th scope="col">Language</th>
<th scope="col">Format</th>
<th scope="col">Size</th>
<th scope="col" data-sort="title">Title <span class="sort-icon" uk-icon></span></th>
<th scope="col" data-sort="author">Author <span class="sort-icon" uk-icon></span></th>
<th scope="col" data-sort="publisher">Publisher <span class="sort-icon" uk-icon></span></th>
<th scope="col" data-sort="year">Year <span class="sort-icon" uk-icon></span></th>
<th scope="col" data-sort="language">Language <span class="sort-icon" uk-icon></span></th>
<th scope="col" data-sort="format">Format <span class="sort-icon" uk-icon></span></th>
<th scope="col" data-sort="size">Size <span class="sort-icon" uk-icon></span></th>
<th scope="col">Actions</th>
</tr>
</thead>