formha/static/e_blog/a_all_posts_visit copy.js

283 lines
8.5 KiB
JavaScript

// 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 = `
<div class="col-12 text-center py-5">
<h4>No se encontraron resultados</h4>
<p class="text-muted">Intenta con otros términos de búsqueda</p>
</div>
`;
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 ? `<i class="bi bi-arrow-repeat"></i> ${post.author_updated}<br>` : "";
const card = document.createElement("div");
card.className = "col card-wrapper";
card.innerHTML = `
<div class="card h-100">
<a href="/blog/${post.id}" class="stretched-link" onclick="utils.saveState()">
<img src="${imgSrc}" class="card-img-top" alt="${post.title}" loading="lazy">
</a>
<div class="card-body d-flex flex-column">
<div class="mb-3">
<h5 class="card-title">${post.title}</h5>
<small class="text-muted d-block mt-2">
<i class="bi bi-file-person-fill"></i> ${post.autor}<br>
<i class="bi bi-calendar-week"></i> ${post.author_creation}<br>
${dateUpdate}
<i class="bi bi-clock"></i> ${post.read_time_min} min(s) de lectura.
</small>
<p class="card-text mt-2">${post.preview}...</p>
</div>
</div>
</div>
`;
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 = `<span class="page-link">...</span>`;
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 = `
<div class="col-12 text-center py-5">
<h4>Error al cargar los posts</h4>
<p class="text-muted">Por favor intenta recargar la página</p>
</div>
`;
})
.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
});