From 27fc54733eb65f599821a6828a6dccf64168ab6b Mon Sep 17 00:00:00 2001 From: Profile Profile Date: Sat, 14 Mar 2026 18:26:46 +0300 Subject: [PATCH] add js files --- src/scripts/ContentGrid.js | 231 +++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 src/scripts/ContentGrid.js diff --git a/src/scripts/ContentGrid.js b/src/scripts/ContentGrid.js new file mode 100644 index 0000000..d349edb --- /dev/null +++ b/src/scripts/ContentGrid.js @@ -0,0 +1,231 @@ +// src/scripts/infinity-scroll.js +(function() { + 'use strict'; + + class InfinityScroll { + constructor(sentinelElement) { + this.grid = document.getElementById('posts-grid'); + this.sentinel = sentinelElement; + this.loadingIndicator = document.getElementById('loading-indicator'); + this.noMorePosts = document.getElementById('no-more-posts'); + this.postsCount = document.getElementById('posts-count'); + this.observer = null; + this.isLoading = false; + this.hasMore = true; + this.endCursor = null; + this.currentIndex = 0; + this.gridColumns = 4; + this.loadMoreConfig = { type: 'latest', slug: '', gridColumns: 4 }; + + // Константы для разных сеток + this.CYCLE_LENGTH = { + 3: 14, // Полный цикл для 3 колонок: 6 обычных + 1 большая + 6 обычных + 1 большая + 4: 19 // Полный цикл для 4 колонок: 8 обычных + 1 большая + 9 обычных + 1 большая + }; + + this.OPTIMAL_LOADS = { + 3: [9, 12, 15], // 3, 4, 5 полных рядов + 4: [12, 16, 20] // 3, 4, 5 полных рядов + }; + + this.init(); + } + + init() { + if (!this.sentinel || !this.grid) return; + + // Получаем данные из sentinel + this.endCursor = this.sentinel.dataset.endCursor || null; + this.currentIndex = parseInt(this.sentinel.dataset.currentIndex || '0'); + this.gridColumns = parseInt(this.sentinel.dataset.gridColumns || '4'); + + try { + this.loadMoreConfig = JSON.parse(this.sentinel.dataset.loadConfig || '{}'); + } catch { + this.loadMoreConfig = { type: 'latest', slug: '', gridColumns: this.gridColumns }; + } + + this.observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting && !this.isLoading && this.hasMore) { + this.loadMorePosts(); + } + }); + }, + { + rootMargin: '200px', + threshold: 0.1 + } + ); + + this.observer.observe(this.sentinel); + } + + /** + * Определяет оптимальное количество постов для загрузки + */ + getOptimalLoadCount() { + const columns = this.gridColumns; + const cycleLength = this.CYCLE_LENGTH[columns]; + const position = this.currentIndex % cycleLength; + const options = this.OPTIMAL_LOADS[columns]; + + // Выбираем оптимальное число в зависимости от позиции в цикле + if (position < columns) { + return options[0]; // В начале цикла - 3 полных ряда + } else if (position < columns * 2) { + return options[1]; // В середине цикла - 4 полных ряда + } else { + return options[2]; // Ближе к концу - 5 полных рядов + } + } + + async loadMorePosts() { + if (this.isLoading || !this.hasMore) return; + + this.isLoading = true; + this.showLoading(); + + try { + const loadCount = this.getOptimalLoadCount(); + + const response = await fetch('/load-more-posts', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + perLoad: loadCount, + after: this.endCursor, + type: this.loadMoreConfig?.type || 'latest', + slug: this.loadMoreConfig?.slug || '', + startIndex: this.currentIndex, + gridColumns: this.gridColumns + }) + }); + + if (!response.ok) { + throw new Error(`Ошибка загрузки: ${response.status}`); + } + + const html = await response.text(); + const temp = document.createElement('div'); + temp.innerHTML = html; + + const newSentinel = temp.querySelector('#infinity-scroll-sentinel'); + const hasNextPage = !!newSentinel; + const newEndCursor = newSentinel?.dataset.endCursor || null; + + const articles = temp.querySelectorAll('article'); + + if (articles.length > 0) { + const fragment = document.createDocumentFragment(); + articles.forEach(article => fragment.appendChild(article.cloneNode(true))); + this.grid.appendChild(fragment); + + this.currentIndex += articles.length; + this.endCursor = newEndCursor; + this.hasMore = hasNextPage; + + if (this.postsCount) { + this.postsCount.textContent = ` (${this.currentIndex})`; + } + + if (this.sentinel) { + this.sentinel.dataset.currentIndex = String(this.currentIndex); + this.sentinel.dataset.endCursor = newEndCursor || ''; + + if (!hasNextPage) { + this.showNoMorePosts(); + } + } + } else { + this.hasMore = false; + this.showNoMorePosts(); + } + } catch (error) { + console.error('Ошибка загрузки:', error); + this.hasMore = false; + this.showError(); + } finally { + this.isLoading = false; + this.hideLoading(); + } + } + + showLoading() { + if (this.loadingIndicator) { + this.loadingIndicator.style.display = 'block'; + } + } + + hideLoading() { + if (this.loadingIndicator) { + this.loadingIndicator.style.display = 'none'; + } + } + + showNoMorePosts() { + if (this.sentinel && this.observer) { + this.observer.unobserve(this.sentinel); + this.sentinel.style.display = 'none'; + } + + if (this.noMorePosts) { + this.noMorePosts.style.display = 'block'; + } + } + + showError() { + if (this.noMorePosts) { + this.noMorePosts.textContent = 'Ошибка загрузки. Попробуйте обновить страницу.'; + this.noMorePosts.style.display = 'block'; + } + } + + destroy() { + if (this.observer && this.sentinel) { + this.observer.unobserve(this.sentinel); + } + if (this.observer) { + this.observer.disconnect(); + } + } + } + + // Глобальная функция для инициализации + window.initInfinityScroll = function(sentinelElement) { + if (!sentinelElement) { + sentinelElement = document.getElementById('infinity-scroll-sentinel'); + } + + if (sentinelElement) { + // Очищаем предыдущий инстанс если есть + if (window.__infinityScrollInstance) { + window.__infinityScrollInstance.destroy(); + } + + // Создаем новый инстанс + window.__infinityScrollInstance = new InfinityScroll(sentinelElement); + } + }; + + // Автоматическая инициализация при загрузке страницы + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + window.initInfinityScroll(); + }); + } else { + window.initInfinityScroll(); + } + + // Обработка для динамической навигации Astro + document.addEventListener('astro:after-swap', () => { + // Переинициализируем после смены страницы + setTimeout(() => { + window.initInfinityScroll(); + }, 100); + }); + +})(); \ No newline at end of file