// DOM Elements const DOM = { container: document.getElementById("card-container"), pagination: document.getElementById("pagination"), searchInput: document.getElementById("search-input"), loading: document.getElementById("loading-indicator"), resultCount: document.getElementById("result-count") }; // Configuration const CONFIG = { url: window.location.href.replace(/\/$/, "") + "/api/posts", postsPerPage: 12, maxVisiblePages: 5, searchDelay: 300 }; // Application State const state = { allPosts: [], filteredPosts: [], currentPage: 1, totalPages: 1, searchTerm: "" }; // Utility Functions const utils = { debounce: (func, delay) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), delay); }; }, normalizePost: post => ({ ...post, _autor: post.autor.toLowerCase(), _title: post.title.toLowerCase(), _preview: post.preview.toLowerCase() }), saveState: () => { const searchState = { page: state.currentPage, term: state.searchTerm }; localStorage.setItem("searchState", JSON.stringify(searchState)); }, loadState: () => { const savedState = JSON.parse(localStorage.getItem("searchState")) || {}; state.currentPage = savedState.page || 1; state.searchTerm = savedState.term || ""; DOM.searchInput.value = state.searchTerm; }, animateCards: () => { const cards = document.querySelectorAll('.card-wrapper'); let index = 0; const animate = () => { if (index < cards.length) { cards[index].classList.add('visible'); index++; requestAnimationFrame(animate); } }; requestAnimationFrame(animate); } }; // Rendering Functions const render = { showLoading: () => { DOM.loading.style.display = 'block'; DOM.container.style.opacity = '0.5'; }, hideLoading: () => { DOM.loading.style.display = 'none'; DOM.container.style.opacity = '1'; }, posts: () => { DOM.container.innerHTML = ""; const start = (state.currentPage - 1) * CONFIG.postsPerPage; const end = start + CONFIG.postsPerPage; const postsToShow = state.filteredPosts.slice(start, end); if (postsToShow.length === 0) { DOM.container.innerHTML = `

No se encontraron resultados

Intenta con otros términos de búsqueda

`; return; } const fragment = document.createDocumentFragment(); postsToShow.forEach(post => { const imgSrc = post.first_img || 'static/y_img/other/no_img.png'; const dateUpdate = post.author_updated ? ` ${post.author_updated}
` : ""; const card = document.createElement("div"); card.className = "col card-wrapper"; card.innerHTML = `
${post.title}
${post.title}
${post.autor}
${post.author_creation}
${dateUpdate} ${post.read_time_min} min(s) de lectura.

${post.preview}...

`; fragment.appendChild(card); }); DOM.container.appendChild(fragment); utils.animateCards(); }, pagination: () => { DOM.pagination.innerHTML = ""; state.totalPages = Math.ceil(state.filteredPosts.length / CONFIG.postsPerPage); DOM.resultCount.textContent = state.filteredPosts.length; const pageItem = (page, label = null, disabled = false, active = false) => { const li = document.createElement("li"); li.className = `page-item ${disabled ? 'disabled' : ''} ${active ? 'active' : ''}`; const a = document.createElement("a"); a.className = "page-link"; a.href = "#"; a.textContent = label || page; if (!disabled && !active) { a.addEventListener("click", (e) => { e.preventDefault(); state.currentPage = page; utils.saveState(); render.updateView(); }); } li.appendChild(a); return li; }; const pageDots = () => { const li = document.createElement("li"); li.className = "page-item disabled"; li.innerHTML = `...`; return li; }; // Botón anterior if (state.currentPage > 1) { DOM.pagination.appendChild(pageItem(state.currentPage - 1, "«")); } const startPage = Math.max(1, state.currentPage - Math.floor(CONFIG.maxVisiblePages / 2)); const endPage = Math.min(state.totalPages, startPage + CONFIG.maxVisiblePages - 1); if (startPage > 1) { DOM.pagination.appendChild(pageItem(1)); if (startPage > 2) DOM.pagination.appendChild(pageDots()); } for (let i = startPage; i <= endPage; i++) { DOM.pagination.appendChild(pageItem(i, null, false, i === state.currentPage)); } if (endPage < state.totalPages) { if (endPage < state.totalPages - 1) DOM.pagination.appendChild(pageDots()); DOM.pagination.appendChild(pageItem(state.totalPages)); } // Botón siguiente if (state.currentPage < state.totalPages) { DOM.pagination.appendChild(pageItem(state.currentPage + 1, "»")); } }, updateView: () => { render.posts(); render.pagination(); window.scrollTo({ top: 0, behavior: 'smooth' }); } }; // Search Functionality const search = { filterPostsByTerm: (posts, term) => { if (!term) return [...posts]; const loweredTerm = term.toLowerCase(); return posts.filter(post => post._autor.includes(loweredTerm) || post._title.includes(loweredTerm) || post._preview.includes(loweredTerm) ); }, filterPosts: term => { const newSearchTerm = term.toLowerCase(); // if (newSearchTerm === state.searchTerm) return; if (newSearchTerm === state.searchTerm && newSearchTerm !== "") return; state.searchTerm = newSearchTerm; state.filteredPosts = search.filterPostsByTerm(state.allPosts, state.searchTerm); state.currentPage = 1; render.updateView(); }, init: () => { DOM.searchInput.addEventListener("input", utils.debounce(() => { search.filterPosts(DOM.searchInput.value); }, CONFIG.searchDelay)); } }; // Initialization const init = { loadPosts: () => { render.showLoading(); fetch(CONFIG.url) .then(response => { if (!response.ok) throw new Error('Error en la red'); return response.json(); }) .then(data => { state.allPosts = data.map(utils.normalizePost); utils.loadState(); state.filteredPosts = search.filterPostsByTerm(state.allPosts, state.searchTerm); state.totalPages = Math.ceil(state.filteredPosts.length / CONFIG.postsPerPage); state.currentPage = Math.min(state.currentPage, state.totalPages || 1); render.updateView(); }) .catch(error => { console.error('Error:', error); DOM.container.innerHTML = `

Error al cargar los posts

Por favor intenta recargar la página

`; }) .finally(() => { render.hideLoading(); }); } }; document.addEventListener("DOMContentLoaded", () => { init.loadPosts(); search.init(); }); // limpiar buscador document.getElementById('clear-btn').addEventListener('click', function () { const input = document.getElementById('search-input'); input.value = ''; state.searchTerm = ''; // resetear el estado state.filteredPosts = [...state.allPosts]; // mostrar todo de nuevo state.currentPage = 1; render.updateView(); // actualizar la vista localStorage.removeItem("searchState"); // opcional: limpiar localStorage });