aboutsummaryrefslogtreecommitdiff
path: root/src/layouts/BlogListLayout.astro
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-06-05 20:03:27 +0200
committerAriel Costas Guerrero <ariel@costas.dev>2025-06-05 20:03:27 +0200
commita2830a0dd6f634147456406c7855881ff298078e (patch)
tree93af1b60258b0b19a739b294fa31f201c2d64158 /src/layouts/BlogListLayout.astro
parenta423c9b15bdf43d28390fb0424dfeec012d82828 (diff)
Refresh portfolio design and fonts
Diffstat (limited to 'src/layouts/BlogListLayout.astro')
-rw-r--r--src/layouts/BlogListLayout.astro325
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>