1document.addEventListener("DOMContentLoaded", () => { 2 document.querySelectorAll('.cmp-text').forEach(container => { 3 const table = container.querySelector('table'); 4 if (!table) return; 5 6 const tbodys = table.querySelectorAll('tbody'); 7 let headerRow, dataRows = []; 8 if (tbodys.length === 1) { 9 const rows = Array.from(tbodys[0].querySelectorAll('tr')); 10 headerRow = rows[0]; 11 dataRows = rows.slice(1); 12 } else if (tbodys.length > 1) { 13 headerRow = tbodys[0].querySelector('tr'); 14 dataRows = Array.from(tbodys[1].querySelectorAll('tr')); 15 } else return; 16 17 const rowsPerPage = parseInt(container.querySelector('[data-max-items]')?.dataset.maxItems) || 5; 18 let currentRows = [...dataRows], currentPage = 1, sortColumnIndex = null, sortDirection = 'asc'; 19 20 const headerCells = headerRow.querySelectorAll('th'); 21 const enableSearch = container.querySelector('.cmp-text__table-search'); 22 const enableSort = container.querySelector('.cmp-text__table-sort'); 23 const enablePagination = container.querySelector('.cmp-text__table-pagination'); 24 const noResultsMessage = container.querySelector(".cmp-text__table-no-results-message"); 25 26 const getPageRows = () => enablePagination ? currentRows.slice((currentPage - 1) * rowsPerPage, currentPage * rowsPerPage) : currentRows; 27 28 const updateTableBody = () => { 29 const targetBody = tbodys.length === 1 ? tbodys[0] : tbodys[1]; 30 targetBody.innerHTML = ''; 31 if (tbodys.length === 1) targetBody.appendChild(headerRow); 32 getPageRows().forEach(row => targetBody.appendChild(row)); 33 }; 34 35 const resetSorting = () => { 36 sortColumnIndex = null; 37 sortDirection = 'asc'; 38 currentPage = 1; 39 headerCells.forEach(th => th.classList.remove('th-sort-asc', 'th-sort-desc')); 40 }; 41 42 const scrollToSearch = () => { 43 const searchContainer = container.querySelector('.cmp-text__table-search-container'); 44 if (searchContainer) { 45 window.scrollTo({ 46 top: window.pageYOffset + searchContainer.getBoundingClientRect().top - window.innerHeight * 0.3, 47 behavior: 'smooth' 48 }); 49 } 50 }; 51 52 const toggleNoResultsMessage = () => { 53 const hasResults = currentRows.length > 0; 54 noResultsMessage && (noResultsMessage.style.display = hasResults ? 'none' : 'block'); 55 const paginationContainer = container.querySelector('.pagination'); 56 const paginationLabel = container.querySelector('.cmp-text__table-pagination-lable'); 57 const paginationButtons = paginationContainer?.querySelectorAll("button"); 58 59 if (paginationContainer && paginationLabel) { 60 paginationContainer.style.display = paginationLabel.style.display = hasResults ? 'block' : 'none'; 61 paginationButtons?.forEach(btn => btn.style.display = hasResults ? 'inline-block' : 'none'); 62 } 63 }; 64 65 if (enableSearch) { 66 const searchContainer = document.createElement('div'); 67 searchContainer.className = 'cmp-text__table-search-container'; 68 searchContainer.innerHTML = `<input type="text" id="filterInput" placeholder="Filter" /><span class="clear-button" style="display:none;">X</span>`; 69 container.insertBefore(searchContainer, table); 70 71 const filterInput = searchContainer.querySelector('#filterInput'); 72 const clearButton = searchContainer.querySelector('.clear-button'); 73 74 const filterRows = (rows, query) => !query ? rows : rows.filter(row => [...row.cells].some(cell => cell.textContent.toLowerCase().includes(query.toLowerCase()))); 75 76 filterInput.addEventListener('input', () => { 77 clearButton.style.display = filterInput.value ? 'inline' : 'none'; 78 currentRows = filterRows(dataRows, filterInput.value); 79 resetSorting(); 80 renderTable(); 81 toggleNoResultsMessage(); 82 }); 83 84 clearButton.addEventListener('click', () => { 85 filterInput.value = ''; 86 clearButton.style.display = 'none'; 87 currentRows = [...dataRows]; 88 resetSorting(); 89 renderTable(); 90 toggleNoResultsMessage(); 91 }); 92 } 93 94 if (enableSort) {
95 headerCells.forEach((th, i) => { 96 th.classList.add('sortable'); 97 th.addEventListener('click', () => { 98 sortDirection = (sortColumnIndex === i && sortDirection === 'asc') ? 'desc' : 'asc'; 99 headerCells.forEach(th => th.classList.remove('th-sort-asc', 'th-sort-desc')); 100 th.classList.add(`th-sort-${sortDirection}`); 101 currentRows.sort((a, b) => { 102 const aText = a.cells[i].textContent.trim().toLowerCase(); 103 const bText = b.cells[i].textContent.trim().toLowerCase(); 104 return sortDirection === 'asc' ? aText.localeCompare(bText) : bText.localeCompare(aText); 105 }); 106 sortColumnIndex = i; 107 currentPage = 1; 108 renderTable(); 109 }); 110 }); 111 } 112 113 const updatePagination = (start, end, total) => { 114 container.querySelectorAll('.cmp-text__table-pagination-lable').forEach(label => { 115 if (!label.dataset.template) label.dataset.template = label.textContent || ''; 116 label.textContent = label.dataset.template.replace(/#start/g, start).replace(/#end/g, end).replace(/#total/g, total); 117 }); 118 }; 119 const renderPagination = () => { 120 const totalPages = Math.ceil(currentRows.length / rowsPerPage); 121 if (totalPages <= 1) return; 122 let paginationDiv = container.querySelector('#pagination'); 123 if (!paginationDiv) { 124 paginationDiv = Object.assign(document.createElement('div'), { 125 id: 'pagination', 126 className: 'pagination' 127 }); 128 container.appendChild(paginationDiv); 129 } 130 paginationDiv.innerHTML = ''; 131 132 const createButton = (label, disabled, onClick, classes = '') => { 133 const btn = document.createElement('button'); 134 btn.textContent = label; 135 btn.disabled = disabled; 136 if (classes) btn.className = classes; 137 btn.addEventListener('click', onClick); 138 return btn; 139 }; 140 141 const createEllipsis = (direction) => { 142 const span = document.createElement('span'); 143 span.textContent = '...'; 144 span.className = 'pagination-ellipsis'; 145 span.addEventListener('click', () => { 146 const jump = 5; 147 currentPage = Math.max(1, Math.min(totalPages, currentPage + (direction === 'right' ? jump : -jump))); 148 renderTable(); 149 }); 150 return span; 151 }; 152 153 paginationDiv.appendChild(createButton('â¹', currentPage === 1, () => { 154 if (currentPage > 1) { 155 currentPage--; 156 renderTable(); 157 } 158 }, currentPage === 1 ? 'prev-disable' : '')); 159 160 const pageButtons = [1]; 161 let start = Math.max(2, currentPage - 1); 162 let end = Math.min(totalPages - 1, currentPage + 1); 163 164 if (currentPage <= 2) { 165 end = Math.min(3, totalPages - 1); 166 } else if (currentPage >= totalPages - 2) { 167 start = Math.max(2, totalPages - 3); 168 } 169 170 if (start > 2) pageButtons.push('left-ellipsis'); 171 for (let i = start; i <= end; i++) pageButtons.push(i); 172 if (end < totalPages - 1) pageButtons.push('right-ellipsis'); 173 if (totalPages > 1) pageButtons.push(totalPages); 174 175 pageButtons.forEach(item => { 176 if (item === 'left-ellipsis') { 177 paginationDiv.appendChild(createEllipsis('left')); 178 } else if (item === 'right-ellipsis') { 179 paginationDiv.appendChild(createEllipsis('right')); 180 } else { 181 const btn = createButton(item, item === currentPage, () => { 182 currentPage = item; 183 renderTable(); 184 }, item === currentPage ? 'active' : ''); 185 paginationDiv.appendChild(btn); 186 } 187 }); 188 189 paginationDiv.appendChild(createButton('âº', currentPage === totalPages, () => { 190 if (currentPage < totalPages) { 191 currentPage++; 192 renderTable(); 193 } 194 }, currentPage === totalPages ? 'next-disable' : '')); 195}; 196 197 const renderTable = () => { 198 updateTableBody(); 199 if (enablePagination) { 200 renderPagination(); 201 updatePagination((currentPage - 1) * rowsPerPage + 1, Math.min(currentPage * rowsPerPage, currentRows.length), currentRows.length); 202 scrollToSearch(); 203 } 204 }; 205 206 renderTable(); 207 }); 208});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.