diff options
Diffstat (limited to 'src/layouts/BlogListLayout.astro')
| -rw-r--r-- | src/layouts/BlogListLayout.astro | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/src/layouts/BlogListLayout.astro b/src/layouts/BlogListLayout.astro new file mode 100644 index 0000000..4ad72ea --- /dev/null +++ b/src/layouts/BlogListLayout.astro @@ -0,0 +1,325 @@ +--- +import { getCollection } from "astro:content"; +import Layout from "@/layouts/Layout.astro"; + +const blogCollection = (await getCollection("blog")).sort((a, b) => { + return b.data.publishedAt.getTime() - a.data.publishedAt.getTime(); +}); + +// Agrupar artículos por fecha +const groupedPosts = blogCollection.reduce( + (acc: Record<string, any[]>, post) => { + const year = post.data.publishedAt.getFullYear(); + const month = post.data.publishedAt.getMonth() + 1; + const key = `${year}-${month}`; + if (!acc[key]) { + acc[key] = []; + } + acc[key].push(post); + return acc; + }, + {}, +); + +// Colección de todas las etiquetas únicas +const allTags = [...new Set(blogCollection.flatMap(post => post.data.tags || []))].sort(); + +function humaniseDate(date: Date) { + const result = date.toLocaleDateString("es-ES", { + month: "long", + year: "numeric", + }); + return result.charAt(0).toUpperCase() + result.slice(1); +} + +const schema = { + "@context": "https://schema.org", + "@type": "Blog", + headline: "Blog de Ariel Costas", + description: + "En este blog encontrarás artículos sobre desarrollo, tecnología y otras temáticas que pueda querer compartir. Disclaimer de siempre: las opiniones son mías, y no representan a ninguna empresa o institución.", + publisher: { + "@type": "Person", + name: "Ariel Costas Guerrero", + url: "https://www.costas.dev", + }, + author: { + "@type": "Person", + name: "Ariel Costas Guerrero", + url: "https://www.costas.dev", + }, +}; +--- + +<Layout + title="Blog" + description="Artículos sobre desarrollo, tecnología y otras temáticas que pueda querer compartir. Disclaimer de siempre: las opiniones son mías, y no representan a ninguna empresa o institución." +> + <script + is:inline + type="application/ld+json" + slot="head-jsonld" + set:html={JSON.stringify(schema)} + /> + + <h1>Blog de Ariel Costas</h1> + + <p> + En este blog encontrarás artículos sobre desarrollo, tecnología y otras + temáticas que pueda querer compartir. Disclaimer de siempre: las opiniones + son mías, y no representan a ninguna empresa o institución. + </p> + + {allTags.length > 0 && ( + <div class="tags-container"> + <h2>Etiquetas</h2> + <div class="tag-filter"> + <button class="tag-button active" data-tag="all">Todas</button> + {allTags.map((tag) => ( + <button class="tag-button" data-tag={tag}>{tag}</button> + ))} + </div> + </div> + )} + + <div id="blog-posts"> + { + Object.entries(groupedPosts).map(([key, posts]) => ( + <section class="post-section" data-date={key}> + <h2>{humaniseDate(new Date(key))}</h2> + <ul> + {posts.map((post) => { + const postTags = post.data.tags || []; + const tagsAttribute = postTags.join(','); + return ( + <li class="post-item" data-tags={tagsAttribute}> + <a href={`/blog/${post.id}`}>{post.data.title}</a> + {postTags.length > 0 && ( + <ul class="post-tags"> + {postTags.map((tag: string) => ( + <li> + <button class="tag-link" data-tag={tag}> + {tag} + </button> + </li> + ))} + </ul> + )} + </li> + ); + })} + </ul> + </section> + )) + } + </div> + + <script> + document.addEventListener('DOMContentLoaded', () => { + const tagButtons = document.querySelectorAll('.tag-button'); + const tagLinks = document.querySelectorAll('.tag-link'); + const postItems = document.querySelectorAll('.post-item'); + const postSections = document.querySelectorAll('.post-section'); + + function filterByTag(tag: string) { + postItems.forEach(item => { + (item as HTMLElement).style.display = ''; + }); + postSections.forEach(section => { + (section as HTMLElement).style.display = ''; + }); + + if (tag !== 'all') { + postItems.forEach(item => { + const itemEl = item as HTMLElement; + const tagsAttr = itemEl.dataset.tags || ''; + const itemTags = tagsAttr ? tagsAttr.split(',') : []; + if (!itemTags.includes(tag)) { + itemEl.style.display = 'none'; + } + }); + + postSections.forEach(section => { + const items = section.querySelectorAll('.post-item'); + let allHidden = true; + + items.forEach(item => { + if ((item as HTMLElement).style.display !== 'none') { + allHidden = false; + } + }); + + if (allHidden) { + (section as HTMLElement).style.display = 'none'; + } + }); + } + + tagButtons.forEach(button => { + if ((button as HTMLElement).dataset.tag === tag) { + button.classList.add('active'); + } else { + button.classList.remove('active'); + } + }); + + if (tag === 'all') { + history.replaceState(null, document.title, window.location.pathname); + } else { + history.replaceState(null, document.title, `?tag=${encodeURIComponent(tag)}`); + } + } + + tagButtons.forEach(button => { + button.addEventListener('click', () => { + const tag = (button as HTMLElement).dataset.tag; + const isCurrentlyActive = button.classList.contains('active'); + + if (tag && isCurrentlyActive && tag !== 'all') { + // If the clicked tag is already active, switch back to "all" + filterByTag('all'); + } else if (tag) { + // Otherwise apply the tag filter + filterByTag(tag); + } + }); + }); + + tagLinks.forEach(link => { + link.addEventListener('click', (e) => { + e.preventDefault(); + const tag = (link as HTMLElement).dataset.tag; + + // Check if this tag is already active + const isCurrentlyActive = Array.from(tagButtons).some(button => + (button as HTMLElement).dataset.tag === tag && button.classList.contains('active') + ); + + if (tag && isCurrentlyActive) { + // If the clicked tag is already active, switch back to "all" + filterByTag('all'); + } else if (tag) { + // Otherwise apply the tag filter + filterByTag(tag); + } + + const tagsContainer = document.querySelector('.tags-container'); + if (tagsContainer) { + window.scrollTo({ + top: (tagsContainer as HTMLElement).offsetTop - 20, + }); + } + }); + }); + + // Verificar si hay un parámetro de consulta para filtrar + const urlParams = new URLSearchParams(window.location.search); + const tagParam = urlParams.get('tag'); + + if (tagParam) { + const tagExists = Array.from(tagButtons).some(button => + (button as HTMLElement).dataset.tag === tagParam + ); + if (tagExists) { + filterByTag(tagParam); + } + } + }); + </script> +</Layout> + +<style lang="scss"> + @use "../../styles/variables" as v; + @use "sass:color"; + + .tags-container { + margin-bottom: 2rem; + } + + .tag-filter { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 1.5rem; + } + + .tag-button { + padding: 0.25rem 0.6rem; + background-color: v.$light; + color: v.$accent; + border: 1px solid v.$accent; + border-radius: 1.5rem; + font-size: 0.85rem; + font-family: v.$monoFontStack; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: v.$shadow; + + &:hover { + background-color: color.adjust(v.$accent, $lightness: 45%); + color: v.$accentDark; + transform: translateY(-1px); + } + + &.active { + background-color: v.$accent; + color: v.$lightAlt; + border-color: v.$accentDark; + } + } + + .post-tags { + display: inline-flex; + list-style: none; + margin: 0; + padding: 0; + gap: 0.25rem; + margin-left: 0.5rem; + } + + .post-tags li { + display: inline; + } + + .tag-link { + display: inline-block; + padding: 0.1rem 0.4rem; + background-color: color.adjust(v.$background, $lightness: -3%); + color: v.$accentDark; + border: none; + border-radius: 1rem; + font-size: 0.75rem; + font-family: v.$monoFontStack; + text-decoration: none; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background-color: color.adjust(v.$accent, $lightness: 45%); + color: v.$accentDark; + transform: translateY(-1px); + } + } + + /* Efecto de transición para el filtrado */ + .post-item { + transition: all 0.3s ease; + } + + .post-section { + transition: opacity 0.3s ease; + } + + #blog-posts { + min-height: 200px; + } + + /* Mejora el aspecto de los enlaces de posts */ + .post-item { + margin-bottom: 0.5rem; + + a { + font-weight: 500; + } + } +</style> |
