💥 feat!: add tag filtering for projects (#431)

- Card (project) images no longer require manual top/bottom margin
adjustments for proper spacing. Action needed: Review existing card
images as previous manual margin adjustments may now be
unnecessary/excessive.
- Sites using `cards.html` with tags will now load JavaScript by
default when tags are present. To maintain no-JS behaviour, explicitly
set `enable_cards_tag_filtering = false` in either `config.toml` or the
`_index.md` file where `cards.html` is used.
main
welpo 3 months ago
parent f93def0692
commit a7833299ff
No known key found for this signature in database
GPG Key ID: A2F978CF4EC1F5A6

@ -231,6 +231,12 @@ compact_tags = false
# Default: "name". # Default: "name".
tag_sorting = "name" tag_sorting = "name"
# Show clickable tags above cards.html template (e.g. projects/) to filter the displayed items.
# Loads JS to filter. If JS is disabled, the buttons are links to the tag's page.
# Can be set at the section or config.toml level, following the hierarchy: section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
# Default: true
enable_cards_tag_filtering = true
# Invert the order of the site title and page title in the browser tab. # Invert the order of the site title and page title in the browser tab.
# Example: true => "Blog • ~/tabi", false => "~/tabi • Blog" # Example: true => "Blog • ~/tabi", false => "~/tabi • Blog"
invert_title_order = false invert_title_order = false

@ -1,7 +1,7 @@
+++ +++
title = "Sense JavaScript obligatori" title = "Sense JavaScript obligatori"
date = 2023-01-06 date = 2023-01-06
updated = 2024-08-28 updated = 2024-11-16
description = "JavaScript només s'utilitza quan HTML i CSS no són suficients." description = "JavaScript només s'utilitza quan HTML i CSS no són suficients."
[taxonomies] [taxonomies]
@ -29,6 +29,7 @@ Les següents opcions es poden especificar per a publicacions, seccions i global
- [**Diagrames de Mermaid**](@/blog/shortcodes/index.ca.md#diagrames-de-mermaid). Habilitat configurant `mermaid = true` (~2.5 MB). - [**Diagrames de Mermaid**](@/blog/shortcodes/index.ca.md#diagrames-de-mermaid). Habilitat configurant `mermaid = true` (~2.5 MB).
- [**Còpia de blocs de codi amb un sol clic**](@/blog/markdown/index.ca.md#bloc-de-codi). Habilitada configurant `copy_button = true`. (~700 bytes) - [**Còpia de blocs de codi amb un sol clic**](@/blog/markdown/index.ca.md#bloc-de-codi). Habilitada configurant `copy_button = true`. (~700 bytes)
- [**Mostrar ruta/URL a blocs de codi**](@/blog/shortcodes/index.ca.md#mostrar-ruta-o-url). S'activa configurant `add_src_to_code_block = true`. (~400 bytes) - [**Mostrar ruta/URL a blocs de codi**](@/blog/shortcodes/index.ca.md#mostrar-ruta-o-url). S'activa configurant `add_src_to_code_block = true`. (~400 bytes)
- [**Filtratge per etiquetes** per a graelles de targetes](@/blog/mastering-tabi-settings/index.ca.md#filtrar-projectes) (p. ex. [projectes](@/projects/_index.ca.md)) (~2KB). S'habilita configurant `enable_cards_tag_filtering = true`.
Per especificar aquestes opcions: Per especificar aquestes opcions:

@ -1,7 +1,7 @@
+++ +++
title = "Sin JavaScript obligatorio" title = "Sin JavaScript obligatorio"
date = 2023-01-06 date = 2023-01-06
updated = 2024-08-28 updated = 2024-11-16
description = "JavaScript solo se utiliza cuando HTML y CSS no son suficientes." description = "JavaScript solo se utiliza cuando HTML y CSS no son suficientes."
[taxonomies] [taxonomies]
@ -29,6 +29,7 @@ Las siguientes opciones pueden especificarse para publicaciones, secciones y a n
- [**Diagramas de Mermaid**](@/blog/shortcodes/index.es.md#diagramas-de-mermaid). Habilitado al configurar `mermaid = true` (~2.5 MB). - [**Diagramas de Mermaid**](@/blog/shortcodes/index.es.md#diagramas-de-mermaid). Habilitado al configurar `mermaid = true` (~2.5 MB).
- [**Copia de bloques de código con un solo clic**](@/blog/markdown/index.es.md#bloque-de-codigo). Habilitado al configurar `copy_button = true` (~700 bytes). - [**Copia de bloques de código con un solo clic**](@/blog/markdown/index.es.md#bloque-de-codigo). Habilitado al configurar `copy_button = true` (~700 bytes).
- [**Mostrar ruta/URL en bloques de código**](@/blog/shortcodes/index.es.md#mostrar-ruta-o-url). Se activa configurando `add_src_to_code_block = true`. (~400 bytes) - [**Mostrar ruta/URL en bloques de código**](@/blog/shortcodes/index.es.md#mostrar-ruta-o-url). Se activa configurando `add_src_to_code_block = true`. (~400 bytes)
- [**Filtraje por etiquetas** para cuadrículas de tarjetas](@/blog/mastering-tabi-settings/index.es.md#filtrar-proyectos) (p. ej. [proyectos](@/projects/_index.es.md)) (~2KB). Habilitado al configurar `enable_cards_tag_filtering = true`.
Para especificar estas opciones: Para especificar estas opciones:

@ -1,7 +1,7 @@
+++ +++
title = "No mandatory JavaScript" title = "No mandatory JavaScript"
date = 2023-01-06 date = 2023-01-06
updated = 2024-08-28 updated = 2024-11-16
description = "JavaScript is only used when HTML and CSS aren't enough." description = "JavaScript is only used when HTML and CSS aren't enough."
[taxonomies] [taxonomies]
@ -29,6 +29,7 @@ The following settings can be specified for posts, sections and globally, follow
- [**Mermaid diagrams**](@/blog/shortcodes/index.md#mermaid-diagrams). Enabled by setting `mermaid = true` (~2.5 MB). - [**Mermaid diagrams**](@/blog/shortcodes/index.md#mermaid-diagrams). Enabled by setting `mermaid = true` (~2.5 MB).
- [**One-click copy of code blocks**](@/blog/markdown/index.md#code-block). Enabled by setting `copy_button = true`. (~700 bytes) - [**One-click copy of code blocks**](@/blog/markdown/index.md#code-block). Enabled by setting `copy_button = true`. (~700 bytes)
- [**Showing source (path or URL) in code blocks**](@/blog/shortcodes/index.md#show-source-or-path). Enabled by setting `add_src_to_code_block = true`. (~300 bytes) - [**Showing source (path or URL) in code blocks**](@/blog/shortcodes/index.md#show-source-or-path). Enabled by setting `add_src_to_code_block = true`. (~300 bytes)
- [**Tag filtering** for card grids](@/blog/mastering-tabi-settings/index.md#filtering-projects) (e.g. [projects](@/projects/_index.md)) (~2KB). Enabled by setting `enable_cards_tag_filtering = true`.
To specify these settings: To specify these settings:

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

@ -1,7 +1,7 @@
+++ +++
title = "Domina la configuració de tabi: guia completa" title = "Domina la configuració de tabi: guia completa"
date = 2023-09-18 date = 2023-09-18
updated = 2024-11-14 updated = 2024-11-16
description = "Descobreix les múltiples maneres en què pots personalitzar tabi." description = "Descobreix les múltiples maneres en què pots personalitzar tabi."
[taxonomies] [taxonomies]
@ -391,6 +391,33 @@ Quan un usuari faci clic a la imatge o al títol d'un projecte, serà portat a l
La pàgina del projecte individual es renderitza amb la plantilla predeterminada, tret que estableixis una altra, per exemple, `template = "info-page.html"`. La pàgina del projecte individual es renderitza amb la plantilla predeterminada, tret que estableixis una altra, per exemple, `template = "info-page.html"`.
#### Filtrar projectes
Si afegeixes etiquetes als teus projectes, veuràs un filtre d'etiquetes:
{{ dual_theme_image(light_src="blog/mastering-tabi-settings/img/projects_tag_filter_light.webp", dark_src="blog/mastering-tabi-settings/img/projects_tag_filter_dark.webp", alt="Pàgina de projectes amb filtre d'etiquetes", full_width=true) }}
El sistema de filtratge d'etiquetes utilitza millora progressiva:
- Sense JavaScript: Les etiquetes enllacen directament a pàgines d'etiquetes dedicades (per exemple, `/tags/nom-de-l-etiqueta`).
- Amb JavaScript: Filtratge instantani, sincronització d'URL (#nom-etiqueta) i navegació amb el teclat.
Per desactivar aquesta funció, estableix `enable_cards_tag_filtering = false` a la secció `[extra]` del fitxer `projects/_index.md` o a `config.toml`.
{% admonition(type="tip") %}
Per filtrar projectes per etiquetes, necessites establir etiquetes a la front matter de cada projecte. Per exemple:
```toml
title = "nom del projecte"
weight = 40
[taxonomies]
tags = ["etiqueta", "etiqueta 2", "tercera etiqueta"]
```
{% end %}
### Arxiu ### Arxiu
Afegir una pàgina d'arxiu és similar a afegir una pàgina de projectes. Pots crear un directori a `content/archive/`. Allà, pots crear un fitxer `_index.md` amb el següent encapçalament: Afegir una pàgina d'arxiu és similar a afegir una pàgina de projectes. Pots crear un directori a `content/archive/`. Allà, pots crear un fitxer `_index.md` amb el següent encapçalament:

@ -1,7 +1,7 @@
+++ +++
title = "Domina la configuración de tabi: guía completa" title = "Domina la configuración de tabi: guía completa"
date = 2023-09-18 date = 2023-09-18
updated = 2024-11-14 updated = 2024-11-16
description = "Descubre las múltiples maneras en que puedes personalizar tabi." description = "Descubre las múltiples maneras en que puedes personalizar tabi."
[taxonomies] [taxonomies]
@ -391,6 +391,33 @@ Cuando un usuario haga clic en la imagen o el título de un proyecto, será llev
La página del proyecto individual se renderiza con la plantilla predeterminada, a menos que establezcas otra, por ejemplo, `template = "info-page.html"`. La página del proyecto individual se renderiza con la plantilla predeterminada, a menos que establezcas otra, por ejemplo, `template = "info-page.html"`.
#### Filtrar proyectos
Si agregas etiquetas a tus proyectos, verás un filtro de etiquetas:
{{ dual_theme_image(light_src="blog/mastering-tabi-settings/img/projects_tag_filter_light.webp", dark_src="blog/mastering-tabi-settings/img/projects_tag_filter_dark.webp", alt="Página de proyectos con filtro de etiquetas", full_width=true) }}
El sistema de filtrado de etiquetas utiliza mejora progresiva:
- Sin JavaScript: Las etiquetas enlazan directamente a páginas de etiquetas dedicadas (por ejemplo, `/tags/nombre-etiqueta`).
- Con JavaScript: Filtrado instantáneo, sincronización de URL (#nombre-etiqueta) y navegación por teclado.
Para desactivar esta función, establece `enable_cards_tag_filtering = false` en la sección `[extra]` del archivo `projects/_index.md` o en `config.toml`.
{% admonition(type="tip") %}
Para filtrar proyectos por etiquetas, necesitas establecer etiquetas en el front matter de cada proyecto. Por ejemplo:
```toml
title = "nombre del proyecto"
weight = 40
[taxonomies]
tags = ["etiqueta uno", "etiqueta 2", "tercera etiqueta"]
```
{% end %}
### Archivo ### Archivo
Agregar una página de archivo es similar a agregar una página de proyectos. Puedes crear un directorio en `content/archive/`. Allí, puedes crear un archivo `_index.md` con el siguiente encabezado: Agregar una página de archivo es similar a agregar una página de proyectos. Puedes crear un directorio en `content/archive/`. Allí, puedes crear un archivo `_index.md` con el siguiente encabezado:

@ -1,7 +1,7 @@
+++ +++
title = "Mastering tabi Settings: A Comprehensive Guide" title = "Mastering tabi Settings: A Comprehensive Guide"
date = 2023-09-18 date = 2023-09-18
updated = 2024-11-14 updated = 2024-11-16
description = "Discover the many ways you can customise your tabi site." description = "Discover the many ways you can customise your tabi site."
[taxonomies] [taxonomies]
@ -356,7 +356,7 @@ Clicking on this link will take you to the commit history of the post, where you
### Projects ### Projects
tabi has a built-in projects template. To enable it, you can create a directory in `content/projects/`. There, you can create a `_index.md` file with the following front matter: tabi has a built-in projects (cards) template. To enable it, you can create a directory in `content/projects/`. There, you can create a `_index.md` file with the following front matter:
```toml ```toml
title = "Projects" title = "Projects"
@ -396,6 +396,33 @@ When a user clicks on the image or title of a project, they will be taken to the
The individual project's page is rendered with the default template, unless you set another one, e.g. `template = "info-page.html"`. The individual project's page is rendered with the default template, unless you set another one, e.g. `template = "info-page.html"`.
#### Filtering Projects
If you add tags to your projects, you will see a tag filter:
{{ dual_theme_image(light_src="blog/mastering-tabi-settings/img/projects_tag_filter_light.webp", dark_src="blog/mastering-tabi-settings/img/projects_tag_filter_dark.webp", alt="Projects page with tag filter", full_width=true) }}
The tag filtering system uses progressive enhancement:
- Without JavaScript: Tags link directly to dedicated tag pages (e.g. `/tags/tag-name`)
- With JavaScript: Instant filtering, URL syncing (#tag-name), and keyboard navigation
To disable this feature, set `enable_cards_tag_filtering = false` in the `[extra]` section of the `projects/_index.md` file or in `config.toml`.
{% admonition(type="tip") %}
To filter projects by tags, you need to set tags in the front matter of each project. For example:
```toml
title = "project name"
weight = 40
[taxonomies]
tags = ["tag one", "tag 2", "third tag"]
```
{% end %}
### Archive ### Archive
Adding an archive page is similar to adding a projects page. You can create a directory in `content/archive/`. There, you can create a `_index.md` file with the following front matter: Adding an archive page is similar to adding a projects page. You can create a directory in `content/archive/`. There, you can create a `_index.md` file with the following front matter:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

@ -3,6 +3,9 @@ title = "dōteki"
description = "Afegeix contingut dinàmic al teu perfil de GitHub amb un sistema intuïtiu de plugins." description = "Afegeix contingut dinàmic al teu perfil de GitHub amb un sistema intuïtiu de plugins."
weight = 30 weight = 30
[taxonomies]
tags = ["GitHub Actions", "automatització", "Python"]
[extra] [extra]
local_image = "projects/doteki/doteki_logo.webp" local_image = "projects/doteki/doteki_logo.webp"
social_media_card = "social_cards/projects_doteki.jpg" social_media_card = "social_cards/projects_doteki.jpg"

@ -3,6 +3,9 @@ title = "dōteki"
description = "Añade contenido dinámico a tu perfil de GitHub con un sistema intuitivo de plugins." description = "Añade contenido dinámico a tu perfil de GitHub con un sistema intuitivo de plugins."
weight = 30 weight = 30
[taxonomies]
tags = ["GitHub Actions", "automatización", "Python"]
[extra] [extra]
local_image = "projects/doteki/doteki_logo.webp" local_image = "projects/doteki/doteki_logo.webp"
social_media_card = "social_cards/projects_doteki.jpg" social_media_card = "social_cards/projects_doteki.jpg"

@ -3,6 +3,9 @@ title = "dōteki"
description = "Add dynamic content to your GitHub profile through an intuitive plugin system." description = "Add dynamic content to your GitHub profile through an intuitive plugin system."
weight = 30 weight = 30
[taxonomies]
tags = ["GitHub Actions", "automation", "Python"]
[extra] [extra]
local_image = "projects/doteki/doteki_logo.webp" local_image = "projects/doteki/doteki_logo.webp"
social_media_card = "social_cards/projects_doteki.jpg" social_media_card = "social_cards/projects_doteki.jpg"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 22 KiB

@ -3,6 +3,9 @@ title = "git-sumi"
description = "El linter de missatges de commit no opinat basat en Rust." description = "El linter de missatges de commit no opinat basat en Rust."
weight = 10 weight = 10
[taxonomies]
tags = ["Git", "Rust", "Continuous Integration", "GitHub Actions", "CLI", "automatització"]
[extra] [extra]
local_image = "projects/git-sumi/git-sumi_logo.webp" local_image = "projects/git-sumi/git-sumi_logo.webp"
social_media_card = "social_cards/projects_git-sumi.jpg" social_media_card = "social_cards/projects_git-sumi.jpg"

@ -3,6 +3,9 @@ title = "git-sumi"
description = "El linter de mensajes de commit no opinado basado en Rust." description = "El linter de mensajes de commit no opinado basado en Rust."
weight = 10 weight = 10
[taxonomies]
tags = ["Git", "Rust", "Continuous Integration", "GitHub Actions", "CLI", "automatización"]
[extra] [extra]
local_image = "projects/git-sumi/git-sumi_logo.webp" local_image = "projects/git-sumi/git-sumi_logo.webp"
social_media_card = "social_cards/projects_git-sumi.jpg" social_media_card = "social_cards/projects_git-sumi.jpg"

@ -3,6 +3,9 @@ title = "git-sumi"
description = "The non-opinionated Rust-based commit message linter." description = "The non-opinionated Rust-based commit message linter."
weight = 10 weight = 10
[taxonomies]
tags = ["Git", "Rust", "Continuous Integration", "GitHub Actions", "CLI", "automation"]
[extra] [extra]
local_image = "projects/git-sumi/git-sumi_logo.webp" local_image = "projects/git-sumi/git-sumi_logo.webp"
social_media_card = "social_cards/projects_git-sumi.jpg" social_media_card = "social_cards/projects_git-sumi.jpg"

@ -3,6 +3,9 @@ title = "nani"
description = "Script Bash per crear URLs públiques a partir d'arxius o text en servidors remots." description = "Script Bash per crear URLs públiques a partir d'arxius o text en servidors remots."
weight = 50 weight = 50
[taxonomies]
tags = ["bash", "CLI"]
[extra] [extra]
local_image = "projects/nani/nani_logo.webp" local_image = "projects/nani/nani_logo.webp"
canonical_url = "https://osc.garden/ca/projects/tabi/" canonical_url = "https://osc.garden/ca/projects/tabi/"

@ -3,6 +3,9 @@ title = "nani"
description = "Script Bash para crear URLs públicas a partir de archivos o texto en servidores remotos." description = "Script Bash para crear URLs públicas a partir de archivos o texto en servidores remotos."
weight = 50 weight = 50
[taxonomies]
tags = ["bash", "CLI"]
[extra] [extra]
local_image = "projects/nani/nani_logo.webp" local_image = "projects/nani/nani_logo.webp"
canonical_url = "https://osc.garden/es/projects/tabi/" canonical_url = "https://osc.garden/es/projects/tabi/"

@ -3,6 +3,9 @@ title = "nani"
description = "Bash script to create public URLs from files or text on remote servers." description = "Bash script to create public URLs from files or text on remote servers."
weight = 50 weight = 50
[taxonomies]
tags = ["bash", "CLI"]
[extra] [extra]
local_image = "projects/nani/nani_logo.webp" local_image = "projects/nani/nani_logo.webp"
canonical_url = "https://osc.garden/projects/tabi/" canonical_url = "https://osc.garden/projects/tabi/"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 30 KiB

@ -3,8 +3,11 @@ title = "ラム (ramu)"
description = "Una aplicació web per practicar la lectura i comprensió auditiva de nombres en japonès." description = "Una aplicació web per practicar la lectura i comprensió auditiva de nombres en japonès."
weight = 30 weight = 30
[taxonomies]
tags = ["Japonès", "interactiu", "web app", "web", "PWA", "JavaScript"]
[extra] [extra]
local_image = "projects/ramu/ramu_logo.png" local_image = "projects/ramu/ramu_logo.webp"
canonical_url = "https://osc.garden/ca/projects/ramu/" canonical_url = "https://osc.garden/ca/projects/ramu/"
social_media_card = "social_cards/projects_ramu.jpg" social_media_card = "social_cards/projects_ramu.jpg"
+++ +++

@ -3,8 +3,11 @@ title = "ラム (ramu)"
description = "Una aplicación web para practicar la lectura y comprensión auditiva de números en japonés." description = "Una aplicación web para practicar la lectura y comprensión auditiva de números en japonés."
weight = 30 weight = 30
[taxonomies]
tags = ["Japonés", "interactivo", "web app", "web", "PWA", "JavaScript"]
[extra] [extra]
local_image = "projects/ramu/ramu_logo.png" local_image = "projects/ramu/ramu_logo.webp"
canonical_url = "https://osc.garden/es/projects/ramu/" canonical_url = "https://osc.garden/es/projects/ramu/"
social_media_card = "social_cards/projects_ramu.jpg" social_media_card = "social_cards/projects_ramu.jpg"
+++ +++

@ -3,8 +3,11 @@ title = "ラム (ramu)"
description = "A web app to practice reading and listening to Japanese numbers." description = "A web app to practice reading and listening to Japanese numbers."
weight = 30 weight = 30
[taxonomies]
tags = ["Japanese", "interactive", "web app", "web", "PWA", "JavaScript"]
[extra] [extra]
local_image = "projects/ramu/ramu_logo.png" local_image = "projects/ramu/ramu_logo.webp"
canonical_url = "https://osc.garden/projects/ramu/" canonical_url = "https://osc.garden/projects/ramu/"
social_media_card = "social_cards/projects_ramu.jpg" social_media_card = "social_cards/projects_ramu.jpg"
+++ +++

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@ -3,6 +3,9 @@ title = "Calculadora de royalties de streaming"
description = "Una eina per calcular els royalties de streaming per a músics." description = "Una eina per calcular els royalties de streaming per a músics."
weight = 45 weight = 45
[taxonomies]
tags = ["música", "web app", "web", "JavaScript", "anàlisi de dades"]
[extra] [extra]
local_image = "projects/streaming-royalties-calculator/streaming-royalties-calculator_logo.webp" local_image = "projects/streaming-royalties-calculator/streaming-royalties-calculator_logo.webp"
canonical_url = "https://osc.garden/ca/projects/streaming-royalties-calculator/" canonical_url = "https://osc.garden/ca/projects/streaming-royalties-calculator/"

@ -3,6 +3,9 @@ title = "Calculadora de royalties de streaming"
description = "Una herramienta para calcular los royalties de streaming para músicos." description = "Una herramienta para calcular los royalties de streaming para músicos."
weight = 45 weight = 45
[taxonomies]
tags = ["música", "web app", "web", "JavaScript", "análisis de datos"]
[extra] [extra]
local_image = "projects/streaming-royalties-calculator/streaming-royalties-calculator_logo.webp" local_image = "projects/streaming-royalties-calculator/streaming-royalties-calculator_logo.webp"
canonical_url = "https://osc.garden/es/projects/streaming-royalties-calculator/" canonical_url = "https://osc.garden/es/projects/streaming-royalties-calculator/"

@ -3,6 +3,9 @@ title = "Streaming Royalties Calculator"
description = "A tool to calculate streaming royalties for musicians." description = "A tool to calculate streaming royalties for musicians."
weight = 45 weight = 45
[taxonomies]
tags = ["music", "web app", "web", "JavaScript", "data analysis"]
[extra] [extra]
local_image = "projects/streaming-royalties-calculator/streaming-royalties-calculator_logo.webp" local_image = "projects/streaming-royalties-calculator/streaming-royalties-calculator_logo.webp"
canonical_url = "https://osc.garden/projects/streaming-royalties-calculator/" canonical_url = "https://osc.garden/projects/streaming-royalties-calculator/"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

@ -3,6 +3,9 @@ title = "tabi"
description = "Un tema de Zola ràpid, lleuger i modern amb suport multilingüe." description = "Un tema de Zola ràpid, lleuger i modern amb suport multilingüe."
weight = 40 weight = 40
[taxonomies]
tags = ["web", "JavaScript"]
[extra] [extra]
local_image = "projects/tabi/tabi.webp" local_image = "projects/tabi/tabi.webp"
canonical_url = "https://osc.garden/ca/projects/tabi/" canonical_url = "https://osc.garden/ca/projects/tabi/"
@ -11,7 +14,7 @@ social_media_card = "social_cards/ca_projects_tabi.jpg"
[**tabi**](https://github.com/welpo/tabi) és un tema modern i ric en funcionalitat per a [Zola](https://www.getzola.org/), un generador de llocs web estàtics molt ràpid. [**tabi**](https://github.com/welpo/tabi) és un tema modern i ric en funcionalitat per a [Zola](https://www.getzola.org/), un generador de llocs web estàtics molt ràpid.
{{ full_width_image(src="light_dark_tabi.webp", alt="Modes clar i fosc de tabi") }} {{ full_width_image(src="https://cdn.jsdelivr.net/gh/welpo/tabi@main/light_dark_screenshot.png", alt="Modes clar i fosc de tabi") }}
#### [Veure a GitHub](https://github.com/welpo/tabi) • [Demo i documentación](https://welpo.github.io/tabi/ca/) {.centered-text} #### [Veure a GitHub](https://github.com/welpo/tabi) • [Demo i documentación](https://welpo.github.io/tabi/ca/) {.centered-text}

@ -3,6 +3,9 @@ title = "tabi"
description = "Un tema de Zola rápido, ligero y moderno con soporte multilingüe." description = "Un tema de Zola rápido, ligero y moderno con soporte multilingüe."
weight = 40 weight = 40
[taxonomies]
tags = ["web", "JavaScript"]
[extra] [extra]
local_image = "projects/tabi/tabi.webp" local_image = "projects/tabi/tabi.webp"
canonical_url = "https://osc.garden/es/projects/tabi/" canonical_url = "https://osc.garden/es/projects/tabi/"
@ -11,7 +14,7 @@ social_media_card = "social_cards/es_projects_tabi.jpg"
[**tabi**](https://github.com/welpo/tabi) es un tema moderno y rico en funcionalidad para [Zola](https://www.getzola.org/), un generador de sitios web estáticos muy rápido. [**tabi**](https://github.com/welpo/tabi) es un tema moderno y rico en funcionalidad para [Zola](https://www.getzola.org/), un generador de sitios web estáticos muy rápido.
{{ full_width_image(src="light_dark_tabi.webp", alt="Modos claro y oscuro de tabi") }} {{ full_width_image(src="https://cdn.jsdelivr.net/gh/welpo/tabi@main/light_dark_screenshot.png", alt="Modos claro y oscuro de tabi") }}
#### [Ver en GitHub](https://github.com/welpo/tabi) • [Demo y documentación](https://welpo.github.io/tabi/es/) {.centered-text} #### [Ver en GitHub](https://github.com/welpo/tabi) • [Demo y documentación](https://welpo.github.io/tabi/es/) {.centered-text}

@ -3,6 +3,9 @@ title = "tabi"
description = "A feature-rich modern Zola theme with first-class multi-language support." description = "A feature-rich modern Zola theme with first-class multi-language support."
weight = 40 weight = 40
[taxonomies]
tags = ["web", "JavaScript"]
[extra] [extra]
local_image = "projects/tabi/tabi.webp" local_image = "projects/tabi/tabi.webp"
social_media_card = "social_cards/projects_tabi.jpg" social_media_card = "social_cards/projects_tabi.jpg"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 62 KiB

@ -1,3 +1,44 @@
.filter-controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 12px;
margin-top: 1.2rem;
margin-bottom: -1rem;
padding: 0;
list-style: none;
#all-projects-filter {
display: none;
}
.taxonomy-item {
margin: 0;
a {
display: inline-block;
box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;
border-radius: 1rem;
background: var(--bg-2);
padding: 0 16px;
color: var(--text-color);
font-size: 0.8rem;
text-decoration: none;
&:hover {
background: var(--primary-color);
color: var(--hover-color);
}
&.active {
background: var(--primary-color);
color: var(--hover-color);
}
}
}
}
.cards { .cards {
display: grid; display: grid;
grid-template-rows: auto; grid-template-rows: auto;
@ -27,13 +68,12 @@
padding-block-end: 24px; padding-block-end: 24px;
text-align: center; text-align: center;
} }
.card-title {
margin-top: 0.7em;
}
.card-image { .card-image {
width: 100%; $margin: 1.6rem;
margin: $margin;
margin-bottom: $margin / 1.5;
width: calc(100% - $margin * 2);
height: auto;
} }
.card-image-placeholder { .card-image-placeholder {
@ -53,4 +93,13 @@
.cards { .cards {
gap: 18px; gap: 18px;
} }
.filter-controls {
gap: 8px;
margin: 18px 0;
.taxonomy-item a {
padding: 4px 12px;
}
}
} }

@ -11,6 +11,19 @@ $padding: 2.5rem;
align-items: flex-start; align-items: flex-start;
background-color: var(--navbar-color); background-color: var(--navbar-color);
padding-block: $padding; padding-block: $padding;
min-width: 13.5rem;
.thumbnail-image {
margin: 0;
margin-inline: auto; // Centred by default.
max-width: 70%;
}
li.date + li.post-thumbnail .thumbnail-image {
// Styles for the thumbnail when there's a date above (either date or updated).
margin-inline: 0; // Since metadata is left aligned, the image looks weird when centred.
margin-block-start: 0.7rem;
}
ul { ul {
margin-inline-end: 0.7rem; margin-inline-end: 0.7rem;
@ -24,10 +37,6 @@ $padding: 2.5rem;
white-space: nowrap; white-space: nowrap;
} }
li.date {
width: 13.5rem;
}
li.draft-label { li.draft-label {
width: fit-content; width: fit-content;
line-height: 1.2rem; line-height: 1.2rem;
@ -137,6 +146,10 @@ $padding: 2.5rem;
margin-inline-end: 0.3rem; margin-inline-end: 0.3rem;
} }
} }
.post-thumbnail {
display: none;
}
} }
.bloglist-content { .bloglist-content {

@ -0,0 +1,99 @@
document.addEventListener('DOMContentLoaded', () => {
const cards = document.querySelectorAll('.card');
const filterLinks = document.querySelectorAll('.filter-controls a');
const allProjectsFilter = document.querySelector('#all-projects-filter');
if (!cards.length || !filterLinks.length) return;
allProjectsFilter.style.display = 'block';
// Create a Map for O(1) lookups of links by filter value.
const linkMap = new Map(
Array.from(filterLinks).map(link => [link.dataset.filter, link])
);
// Pre-process cards data for faster filtering.
const cardData = Array.from(cards).map(card => ({
element: card,
tags: card.dataset.tags?.toLowerCase().split(',').filter(Boolean) ?? []
}));
function getTagSlugFromUrl(url) {
return url.split('/').filter(Boolean).pop();
}
function getFilterFromHash() {
if (!window.location.hash) return 'all';
const hash = decodeURIComponent(window.location.hash.slice(1));
const matchingLink = Array.from(filterLinks).find(link =>
getTagSlugFromUrl(link.getAttribute('href')) === hash
);
return matchingLink?.dataset.filter ?? 'all';
}
function setActiveFilter(filterValue, updateHash = true) {
if (updateHash) {
if (filterValue === 'all') {
history.pushState(null, '', window.location.pathname);
} else {
const activeLink = linkMap.get(filterValue);
if (activeLink) {
const tagSlug = getTagSlugFromUrl(activeLink.getAttribute('href'));
history.pushState(null, '', `#${tagSlug}`);
}
}
}
const isAll = filterValue === 'all';
const display = isAll ? '' : 'none';
const ariaHidden = isAll ? 'false' : 'true';
requestAnimationFrame(() => {
filterLinks.forEach(link => {
const isActive = link.dataset.filter === filterValue;
link.classList.toggle('active', isActive);
link.setAttribute('aria-pressed', isActive);
});
if (isAll) {
cardData.forEach(({ element }) => {
element.style.display = display;
element.setAttribute('aria-hidden', ariaHidden);
});
} else {
cardData.forEach(({ element, tags }) => {
const shouldShow = tags.includes(filterValue);
element.style.display = shouldShow ? '' : 'none';
element.setAttribute('aria-hidden', !shouldShow);
});
}
});
}
const filterContainer = filterLinks[0].parentElement.parentElement;
filterContainer.addEventListener('click', e => {
const link = e.target.closest('a');
if (!link) return;
e.preventDefault();
const filterValue = link.dataset.filter;
if (filterValue) setActiveFilter(filterValue);
});
filterContainer.addEventListener('keydown', e => {
const link = e.target.closest('a');
if (!link) return;
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
link.click();
}
});
filterLinks.forEach(link => {
link.setAttribute('role', 'button');
link.setAttribute('aria-pressed', link.classList.contains('active'));
});
window.addEventListener('popstate', () => {
setActiveFilter(getFilterFromHash(), false);
});
const initialFilter = getFilterFromHash();
if (initialFilter !== 'all') {
setActiveFilter(initialFilter, false);
}
});

@ -0,0 +1 @@
document.addEventListener("DOMContentLoaded",()=>{var t=document.querySelectorAll(".card");const l=document.querySelectorAll(".filter-controls a");var e=document.querySelector("#all-projects-filter");if(t.length&&l.length){e.style.display="block";const s=new Map(Array.from(l).map(t=>[t.dataset.filter,t])),i=Array.from(t).map(t=>({element:t,tags:t.dataset.tags?.toLowerCase().split(",").filter(Boolean)??[]}));function o(t){return t.split("/").filter(Boolean).pop()}function a(){if(!window.location.hash)return"all";const e=decodeURIComponent(window.location.hash.slice(1));return Array.from(l).find(t=>o(t.getAttribute("href"))===e)?.dataset.filter??"all"}function r(a,t=!0){t&&("all"===a?history.pushState(null,"",window.location.pathname):(t=s.get(a))&&(t=o(t.getAttribute("href")),history.pushState(null,"","#"+t)));const e="all"===a,r=e?"":"none",n=e?"false":"true";requestAnimationFrame(()=>{l.forEach(t=>{var e=t.dataset.filter===a;t.classList.toggle("active",e),t.setAttribute("aria-pressed",e)}),e?i.forEach(({element:t})=>{t.style.display=r,t.setAttribute("aria-hidden",n)}):i.forEach(({element:t,tags:e})=>{e=e.includes(a),t.style.display=e?"":"none",t.setAttribute("aria-hidden",!e)})})}(e=l[0].parentElement.parentElement).addEventListener("click",t=>{var e=t.target.closest("a");e&&(t.preventDefault(),t=e.dataset.filter)&&r(t)}),e.addEventListener("keydown",t=>{var e=t.target.closest("a");!e||" "!==t.key&&"Enter"!==t.key||(t.preventDefault(),e.click())}),l.forEach(t=>{t.setAttribute("role","button"),t.setAttribute("aria-pressed",t.classList.contains("active"))}),window.addEventListener("popstate",()=>{r(a(),!1)}),"all"!==(t=a())&&r(t,!1)}});

@ -18,6 +18,11 @@
{%- set show_pages = section.pages -%} {%- set show_pages = section.pages -%}
{% endif -%} {% endif -%}
{%- if macros_settings::evaluate_setting_priority(setting="enable_cards_tag_filtering", page=section, default_global_value=true) == "true" -%}
{%- include "partials/filter_card_tags.html" -%}
{%- endif -%}
{%- include "partials/cards_pages.html" -%} {%- include "partials/cards_pages.html" -%}
</main> </main>

@ -92,6 +92,25 @@
{%- endif -%} {%- endif -%}
{%- endif -%} {%- endif -%}
{% if post.extra.local_image or post.extra.remote_image %}
<li class="post-thumbnail">
<a href="{{ post.permalink }}">
{% if post.extra.local_image %}
{% set meta = get_image_metadata(path=post.extra.local_image, allow_missing=true) %}
<img class="thumbnail-image"
alt="{{ post.extra.local_image }}"
src="{{ get_url(path=post.extra.local_image) }}"
{% if meta.width %}width="{{ meta.width }}"{% endif %}
{% if meta.height %}height="{{ meta.height }}"{% endif %}>
{% elif post.extra.remote_image %}
<img class="thumbnail-image"
alt="{{ post.extra.remote_image }}"
src="{{ post.extra.remote_image }}">
{% endif %}
</a>
</li>
{% endif %}
{% if post.draft %} {% if post.draft %}
<li class="draft-label">{{ macros_translate::translate(key="draft", default="DRAFT", language_strings=language_strings) }}</li> <li class="draft-label">{{ macros_translate::translate(key="draft", default="DRAFT", language_strings=language_strings) }}</li>
{% endif %} {% endif %}

@ -61,6 +61,7 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
</pre></div> </pre></div>
{% set settings_to_test = [ {% set settings_to_test = [
"enable_cards_tag_filtering",
"footnote_backlinks", "footnote_backlinks",
"add_src_to_code_block", "add_src_to_code_block",
"force_codeblock_ltr", "force_codeblock_ltr",

@ -15,13 +15,24 @@
{% set target_url = page.extra.link_to | default(value=page.permalink) %} {% set target_url = page.extra.link_to | default(value=page.permalink) %}
<a rel="{{ rel_attributes }}" {{ blank_target }} href="{{ target_url }}" class="card"> <a rel="{{ rel_attributes }}"
<div> {{ blank_target }}
href="{{ target_url }}"
class="card"
{% if page.taxonomies %}
data-tags="{% for tax_name, terms in page.taxonomies %}{% for term in terms | unique %}{{ term | lower }}{% if not loop.last %},{% endif %}{% endfor %}{% endfor %}"
{% endif %}>
{% if page.extra.local_image %} {% if page.extra.local_image %}
{% set meta = get_image_metadata(path=page.extra.local_image, allow_missing=true) %} {% set meta = get_image_metadata(path=page.extra.local_image, allow_missing=true) %}
<img class="card-image" alt="{{ page.extra.local_image }}" src="{{ get_url(path=page.extra.local_image) }}" {% if meta.width %}width="{{ meta.width }}" {% endif %} {% if meta.height %}height="{{ meta.height }}" {% endif %}> <img class="card-image"
alt="{{ page.extra.local_image }}"
src="{{ get_url(path=page.extra.local_image) }}"
{% if meta.width %}width="{{ meta.width }}"{% endif %}
{% if meta.height %}height="{{ meta.height }}"{% endif %}>
{% elif page.extra.remote_image %} {% elif page.extra.remote_image %}
<img class="card-image" alt="{{ page.extra.remote_image }}" src="{{ page.extra.remote_image }}"> <img class="card-image"
alt="{{ page.extra.remote_image }}"
src="{{ page.extra.remote_image }}">
{% else %} {% else %}
<div class="card-image-placeholder"></div> <div class="card-image-placeholder"></div>
{% endif %} {% endif %}
@ -34,7 +45,6 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
</a> </a>
{% endfor -%} {% endfor -%}
</div> </div>

@ -0,0 +1,34 @@
{#- Collect all terms. -#}
{#- We don't use `get_taxonomy` so users aren't forced to use 'tags' -#}
{% set all_terms = [] %}
{% for page in show_pages %}
{% if page.taxonomies %}
{% for tax_name, terms in page.taxonomies %}
{% for term in terms %}
{% set_global all_terms = all_terms | concat(with=term) %}
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}
{#- Display unique terms -#}
{%- if all_terms -%}
<ul class="filter-controls" role="group" aria-label="{{ macros_translate::translate(key='project_filters', default='Project filters', language_strings=language_strings) }}">
<li class="taxonomy-item no-hover-padding">
<a id="all-projects-filter" class="no-hover-padding active"
href="{{ get_url(path="projects", lang=lang) }}"
data-filter="all">
{{- macros_translate::translate(key="all_projects", default="All projects", language_strings=language_strings) -}}
</a>
</li>
{% for term in all_terms | unique | sort %}
<li class="taxonomy-item no-hover-padding">
<a class="no-hover-padding"
href="{{ get_taxonomy_url(kind="tags", name=term, lang=lang) }}"
data-filter="{{ term | lower }}">{{ term }}</a>
</li>
{% endfor %}
</ul>
{#- Load the script -#}
<script src="{{ get_url(path='js/filterCards.min.js', trailing_slash=false, cachebust=true) | safe }}" defer></script>
{% endif %}

@ -188,6 +188,12 @@ compact_tags = false
# Default: "name". # Default: "name".
tag_sorting = "name" tag_sorting = "name"
# Show clickable tags above cards.html template (e.g. projects/) to filter the displayed items.
# Loads JS to filter. If JS is disabled, the buttons are links to the tag's page.
# Can be set at the section or config.toml level, following the hierarchy: section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
# Default: true
enable_cards_tag_filtering = true
# Invert the order of the site title and page title in the browser tab. # Invert the order of the site title and page title in the browser tab.
# Example: true => "Blog • ~/tabi", false => "~/tabi • Blog" # Example: true => "Blog • ~/tabi", false => "~/tabi • Blog"
invert_title_order = false invert_title_order = false

Loading…
Cancel
Save