1document.addEventListener('DOMContentLoaded', function () { 2 const ITEMS_PER_PAGE = 9; 3 const VISIBLE_PAGE_COUNT = 3; 4 5 const blogContainer = document.getElementById('blogContainer'); 6 const paginationBlock = document.querySelector('.pagination-block'); 7 const categoryFilter = document.querySelector('#categoryFilter'); 8 const sortSelect = document.querySelector('#sortSelect'); 9 const searchInput = document.getElementById('searchInput'); 10 11 if (!blogContainer || !paginationBlock) return; 12 13 let currentPage = 1; 14 const state = { 15 search: '', 16 category: 'ÐÑе', 17 sort: 'newest', 18 }; 19 20 function getCards() { 21 const directChildren = Array.from(blogContainer.children).filter((el) => { 22 return el.matches('article, .blog-card'); 23 }); 24 25 if (directChildren.length) { 26 return directChildren; 27 } 28 29 return Array.from(blogContainer.querySelectorAll('.blog-card')); 30 } 31 32 function initCardsMeta() { 33 getCards().forEach((card) => { 34 if (!card.dataset.topic) { 35 if (card.dataset.category) card.dataset.topic = card.dataset.category; 36 else if (card.dataset.parent) card.dataset.topic = card.dataset.parent; 37 } 38 }); 39 } 40 41 function getCardTopicId(card) { 42 return String(card.dataset.topic || card.dataset.category || card.dataset.parent || ''); 43 } 44 45 function getCardDate(card) { 46 if (card.dataset.pubDate) return new Date(card.dataset.pubDate).getTime(); 47 if (card.dataset.date) return new Date(card.dataset.date).getTime(); 48 49 const timeEl = card.querySelector('time[datetime]'); 50 if (timeEl?.getAttribute('datetime')) { 51 return new Date(timeEl.getAttribute('datetime')).getTime(); 52 } 53 54 return 0; 55 } 56 57 function cardMatchesFilters(card) { 58 const topicId = getCardTopicId(card); 59 60 if (state.category !== 'ÐÑе' && topicId !== String(state.category)) { 61 return false; 62 } 63 64 if (state.search) { 65 const title = card.querySelector('h3')?.textContent || ''; 66 const description = card.querySelector('p')?.textContent || ''; 67 const searchText = `${title} ${description}`.toLowerCase(); 68 69 if (!searchText.includes(state.search)) { 70 return false; 71 } 72 } 73 74 return true; 75 } 76 77 function getFilteredCards() { 78 const matched = getCards().filter(cardMatchesFilters); 79 80 matched.sort((a, b) => { 81 const dateA = getCardDate(a); 82 const dateB = getCardDate(b); 83 return state.sort === 'oldest' ? dateA - dateB : dateB - dateA; 84 }); 85 86 return matched; 87 } 88 89 function getVisiblePages(current, total) { 90 if (total <= VISIBLE_PAGE_COUNT) { 91 return Array.from({ length: total }, (_, i) => i + 1); 92 } 93 94 let start = current - Math.floor(VISIBLE_PAGE_COUNT / 2); 95 if (start < 1) start = 1; 96 if (start + VISIBLE_PAGE_COUNT - 1 > total) { 97 start = total - VISIBLE_PAGE_COUNT + 1; 98 } 99 100 return Array.from({ length: VISIBLE_PAGE_COUNT }, (_, i) => start + i); 101 } 102 103 function renderPagination(totalItems) { 104 const totalPages = Math.max(1, Math.ceil(totalItems / ITEMS_PER_PAGE)); 105 106 if (totalItems === 0 || totalPages <= 1) { 107 paginationBlock.innerHTML = ''; 108 return; 109 } 110 111 const isFirst = currentPage === 1; 112 const isLast = currentPage === totalPages; 113 const visiblePages = getVisiblePages(currentPage, totalPages); 114 115 const pageButtons = visiblePages.map((page) => ` 116 <button type="button" data-button-page="${page}" class="button-number px-4 py-2 border-t border-b border-gray-300 ${page === currentPage ? 'bg-primary text-white' : 'bg-white text-gray-700 hover:bg-gray-50'}">${page}</button> 117 `).join(''); 118 119 paginationBlock.innerHTML = ` 120 <nav class="inline-flex"> 121 <button type="button" class="button-pagination px-4 py-2 border border-gray-300 rounded-l-md bg-white text-gray-700 ${isFirst ? 'opacity-50 pointer-events-none' : 'hover:bg-gray-50'}" data-page="prev" ${isFirst ? 'aria-disabled="true"' : ''}> 122 <div class="w-5 h-5 flex items-center justify-center"> 123 <i class="ri-arrow-left-s-line"></i> 124 </div> 125 </button> 126 ${pageButtons} 127 <button type="button" class="button-pagination button-pagination--next px-4 py-2 border border-gray-300 rounded-r-md bg-white text-gray-700 ${isLast ? 'opacity-50 pointer-events-none' : 'hover:bg-gray-50'}" data-page="next" ${isLast ? 'aria-disabled="true"' : ''}> 128 <div class="w-5 h-5 flex items-center justify-center"> 129 <i class="ri-arrow-right-s-line"></i> 130 </div> 131 </button> 132 </nav> 133 `; 134 } 135 136 function applyResults(resetPage = false) { 137 const allCards = getCards(); 138 const matched = getFilteredCards(); 139 140 if (resetPage) { 141 currentPage = 1; 142 } 143 144 const totalPages = Math.max(1, Math.ceil(matched.length / ITEMS_P
144ER_PAGE)); 145 if (currentPage > totalPages) { 146 currentPage = totalPages; 147 } 148 149 allCards.forEach((card) => { 150 card.style.display = 'none'; 151 }); 152 153 matched.forEach((card) => { 154 blogContainer.appendChild(card); 155 }); 156 157 const start = (currentPage - 1) * ITEMS_PER_PAGE; 158 matched.slice(start, start + ITEMS_PER_PAGE).forEach((card) => { 159 card.style.display = ''; 160 }); 161 162 renderPagination(matched.length); 163 } 164 165 function switchPage(page) { 166 const matchedCount = getFilteredCards().length; 167 const totalPages = Math.max(1, Math.ceil(matchedCount / ITEMS_PER_PAGE)); 168 currentPage = Math.min(Math.max(1, page), totalPages); 169 applyResults(false); 170 } 171 172 paginationBlock.addEventListener('click', (e) => { 173 const button = e.target.closest('[data-button-page], [data-page]'); 174 if (!button || button.getAttribute('aria-disabled') === 'true') return; 175 176 if (button.dataset.buttonPage) { 177 switchPage(parseInt(button.dataset.buttonPage, 10)); 178 return; 179 } 180 181 if (button.dataset.page === 'prev') { 182 switchPage(currentPage - 1); 183 } else if (button.dataset.page === 'next') { 184 switchPage(currentPage + 1); 185 } 186 }); 187 188 initCardsMeta(); 189 applyResults(true); 190 191 if (categoryFilter) { 192 categoryFilter.addEventListener('change', () => { 193 state.category = categoryFilter.value; 194 applyResults(true); 195 }); 196 } 197 198 if (sortSelect) { 199 sortSelect.addEventListener('change', () => { 200 state.sort = sortSelect.value; 201 applyResults(true); 202 }); 203 } 204 205 if (searchInput) { 206 searchInput.addEventListener('input', (e) => { 207 state.search = e.target.value.toLowerCase().trim(); 208 applyResults(true); 209 }); 210 } 211});
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.