diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/content.config.ts | 1 | ||||
| -rw-r--r-- | src/data/blog/americans-so-much-freedom.md | 1 | ||||
| -rw-r--r-- | src/data/blog/configurar-php-iis.md | 1 | ||||
| -rw-r--r-- | src/data/blog/europa-responder-aranceles.md | 1 | ||||
| -rw-r--r-- | src/data/blog/gobierno-no-aprueba-leyes.md | 1 | ||||
| -rw-r--r-- | src/data/blog/mapa-facil-protomaps.md | 1 | ||||
| -rw-r--r-- | src/data/blog/shitshow-wordpress.md | 1 | ||||
| -rw-r--r-- | src/data/blog/vibe-coding-stop.md | 1 | ||||
| -rw-r--r-- | src/pages/blog/[id].astro | 61 | ||||
| -rw-r--r-- | src/pages/blog/index.astro | 265 |
10 files changed, 320 insertions, 14 deletions
diff --git a/src/content.config.ts b/src/content.config.ts index 3d69039..a0391a8 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -7,6 +7,7 @@ const blog = defineCollection({ title: z.string(), description: z.string(), publishedAt: z.coerce.date(), + tags: z.array(z.string()).default([]), }), }); diff --git a/src/data/blog/americans-so-much-freedom.md b/src/data/blog/americans-so-much-freedom.md index 0d15709..c576827 100644 --- a/src/data/blog/americans-so-much-freedom.md +++ b/src/data/blog/americans-so-much-freedom.md @@ -2,6 +2,7 @@ title: "Americans: so much for your \"freedom\"" description: "Regarding the \"freedom\" of Americans, the ban of TikTok, and whether Europe should follow suit" publishedAt: 2025-01-17 +tags: ["política", "derecho", "internacional"] --- <div class="note" role="alert"> diff --git a/src/data/blog/configurar-php-iis.md b/src/data/blog/configurar-php-iis.md index cce6051..8441874 100644 --- a/src/data/blog/configurar-php-iis.md +++ b/src/data/blog/configurar-php-iis.md @@ -2,6 +2,7 @@ title: "Alojar aplicación PHP en servidor IIS" description: "Un breve tutorial de cómo alojar una aplicación PHP en un servidor IIS para desarrollo" publishedAt: 2024-08-21 +tags: ["desarrollo web", "php"] --- En este tutorial, aprenderás a alojar una aplicación PHP en un servidor IIS. IIS es un servidor web desarrollado por Microsoft que viene instalado con Windows y es usable tanto en Windows Server como en escritorios Windows 10/11 Pro y Enterprise. diff --git a/src/data/blog/europa-responder-aranceles.md b/src/data/blog/europa-responder-aranceles.md index 004382f..4a7811f 100644 --- a/src/data/blog/europa-responder-aranceles.md +++ b/src/data/blog/europa-responder-aranceles.md @@ -2,6 +2,7 @@ title: "Europa debe responder con firmeza a los aranceles de EE.UU." description: "Un análisis de los recientes aranceles estadounidenses y una propuesta de estrategia de respuesta europea." publishedAt: 2025-04-21 +tags: ["política", "comercio", "economía"] --- Estados Unidos ha lanzado una nueva ofensiva arancelaria bajo el mandato de Trump, y desde Europa no podemos seguir dedicándonos a la "vida contemplativa" al respecto. diff --git a/src/data/blog/gobierno-no-aprueba-leyes.md b/src/data/blog/gobierno-no-aprueba-leyes.md index 6a29387..c8e846b 100644 --- a/src/data/blog/gobierno-no-aprueba-leyes.md +++ b/src/data/blog/gobierno-no-aprueba-leyes.md @@ -2,6 +2,7 @@ title: "El gobierno no 'aprueba una ley' sobre el derecho a rectificación" description: "Hablemos de la frase 'el gobierno aprueba una ley' y por qué no es correcta, poniendo como ejemplo la ley de derecho a rectificación en prensa" publishedAt: 2024-12-17 +tags: ["politica", "derecho", "españa"] --- Hoy leemos la noticia en la prensa española que dice que "El Gobierno aprueba la ley del Derecho de Rectificación ante bulos e incluye a 'influencers'" (ABC), con los demás periódicos diciendo lo mismo. Pero, ¿es correcto decir que el Gobierno aprueba una ley? diff --git a/src/data/blog/mapa-facil-protomaps.md b/src/data/blog/mapa-facil-protomaps.md index 371faf8..59aff03 100644 --- a/src/data/blog/mapa-facil-protomaps.md +++ b/src/data/blog/mapa-facil-protomaps.md @@ -2,6 +2,7 @@ title: "Crear un mapa de manera fácil con Protomaps" description: "Una guía rápida para crear un mapa de manera fácil con Protomaps, sin servidor ni historias complicadas" publishedAt: 2025-01-03 +tags: ["desarrollo web", "GIS"] --- Durante las últimas semanas, he estado explorando tecnologías de mapas para integrar en proyectos que voy a llevar a cabo en el futuro. diff --git a/src/data/blog/shitshow-wordpress.md b/src/data/blog/shitshow-wordpress.md index e72ab53..a10f9ef 100644 --- a/src/data/blog/shitshow-wordpress.md +++ b/src/data/blog/shitshow-wordpress.md @@ -2,6 +2,7 @@ title: "El espectáculo que está montando WordPress (o mejor dicho, Matt Mullenweg)" description: "Hablemos de la polémica que ha surgido en torno a WordPress y su fundador, Matt Mullenweg, contra WPEngine y todo lo que ha desencadenado." publishedAt: 2024-10-13 +tags: ["desarrollo web", "open source"] --- Hace unas semanas que Matt Mullenweg comenzó una batalla contra WPEngine, uno de los mayores proveedores de hosting de WordPress. Pero para entender esta historia, primero hay que aclarar quien es quien: diff --git a/src/data/blog/vibe-coding-stop.md b/src/data/blog/vibe-coding-stop.md index ffee1e3..61d61e4 100644 --- a/src/data/blog/vibe-coding-stop.md +++ b/src/data/blog/vibe-coding-stop.md @@ -2,6 +2,7 @@ title: "Vibe coding, la nueva moda estúpida" description: "Reflexiones sobre el 'vibe coding' y su impacto en la industria del software." publishedAt: 2025-04-13 +tags: ["programación", "IA"] --- <div role="alert" class="warning"> diff --git a/src/pages/blog/[id].astro b/src/pages/blog/[id].astro index 65c8a25..935a796 100644 --- a/src/pages/blog/[id].astro +++ b/src/pages/blog/[id].astro @@ -61,7 +61,68 @@ const schema = { <time datetime={entry.data.publishedAt.toISOString()}> {formattedDate} </time> + {entry.data.tags && entry.data.tags.length > 0 && ( + <> + • Etiquetas: + <ul class="tags"> + {entry.data.tags.map((tag: string) => ( + <li><a href={`/blog/?tag=${encodeURIComponent(tag)}`}>{tag}</a></li> + ))} + </ul> + </> + )} </small> <Content /> </Layout> + +<style lang="scss"> + @use "../../../styles/variables" as v; + @use "sass:color"; + + .tags { + display: inline-flex; + list-style: none; + margin: 0; + padding: 0; + gap: 0.75rem; + } + + .tags li { + display: inline; + } + + .tags a { + // Estilo de enlace normal, siguiendo los estilos predefinidos en Layout.astro + color: v.$accentDark; + font-size: 0.85rem; + font-family: v.$monoFontStack; + text-decoration: none; + box-shadow: 0 1px v.$accent; + transition: all 0.2s ease; + + &:hover { + box-shadow: 0 2px v.$accentDark; + } + + &:focus { + color: v.$accentDark; + outline: none; + background-color: v.$secondary; + box-shadow: 0 4px #0b0c0c; + } + } + + /* Estilos para la información de la publicación */ + small { + display: block; + margin-top: -1rem; + margin-bottom: 1.5rem; + font-size: 0.85rem; + color: color.adjust(v.$dark, $lightness: 30%); + } + + time { + font-style: italic; + } +</style> diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index 68ad02e..b74781b 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -6,6 +6,7 @@ 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(); @@ -20,6 +21,9 @@ const groupedPosts = blogCollection.reduce( {}, ); +// 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", @@ -64,18 +68,251 @@ const schema = { son mías, y no representan a ninguna empresa o institución. </p> - { - Object.entries(groupedPosts).map(([key, posts]) => ( - <section> - <h2>{humaniseDate(new Date(key))}</h2> - <ul> - {posts.map((post) => ( - <li> - <a href={`/blog/${post.id}`}>{post.data.title}</a> - </li> - ))} - </ul> - </section> - )) - } + {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> + // Script para el filtrado de artículos por etiqueta + 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'); + + // Función para filtrar los artículos por etiqueta + function filterByTag(tag: string) { + // Primero, restablecer la visibilidad de todos los elementos + postItems.forEach(item => { + (item as HTMLElement).style.display = ''; + }); + postSections.forEach(section => { + (section as HTMLElement).style.display = ''; + }); + + // Si no es 'todas', filtrar por etiqueta + 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'; + } + }); + + // Ocultar secciones que no tienen artículos visibles + 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'; + } + }); + } + + // Actualizar estado activo de los botones + tagButtons.forEach(button => { + if ((button as HTMLElement).dataset.tag === tag) { + button.classList.add('active'); + } else { + button.classList.remove('active'); + } + }); + + // Actualizar URL con el parámetro de consulta + if (tag === 'all') { + history.replaceState(null, document.title, window.location.pathname); + } else { + history.replaceState(null, document.title, `?tag=${encodeURIComponent(tag)}`); + } + + // Añadir un log para depuración + console.log(`Filtrado por etiqueta: ${tag}`); + console.log(`Artículos visibles: ${document.querySelectorAll('.post-item:not([style*="display: none"])')?.length || 0}`); + } + + // Eventos de clic para los botones de etiquetas + tagButtons.forEach(button => { + button.addEventListener('click', () => { + const tag = (button as HTMLElement).dataset.tag; + if (tag) filterByTag(tag); + }); + }); + + // Eventos de clic para los enlaces de etiquetas dentro de los posts + tagLinks.forEach(link => { + link.addEventListener('click', (e) => { + e.preventDefault(); + const tag = (link as HTMLElement).dataset.tag; + if (tag) filterByTag(tag); + + // Desplazamiento suave hacia arriba para ver todos los resultados + const tagsContainer = document.querySelector('.tags-container'); + if (tagsContainer) { + window.scrollTo({ + top: (tagsContainer as HTMLElement).offsetTop - 20, + behavior: 'smooth' + }); + } + }); + }); + + // 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> |
