mejoras en el blog

This commit is contained in:
2026-09-22 21:21:19 -03:00
parent 0a88f4df20
commit 28cce9a68c
14 changed files with 427 additions and 734 deletions
+232 -90
View File
@@ -1,110 +1,252 @@
document.addEventListener('DOMContentLoaded', () => {
const searchInput = document.getElementById('blog-search');
const searchClear = document.getElementById('search-clear');
const categoryBtns = document.querySelectorAll('.category-btn');
const filterBtns = document.querySelectorAll('.filter-btn');
const resetBtn = document.getElementById('reset-filters');
document.addEventListener('DOMContentLoaded', async () => {
const GHOST_API_URL = 'https://blog.amoryroma.com.ar';
const GHOST_API_KEY = '82450ba91ca6b5b9628006eec4';
// Elementos del DOM
const featuredSection = document.getElementById('featured-posts');
const featuredGrid = document.getElementById('featured-grid');
const blogGrid = document.getElementById('blog-grid');
const loadMoreBtn = document.getElementById('load-more');
const resultsCount = document.getElementById('results-count');
const emptyState = document.getElementById('empty-state');
const allCards = document.querySelectorAll('.blog-card, .featured-card');
let activeCategory = 'all';
let activeFilter = 'all';
let searchQuery = '';
const gridSectionTitle = document.getElementById('grid-section-title');
const gridSectionSubtitle = document.getElementById('grid-section-subtitle');
function filterArticles() {
let visibleCount = 0;
// Estado de los filtros
let posts = [];
let state = {
category: 'all',
filter: 'all', // 'all' o 'destacados'
search: '',
sort: 'recent'
};
allCards.forEach(card => {
const categories = card.dataset.categories || '';
const isFeatured = card.dataset.featured === 'true';
const title = card.querySelector('.featured-title, .card-title')?.textContent.toLowerCase() || '';
const excerpt = card.querySelector('.featured-excerpt, .card-excerpt')?.textContent.toLowerCase() || '';
// 1. Filtro de categoría
const matchCategory = activeCategory === 'all' || categories.includes(activeCategory);
// Cargar artículos desde Ghost API
async function loadPosts() {
try {
const response = await fetch(`${GHOST_API_URL}/ghost/api/content/posts/?key=${GHOST_API_KEY}&limit=all&include=tags,authors`);
const data = await response.json();
// 2. Filtro de tipo (Todos / Destacados)
const matchFilter = activeFilter === 'all' || (activeFilter === 'destacados' && isFeatured);
// 3. Filtro de búsqueda
const matchSearch = searchQuery === '' || title.includes(searchQuery) || excerpt.includes(searchQuery);
posts = data.posts.map(post => ({
id: post.id,
slug: post.slug,
titulo: post.title,
extracto: post.excerpt,
categoria: post.primary_tag ? post.primary_tag.name.toLowerCase() : 'general',
tags: post.tags ? post.tags.map(t => t.name.toLowerCase()) : [],
imagen: post.feature_image || 'assets/blog/default.jpg',
fecha: post.published_at,
tiempo_lectura: Math.ceil(post.reading_time) || 5,
destacado: post.featured
}));
if (matchCategory && matchFilter && matchSearch) {
card.style.display = 'flex';
visibleCount++;
} else {
card.style.display = 'none';
renderPosts();
setupEventListeners();
} catch (error) {
console.error('Error cargando posts de Ghost:', error);
if(emptyState) {
emptyState.style.display = 'block';
emptyState.querySelector('h3').textContent = 'Error al cargar el blog';
emptyState.querySelector('p').textContent = 'Por favor, recarga la página.';
}
}
}
function formatDate(dateString) {
const options = { day: '2-digit', month: 'short', year: 'numeric' };
return new Date(dateString).toLocaleDateString('es-AR', options);
}
function createCard(post, isFeatured = false) {
const catFormatted = post.categoria.charAt(0).toUpperCase() + post.categoria.slice(1);
const cardClass = isFeatured ? 'featured-card' : 'blog-card';
const badge = isFeatured && post.destacado ? '<span class="featured-badge">Destacado</span>' : '';
const metaClass = isFeatured ? 'featured-meta' : 'card-meta';
const titleClass = isFeatured ? 'featured-title' : 'card-title';
const excerptClass = isFeatured ? 'featured-excerpt' : 'card-excerpt';
const linkClass = isFeatured ? 'btn-read' : 'card-link';
const linkText = isFeatured ? 'Leer artículo →' : 'Leer artículo';
return `
<article class="${cardClass}" data-categories="${post.categoria}" data-featured="${post.destacado}">
<div class="${isFeatured ? 'featured-image' : 'card-image'}">
<img src="${post.imagen}" alt="${post.titulo}" loading="lazy">
${!isFeatured ? `<span class="card-category ${post.categoria}">${catFormatted}</span>` : ''}
${badge}
</div>
<div class="${isFeatured ? 'featured-content' : 'card-content'}">
<div class="${metaClass}">
<span class="category-tag ${post.categoria}">${catFormatted}</span>
<time datetime="${post.fecha}">${formatDate(post.fecha)}</time>
</div>
<h3 class="${titleClass}">${post.titulo}</h3>
<p class="${excerptClass}">${post.extracto}</p>
<div class="${isFeatured ? 'featured-footer' : ''}">
${isFeatured ? `<span class="reading-time">⏱ ${post.tiempo_lectura} min</span>` : `<span class="reading-time-mini">${post.tiempo_lectura} min</span>`}
<a href="${GHOST_API_URL}/${post.slug}/" class="${linkClass}" target="_blank">${linkText}</a>
</div>
</div>
</article>
`;
}
function renderPosts() {
// 1. FILTRAR
let filtered = posts.filter(post => {
const matchCategory = state.category === 'all' || post.categoria === state.category;
const matchFilter = state.filter === 'all' || (state.filter === 'destacados' && post.destacado);
const q = state.search.toLowerCase();
const matchSearch = !q ||
post.titulo.toLowerCase().includes(q) ||
post.extracto.toLowerCase().includes(q) ||
post.tags.some(tag => tag.includes(q));
return matchCategory && matchFilter && matchSearch;
});
// Actualizar contador y estado vacío
if (resultsCount) {
resultsCount.innerHTML = `Mostrando <strong>${visibleCount}</strong> artículo${visibleCount !== 1 ? 's' : ''}`;
// 2. ORDENAR
if (state.sort === 'recent') {
filtered.sort((a, b) => new Date(b.fecha) - new Date(a.fecha));
} else if (state.sort === 'popular') {
// Como la API pública de Ghost no expone "vistas", simulamos popularidad poniendo los destacados primero
filtered.sort((a, b) => (b.destacado === a.destacado) ? 0 : b.destacado ? 1 : -1);
}
// 3. DECIDIR VISTA: ¿Es la vista por defecto (sin filtros)?
const isDefaultView = state.category === 'all' && state.filter === 'all' && state.search === '';
if (isDefaultView) {
// VISTA POR DEFECTO: Mostrar Destacados + Últimos 3
if (featuredSection) featuredSection.style.display = 'block';
if (gridSectionTitle) gridSectionTitle.textContent = 'Últimos artículos';
if (gridSectionSubtitle) gridSectionSubtitle.textContent = 'Lo más nuevo y a la vanguardia.';
if (loadMoreBtn) {
loadMoreBtn.style.display = 'inline-block';
loadMoreBtn.textContent = 'Ver todos los artículos en el Blog';
loadMoreBtn.onclick = () => window.location.href = `${GHOST_API_URL}/`;
}
const featuredPosts = filtered.filter(p => p.destacado).slice(0, 3);
const latestPosts = filtered.slice(0, 3);
if (featuredGrid) featuredGrid.innerHTML = featuredPosts.map(p => createCard(p, true)).join('');
if (blogGrid) blogGrid.innerHTML = latestPosts.map(p => createCard(p, false)).join('');
if (resultsCount) resultsCount.innerHTML = `Mostrando los <strong>últimos 3</strong> artículos`;
if (emptyState) emptyState.style.display = 'none';
} else {
// VISTA FILTRADA: Ocultar Destacados, mostrar TODOS los resultados en la grilla principal
if (featuredSection) featuredSection.style.display = 'none';
if (loadMoreBtn) loadMoreBtn.style.display = 'none'; // Ocultamos "cargar más" porque ya mostramos todos los filtrados
// Actualizar títulos según el filtro
if (gridSectionTitle) {
if (state.search) gridSectionTitle.textContent = `Resultados de búsqueda`;
else if (state.category !== 'all') gridSectionTitle.textContent = `Artículos de ${state.category.charAt(0).toUpperCase() + state.category.slice(1)}`;
else if (state.filter === 'destacados') gridSectionTitle.textContent = `Artículos Destacados`;
}
if (gridSectionSubtitle) gridSectionSubtitle.textContent = `Se encontraron ${filtered.length} artículo${filtered.length !== 1 ? 's' : ''}`;
if (blogGrid) {
if (filtered.length === 0) {
blogGrid.innerHTML = '';
if (emptyState) {
emptyState.style.display = 'block';
emptyState.querySelector('h3').textContent = 'No encontramos artículos con esos filtros';
}
} else {
if (emptyState) emptyState.style.display = 'none';
// Mostramos TODOS los artículos que coinciden con el filtro, no solo 3
blogGrid.innerHTML = filtered.map(p => createCard(p, false)).join('');
}
}
if (resultsCount) {
resultsCount.innerHTML = `Mostrando <strong>${filtered.length}</strong> artículo${filtered.length !== 1 ? 's' : ''}`;
}
}
}
function setupEventListeners() {
// 1. Botones de Categoría
document.querySelectorAll('.category-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.category-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
state.category = e.target.dataset.category;
renderPosts();
});
});
// 2. Botones de Filtro (Todos / Destacados)
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
state.filter = e.target.dataset.filter;
renderPosts();
});
});
// 3. Buscador
const searchInput = document.getElementById('blog-search');
const searchClear = document.getElementById('search-clear');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
state.search = e.target.value;
if (searchClear) searchClear.classList.toggle('visible', state.search.length > 0);
renderPosts();
});
}
if (emptyState) {
emptyState.style.display = visibleCount === 0 ? 'block' : 'none';
}
// Mostrar/ocultar botón de limpiar búsqueda
if (searchClear) {
searchClear.classList.toggle('visible', searchQuery.length > 0);
searchClear.addEventListener('click', () => {
if (searchInput) {
searchInput.value = '';
state.search = '';
searchClear.classList.remove('visible');
searchInput.focus();
renderPosts();
}
});
}
}
// Event Listeners
if (searchInput) {
searchInput.addEventListener('input', (e) => {
searchQuery = e.target.value.toLowerCase().trim();
filterArticles();
});
}
// 4. Ordenamiento
const sortSelect = document.getElementById('sort-select');
if (sortSelect) {
sortSelect.addEventListener('change', (e) => {
state.sort = e.target.value;
renderPosts();
});
}
if (searchClear) {
searchClear.addEventListener('click', () => {
searchInput.value = '';
searchQuery = '';
searchInput.focus();
filterArticles();
});
}
categoryBtns.forEach(btn => {
btn.addEventListener('click', () => {
categoryBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
activeCategory = btn.dataset.category;
filterArticles();
});
});
filterBtns.forEach(btn => {
btn.addEventListener('click', () => {
filterBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
activeFilter = btn.dataset.filter;
filterArticles();
});
});
if (resetBtn) {
resetBtn.addEventListener('click', () => {
activeCategory = 'all';
activeFilter = 'all';
searchQuery = '';
// 5. Botón Limpiar Filtros
const resetBtn = document.getElementById('reset-filters');
const emptyReset = document.getElementById('empty-reset');
function resetAll() {
state = { category: 'all', filter: 'all', search: '', sort: 'recent' };
// Resetear UI
document.querySelectorAll('.category-btn').forEach(b => b.classList.remove('active'));
document.querySelector('.category-btn[data-category="all"]').classList.add('active');
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
document.querySelector('.filter-btn[data-filter="all"]').classList.add('active');
if (searchInput) searchInput.value = '';
if (searchClear) searchClear.classList.remove('visible');
if (sortSelect) sortSelect.value = 'recent';
categoryBtns.forEach(b => b.classList.remove('active'));
document.querySelector('.category-btn[data-category="all"]')?.classList.add('active');
filterBtns.forEach(b => b.classList.remove('active'));
document.querySelector('.filter-btn[data-filter="all"]')?.classList.add('active');
filterArticles();
});
renderPosts();
}
if (resetBtn) resetBtn.addEventListener('click', resetAll);
if (emptyReset) emptyReset.addEventListener('click', resetAll);
}
// Ejecutar al inicio
filterArticles();
// Iniciar
await loadPosts();
});