PageSourceSearch

https://sakhtravel.com/template/js/page/blog.js?v=1.8236

js sakhtravel.com collected 2026-09-29 13:25:48 UTC 6,356 bytes, 211 lines download raw bytes

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.