feat: add series functionality (#406)

Co-authored-by: welpo <welpo@users.noreply.github.com>
main
ZzMzaw 4 months ago committed by GitHub
parent 57a0a8e1a0
commit 0253799f23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -38,6 +38,7 @@ tabi has a perfect score on Google's Lighthouse audit:
- [X] [Set any language as default](https://welpo.github.io/tabi/blog/faq-languages/#how-do-i-set-a-default-language-for-my-site). Set your base site to Chinese, Spanish, French, Hindi… or any [other supported language](/i18n). The theme's interface will be translated accordingly. - [X] [Set any language as default](https://welpo.github.io/tabi/blog/faq-languages/#how-do-i-set-a-default-language-for-my-site). Set your base site to Chinese, Spanish, French, Hindi… or any [other supported language](/i18n). The theme's interface will be translated accordingly.
- [X] [Integration with remote repositories](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#git-repository-integration) on GitHub, GitLab, Gitea & Codeberg for commit history and showing the site source. - [X] [Integration with remote repositories](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#git-repository-integration) on GitHub, GitLab, Gitea & Codeberg for commit history and showing the site source.
- [X] [Series support](https://welpo.github.io/tabi/blog/series/) for creating sequential content like tutorials, courses, and multi-part stories.
- [X] Dark and light themes. Defaults to the OS setting, with a switcher in the navigation bar. - [X] Dark and light themes. Defaults to the OS setting, with a switcher in the navigation bar.
- [X] Thorough documentation. See [Mastering tabi Settings: A Comprehensive Guide](https://welpo.github.io/tabi/blog/mastering-tabi-settings/). - [X] Thorough documentation. See [Mastering tabi Settings: A Comprehensive Guide](https://welpo.github.io/tabi/blog/mastering-tabi-settings/).
- [X] Perfect Lighthouse score (Performance, Accessibility, Best Practices and SEO). - [X] Perfect Lighthouse score (Performance, Accessibility, Best Practices and SEO).

@ -157,6 +157,16 @@ show_date = true
# "both" - Show both the original date and the last updated date. # "both" - Show both the original date and the last updated date.
post_listing_date = "date" post_listing_date = "date"
# Show "Jump to posts" link next to series' title.
# By default, the link appears automatically when a series description exceeds 2000 characters.
# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
# show_jump_to_posts = true
# Determines if indexes should be increasing (false) or decreasing (true) in series' posts list.
# It has only effect if the section uses indexes metadata (which is only the case for series as of now).
# Can be set at section levels, following the hierarchy: section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
post_listing_index_reversed = false # Defaults to false.
# DEPRECATED! # DEPRECATED!
# Use Zola's built-in `bottom_footnotes = true` in the [markdown] section instead. (Available since v0.19.0) # Use Zola's built-in `bottom_footnotes = true` in the [markdown] section instead. (Available since v0.19.0)
# Adds backlinks to footnotes (loads ~500 bytes of JavaScripts). # Adds backlinks to footnotes (loads ~500 bytes of JavaScripts).

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

@ -267,6 +267,34 @@ Si configures `tag_sorting = "frequency"`, s'ordenaran segons el nombre de publi
--- ---
### Sèries
Per a una explicació detallada, consulta la [documentació de sèries](@/blog/series/index.ca.md).
#### Enllaç per saltar a les publicacions
| Pàgina | Secció | `config.toml` | Segueix la jerarquia | Requereix JavaScript |
|:------:|:-------:|:-------------:|:------------------:|:-------------------:|
| ❌ | ✅ | ✅ | ✅ | ❌ |
Per defecte, apareix automàticament un enllaç "Salta a les publicacions" al costat del títol de la sèrie quan una sèrie té un contingut de més de 2000 caràcters:
{{ dual_theme_image(light_src="blog/series/img/jump_to_series_posts_light.webp", dark_src="blog/series/img/jump_to_series_posts_dark.webp" alt="enllaç per saltar a les publicacions de la sèrie", full_width=true) }}
Estableix `show_jump_to_posts = true` per forçar l'activació de la funció i `show_jump_to_posts = false` per desactivar-la.
#### Indexació de pàgines de sèries
| Pàgina | Secció | `config.toml` | Segueix la jerarquia | Requereix JavaScript |
|:------:|:-------:|:-------------:|:------------------:|:-------------------:|
| ❌ | ✅ | ✅ | ✅ | ❌ |
Per defecte, les pàgines de sèries s'indexen (usant una indexació basada en 1) segons el `sort_by` de la secció de sèries.
Estableix `post_listing_index_reversed = true` per invertir aquest índex.
---
## Integració amb repositoris Git ## Integració amb repositoris Git
| Pàgina | Secció | `config.toml` | Segueix la jerarquia | Requereix JavaScript | | Pàgina | Secció | `config.toml` | Segueix la jerarquia | Requereix JavaScript |

@ -267,6 +267,34 @@ Si configuras `tag_sorting = "frequency"`, se ordenarán según el número de pu
--- ---
### Series
Para una explicación detallada, consulta la [documentación de series](@/blog/series/index.es.md).
#### Enlace para saltar a las publicaciones
| Página | Sección | `config.toml` | Sigue jerarquía | Requiere JavaScript |
|:------:|:-------:|:-------------:|:------------------:|:-------------------:|
| ❌ | ✅ | ✅ | ✅ | ❌ |
Por defecto, aparece automáticamente un enlace "Saltar a publicaciones" junto al título de la serie cuando una serie tiene un contenido de más de 2000 caracteres:
{{ dual_theme_image(light_src="blog/series/img/jump_to_series_posts_light.webp", dark_src="blog/series/img/jump_to_series_posts_dark.webp" alt="enlace para saltar a las publicaciones de la serie", full_width=true) }}
Establece `show_jump_to_posts = true` para forzar la activación de la función y `show_jump_to_posts = false` para desactivarla.
#### Indexación de páginas de series
| Página | Sección | `config.toml` | Sigue la jerarquía | Requiere JavaScript |
|:------:|:-------:|:-------------:|:------------------:|:-------------------:|
| ❌ | ✅ | ✅ | ✅ | ❌ |
Por defecto, las páginas de series se indexan (usando una indexación basada en 1) según el `sort_by` de la sección de series.
Establece `post_listing_index_reversed = true` para invertir el índice.
---
## Integración con repositorios Git ## Integración con repositorios Git
| Página | Sección | `config.toml` | Sigue la jerarquía | Requiere JavaScript | | Página | Sección | `config.toml` | Sigue la jerarquía | Requiere JavaScript |

@ -272,6 +272,34 @@ Setting `tag_sorting = "frequency"` will sort them by number-of-posts (descendin
--- ---
### Series
For a detailed explanation of the series feature, see the [series documentation](@/blog/series/index.md).
#### Jump to posts link
| Page | Section | `config.toml` | Follows Hierarchy | Requires JavaScript |
|:----:|:-------:|:-------------:|:-----------------:|:-------------------:|
| ❌ | ✅ | ✅ | ✅ | ❌ |
By default, a "Jump to posts" link automatically appears next to the series title when a series has a content over 2000 characters:
{{ dual_theme_image(light_src="blog/series/img/jump_to_series_posts_light.webp", dark_src="blog/series/img/jump_to_series_posts_dark.webp" alt="jump to series posts link", full_width=true) }}
Set `show_jump_to_posts = true` to force the feature on and `show_jump_to_posts = false` to force it off.
#### Series pages indexation
| Page | Section | `config.toml` | Follows Hierarchy | Requires JavaScript |
|:----:|:-------:|:-------------:|:-----------------:|:-------------------:|
| ❌ | ✅ | ✅ | ✅ | ❌ |
By default, series page are indexed (using a 1-based indexing) as per the series section `sort_by`.
Set `post_listing_index_reversed = true` to reverse this index.
---
## Git Repository Integration ## Git Repository Integration
| Page | Section | `config.toml` | Follows Hierarchy | Requires JavaScript | | Page | Section | `config.toml` | Follows Hierarchy | Requires JavaScript |

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@ -0,0 +1,424 @@
+++
title = "Guia completa sobre sèries"
date = 2024-11-08
description = "Aprèn a organitzar les teves publicacions en sèries seqüencials, perfectes per a tutorials, cursos i històries de diverses parts."
[taxonomies]
tags = ["funcionalitat", "tutorial", "preguntes freqüents", "sèries"]
[extra]
quick_navigation_buttons = true
toc = true
mermaid = true
social_media_card = "social_cards/ca_blog_series.jpg"
+++
Una sèrie organitza publicacions relacionades en ordre seqüencial, similar als capítols d'un llibre. A diferència de les etiquetes, que simplement agrupen contingut relacionat, les sèries suggereixen un ordre específic de lectura de principi a fi.
Les publicacions dins d'una sèrie no necessiten publicar-se de forma consecutiva; la funció de sèries reuneix publicacions temàticament vinculades en una seqüència coherent.
El següent diagrama il·lustra com les publicacions de la sèrie (3, 5 i 8) existeixen dins del flux principal del blog mentre mantenen la seva pròpia seqüència ordenada dins de Sèrie 1.
{% mermaid(full_width=true) %}
flowchart
subgraph main[BLOG]
P1[Post 1]
P2[P2]
P3[P3]
P4[P4]
P5[P5]
P6[P6]
P7[P7]
P8[P8]
P9[P9]
end
subgraph series1[SÈRIE 1]
PS1["Post Sèrie 1 (=P3)"]
PS2["Post Sèrie 2 (=P5)"]
PS3["Post Sèrie 3 (=P8)"]
end
P3 o-.-o PS1
P5 o-.-o PS2
P8 o-.-o PS3
{% end %}
## Inici ràpid
1. Crea un directori per a la teva sèrie
2. Crea `_index.md` al directori de la sèrie
3. Configura el front matter de `_index.md`:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
title = "Aprenent Rust"
template = "series.html"
sort_by = "slug"
transparent = true
[extra]
series = true
```
4. Crea els teus articles de la sèrie en aquest directori
Vols saber-ne més? Continua llegint!
## Com funcionen les sèries?
Una sèrie és simplement una secció que tabi gestiona de manera especial. Per a més detalls sobre seccions, consulta la [documentació de Zola](https://www.getzola.org/documentation/content/section/).
Prenent l'exemple del diagrama anterior, l'estructura de directoris seria així:
```txt
content/
_index.md
blog/
_index.md
post1/
index.md
post2/
index.md
post4/
index.md
post6/
index.md
post7/
index.md
post9/
index.md
serie1/
_index.md
post3/
index.md
post5/
index.md
post8/
index.md
```
Per crear una sèrie, necessites:
1. Utilitzar la plantilla `series.html`
2. Establir `series = true` a la configuració `[extra]` de la secció
3. Activar `transparent = true` per integrar les publicacions de la sèrie amb la secció del blog principal
La pàgina principal de la sèrie mostra un resum seguit d'una llista de totes les publicacions a la sèrie:
{{ dual_theme_image(light_src="blog/series/img/series_light.webp", dark_src="blog/series/img/series_dark.webp" alt="una sèrie", full_width=true) }}
## Saltar a les publicacions
Si el contingut d'una sèrie (el Markdown després del frontmatter a `_index.md`) supera els 2000 caràcters, apareix un enllaç "Salta a les publicacions" al costat del títol de la sèrie.
{{ dual_theme_image(light_src="blog/series/img/jump_to_series_posts_light.webp", dark_src="blog/series/img/jump_to_series_posts_dark.webp" alt="enllaç per saltar a les publicacions de la sèrie", full_width=true) }}
Per forçar l'activació o desactivació d'aquesta funció, configura `show_jump_to_posts` a la secció `[extra]` de la teva secció de sèries o a `config.toml`. Aquesta configuració segueix [la jerarquia](@/blog/mastering-tabi-settings/index.ca.md#jerarquia-de-configuracio).
## Pàgines de sèries i ordre
Totes les pàgines a la secció de sèries seran pàgines de sèrie. Les pàgines s'ordenaran segons el `sort_by` de la secció.
Tot i que les sèries mantenen el seu propi ordre intern, romanen independents del flux cronològic de la secció principal (per exemple, `blog/`) gràcies a la configuració `transparent`.
### Opcions d'ordre
Tria entre aquests mètodes d'ordre, cadascun amb els seus avantatges:
{% wide_container() %}
`sort_by` | avantatges | desavantatges
---------|------------|---------------
`slug` | L'ordre de les pàgines és explícit a la ruta (per exemple, `example.com/blog/series1/01-series-post-un`). | Cada pàgina de la sèrie ha de tenir el prefix corresponent.
`weight` | L'ordre de les pàgines és fàcil de configurar de forma transparent.<br>La primera publicació té pes `1`, la segona pes `2` i així successivament. | Cada pàgina de la sèrie ha de tenir el seu pes configurat.
`date` | L'ordre de les pàgines es pot configurar una sola vegada a la configuració de la secció. No cal fer res a cada pàgina. | L'ordre de les pàgines s'ha d'invertir perquè la primera pàgina sol ser la més antiga. Això només es pot aconseguir paginant la secció (`paginate_by = 9999`) i invertint el seu ordre (`paginate_reversed = true`).
{% end %}
{{ admonition(type="danger", title="Versió de Zola per ordenar per data", text="Per invertir correctament les dates, es requereix Zola v0.19.3+ (no publicada) perquè la informació de paginació estigui disponible a través de la funció `get_section`. En cas contrari, qualsevol cosa que depengui de l'ordre de les pàgines de la sèrie no serà correcta (per exemple, pàgina anterior/següent, llistes ordenades i no ordenades...) Vegeu [Zola PR #2653](https://github.com/getzola/zola/pull/2653).") }}
### Indexació de pàgines
Les pàgines en una sèrie s'indexen començant des d'1, seguint el seu ordre `sort_by`. Per invertir la indexació (fent que la primera pàgina tingui l'índex més alt), afegeix aquesta configuració a `_index.md` o `config.toml`:
```toml
[extra]
post_listing_index_reversed = true # Per defecte és false si no es configura
```
{{ dual_theme_image(light_src="blog/series/img/series_reversed_light.webp", dark_src="blog/series/img/series_reversed_dark.webp" alt="una sèrie amb índexs invertits", full_width=true) }}
Aquesta configuració segueix [la jerarquia](@/blog/mastering-tabi-settings/index.ca.md#jerarquia-de-configuracio).
## Plantilles d'introducció i conclusió
Els articles d'una sèrie poden tenir seccions automàtiques d'introducció i conclusió. Aquestes es configuren al `_index.md` de la teva sèrie. Un exemple bàsic:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra.series_intro_templates]
default = "Aquest article és part de la sèrie $SERIES_HTML_LINK."
[extra.series_outro_templates]
default = "Gràcies per llegir la part $SERIES_PAGE_INDEX de $SERIES_HTML_LINK!"
```
Les seccions d'introducció i conclusió tenen les seves pròpies classes CSS (`series-page-intro` i `series-page-outro`), que et permeten personalitzar la seva aparença mitjançant [CSS personalitzat](@/blog/mastering-tabi-settings/index.ca.md#estils-css-personalitzats).
### Tipus de plantilles
El sistema de sèries utilitza diferents plantilles segons la posició de l'article a la sèrie:
- `next_only` - Utilitzat per al primer article (té article següent però no anterior)
- `middle` - Utilitzat per a articles amb articles anterior i següent
- `prev_only` - Utilitzat per a l'últim article (té article anterior però no següent)
- `default` - Plantilla per defecte utilitzada quan no existeix una plantilla específica per a la posició
El sistema determina automàticament quina plantilla utilitzar segons la posició de l'article. Les plantilles es defineixen a la configuració de la sèrie (`_index.md`), com `extra.series_intro_templates` i `extra.series_outro_templates`:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra.series_intro_templates]
next_only = "Benvingut a la part 1! Següent: $NEXT_HTML_LINK"
middle = "Anterior: $PREV_HTML_LINK | Següent: $NEXT_HTML_LINK"
prev_only = "El capítol final! Anteriorment: $PREV_HTML_LINK"
default = "Part $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER"
```
Totes les plantilles són opcionals. La selecció de plantilles segueix un sistema de prioritat:
1. Si existeix una plantilla específica per a la posició (`next_only`, `middle`, o `prev_only`), s'utilitzarà aquesta
2. Si no, s'utilitza la plantilla `default`
3. Si no es defineix cap plantilla, no es mostrarà informació de la sèrie
Mira l'[exemple de plantilla](#exemple-de-plantilla) per veure un exemple més elaborat.
### Ubicació al contingut
Per defecte:
- Les introduccions de sèrie apareixen a l'inici del teu article
- La conclusió apareix al final (abans de les notes al peu, si n'hi ha)
Pots controlar exactament on apareixen utilitzant `<!-- series_intro -->` i `<!-- series_outro -->` al teu Markdown:
```markdown
Aquest paràgraf apareix abans de la introducció de la sèrie.
<!-- series_intro -->
Contingut principal de l'article.
<!-- series_outro -->
## Recursos d'aprenentatge
Contingut addicional...
[^1]: Les notes al peu sempre apareixeran al final.
```
## Variables
Les plantilles de sèries utilitzen un sistema flexible de variables que et permet:
1. Fer referència a informació de la sèrie (títol, enllaços)
2. Afegir navegació entre articles
3. Mostrar indicadors de progrés
4. Incloure informació personalitzada utilitzant les teves pròpies variables
Les variables són marcadors que comencen amb `$` i es reemplacen amb contingut real quan es construeix el teu lloc. Per exemple, `$SERIES_HTML_LINK` es converteix en un enllaç clicable a la pàgina índex de la teva sèrie.
Hi ha tres tipus de variables:
- [Variables bàsiques de sèrie](#variables-basiques-de-serie): Informació general sobre la sèrie
- [Variables de navegació](#variables-de-navegacio): Enllaços a articles anterior/següent
- [Variables personalitzades](#variables-personalitzades): Els teus propis marcadors per a informació addicional
### Variables bàsiques de sèrie
{% wide_container() %}
| Variable | Disponibilitat | Retorna | Descripció | Exemple d'ús | Exemple de sortida |
|----------|---------------|----------|------------|--------------|-------------------|
| `$SERIES_TITLE` | Sempre | Text | Títol de la sèrie en text pla | `Part de $SERIES_TITLE` | Part d'Aprenent Rust |
| `$SERIES_PERMALINK` | Sempre | Text | URL a l'índex de la sèrie | `[Veure totes les publicacions]($SERIES_PERMALINK)` | [Veure totes les publicacions](/series/learn-rust) |
| `$SERIES_HTML_LINK` | Sempre | HTML | Enllaç llest per usar a la sèrie | `Benvingut a $SERIES_HTML_LINK!` | Benvingut a <a href="/series/learn-rust">Aprenent Rust</a>! |
| `$SERIES_PAGES_NUMBER` | Sempre | Nombre | Total d'articles a la sèrie | `Una sèrie de $SERIES_PAGES_NUMBER parts` | Una sèrie de 5 parts |
| `$SERIES_PAGE_INDEX` | Sempre | Nombre | Posició de l'article actual | `Part $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER` | Part 3 de 5 |
| `$SERIES_PAGES_OLIST` | Sempre | HTML | Llista ordenada de tots els articles | `Articles a la sèrie: $SERIES_PAGES_OLIST` | Articles a la sèrie: <ol><li>Article actual</li><li><a href="...">Altres articles</a></li></ol> |
| `$SERIES_PAGES_ULIST` | Sempre | HTML | Llista desordenada de tots els articles | `Articles a la sèrie: $SERIES_PAGES_ULIST` | Articles a la sèrie: <ul><li>Article actual</li><li><a href="...">Altres articles</a></li></ul> |
{% end %}
### Variables de navegació
{% wide_container() %}
| Variable | Disponibilitat | Retorna | Descripció | Exemple d'ús | Exemple de sortida |
|----------|---------------|----------|------------|--------------|-------------------|
| `$PREV_TITLE` | Existeix anterior | Text | Títol de l'article anterior | `Anteriorment: $PREV_TITLE` | Anteriorment: Configurant el teu entorn |
| `$PREV_PERMALINK` | Existeix anterior | Text | URL a l'article anterior | `[← Enrere]($PREV_PERMALINK)` | [← Enrere](/series/learn-rust/setup) |
| `$PREV_HTML_LINK` | Existeix anterior | HTML | Enllaç llest per usar a l'anterior | `Llegeix primer $PREV_HTML_LINK` | Llegeix primer <a href="/series/learn-rust/setup">Configurant el teu entorn</a> |
| `$PREV_DESCRIPTION` | Existeix anterior | Text | Descripció de l'article anterior | `Resum: $PREV_DESCRIPTION` | Resum: Configurant Rust |
| `$NEXT_TITLE` | Existeix següent | Text | Títol del següent article | `Següent: $NEXT_TITLE` | Següent: Patrons avançats |
| `$NEXT_PERMALINK` | Existeix següent | Text | URL al següent article | `[Continuar →]($NEXT_PERMALINK)` | [Continuar →](/series/learn-rust/patterns) |
| `$NEXT_HTML_LINK` | Existeix següent | HTML | Enllaç llest per usar al següent | `Continua amb $NEXT_HTML_LINK` | Continua amb <a href="/series/learn-rust/patterns">Patrons avançats</a> |
| `$NEXT_DESCRIPTION` | Existeix següent | Text | Descripció del següent article | `Properament: $NEXT_DESCRIPTION` | Properament: Aprèn sobre les característiques avançades de pattern matching en Rust |
{% end %}
### Referència al primer article
{% wide_container() %}
| Variable | Disponibilitat | Retorna | Descripció | Exemple d'ús | Exemple de sortida |
|----------|---------------|----------|------------|--------------|-------------------|
| `$FIRST_TITLE` | Sempre | Text | Títol del primer article | `Comença amb $FIRST_TITLE` | Comença amb Introducció a Rust |
| `$FIRST_HTML_LINK` | Sempre | HTML | Enllaç llest per usar al primer article | `Comença a $FIRST_HTML_LINK` | Comença a <a href="/series/learn-rust/intro">Introducció a Rust</a> |
{% end %}
### Exemple de plantilla
{{ admonition(type="tip", title="Variables HTML vs text", text="Utilitza variables HTML (que acaben en `_HTML_LINK`) quan vulguis enllaços preparats per usar. Utilitza variables de text (que acaben en `_TITLE` o `_PERMALINK`) quan vulguis més control sobre el format.") }}
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
# Introducció
[extra.series_intro_templates]
next_only = """
Benvingut a $SERIES_HTML_LINK! Aquesta sèrie de $SERIES_PAGES_NUMBER parts t'ensenyarà Rust des de zero.
Següent: $NEXT_HTML_LINK - $NEXT_DESCRIPTION
"""
middle = """
📚 Part $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER a $SERIES_HTML_LINK
Anterior: $PREV_HTML_LINK
Següent: $NEXT_HTML_LINK
"""
prev_only = """
Benvingut a l'última part de $SERIES_HTML_LINK!
Ets nou? Comença amb $FIRST_HTML_LINK per construir una base sòlida.
Anterior: $PREV_HTML_LINK
"""
# Plantilla de respatller
default = "Aquest article és part de la sèrie $SERIES_HTML_LINK."
# Conclusió
[extra.series_outro_templates]
next_only = """
Gràcies per llegir! 🙌
Continua el teu viatge amb $NEXT_HTML_LINK, on $NEXT_DESCRIPTION
O revisa l'esquema complet de la sèrie [$SERIES_TITLE]($SERIES_PERMALINK).
"""
middle = """
---
📝 Navegació de la sèrie
- Anterior: $PREV_HTML_LINK
- Següent: $NEXT_HTML_LINK
- [Resum de la sèrie]($SERIES_PERMALINK)
"""
prev_only = """
🎉 Felicitats! Has completat $SERIES_HTML_LINK.
Vols repassar? Aquí vam començar: $FIRST_HTML_LINK
O revisa el que acabem de veure a $PREV_HTML_LINK.
"""
# Respatller.
default = """
---
Aquest article és la part $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER a $SERIES_HTML_LINK.
"""
```
### Variables personalitzades
Les plantilles de sèries admeten variables personalitzades per incloure informació addicional a tota la teva sèrie. El procés té dos passos:
1. Primer, defineix els teus **marcadors** a la configuració de la teva sèrie (`_index.md`):
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra]
series = true
series_template_placeholders = ["$POSITION", "$TOPIC", "$DIFFICULTY"]
```
2. Després, a cada article de la sèrie, proporciona els valors per a aquests marcadors a `series_template_variables`:
{{ add_src_to_code_block(src="series/article.md") }}
```toml
[extra.series_template_variables]
position = "primer"
topic = "Variables i tipus"
difficulty = "Principiant"
```
### Ús de variables personalitzades
Pots usar les teves variables personalitzades a qualsevol plantilla, juntament amb les variables integrades:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra.series_intro_templates]
default = """
Aquest és l'article $POSITION a $SERIES_HTML_LINK.
Tema d'avui: $TOPIC
Nivell de dificultat: $DIFFICULTY
"""
```
{{ admonition(type="warning", text="Encara que els marcadors es defineixen en majúscules (`$POSITION`), els noms de variables a `series_template_variables` han d'estar en minúscules (`position`).") }}
### Exemple amb variables personalitzades
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
# A la configuració de la sèrie.
[extra]
series = true
series_template_placeholders = ["$LEARNING_TIME", "$KEY_CONCEPTS"]
series_intro_templates.default = """
📚 Part $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER
⏱️ Temps estimat: $LEARNING_TIME
🔑 Conceptes clau: $KEY_CONCEPTS
"""
```
{{ add_src_to_code_block(src="series/02-learning-rust/index.md") }}
```toml
# En un article de la sèrie.
[extra.series_template_variables]
learning_time = "30 minuts"
key_concepts = "Funcions, gestió d'errors, coincidència de patrons"
```
Això generarà:
```txt
📚 Part 2 de 5
⏱️ Temps estimat: 30 minuts
🔑 Conceptes clau: Funcions, gestió d'errors, coincidència de patrons
```
{{ admonition(type="warning", title="Variables que falten", text="Si uses un marcador a les teves plantilles però no proporciones el seu valor a `series_template_variables`, la compilació fallarà amb un error que llista les variables que falten.") }}

@ -0,0 +1,424 @@
+++
title = "Guía completa sobre series"
date = 2024-11-08
description = "Aprende a organizar tus publicaciones en series secuenciales, perfectas para tutoriales, cursos e historias de varias partes."
[taxonomies]
tags = ["funcionalidad", "tutorial", "preguntas frecuentes", "series"]
[extra]
quick_navigation_buttons = true
toc = true
mermaid = true
social_media_card = "social_cards/es_blog_series.jpg"
+++
Una serie organiza publicaciones relacionadas en orden secuencial, similar a los capítulos de un libro. A diferencia de las etiquetas, que simplemente agrupan contenido relacionado, las series sugieren un orden específico de lectura de principio a fin.
Las publicaciones dentro de una serie no necesitan publicarse de forma consecutiva; la función de series reúne publicaciones temáticamente vinculadas en una secuencia coherente.
El siguiente diagrama ilustra cómo las publicaciones de la serie (3, 5 y 8) existen dentro del flujo principal del blog mientras mantienen su propia secuencia ordenada dentro de Serie 1.
{% mermaid(full_width=true) %}
flowchart
subgraph main[BLOG]
P1[Post 1]
P2[P2]
P3[P3]
P4[P4]
P5[P5]
P6[P6]
P7[P7]
P8[P8]
P9[P9]
end
subgraph series1[SERIE 1]
PS1["Post Serie 1 (=P3)"]
PS2["Post Serie 2 (=P5)"]
PS3["Post Serie 3 (=P8)"]
end
P3 o-.-o PS1
P5 o-.-o PS2
P8 o-.-o PS3
{% end %}
## Inicio rápido
1. Crea un directorio para tu serie
2. Crea `_index.md` en el directorio de la serie
3. Configura el front matter de `_index.md`:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
title = "Aprendiendo Rust"
template = "series.html"
sort_by = "slug"
transparent = true
[extra]
series = true
```
4. Crea tus artículos de la serie en este directorio
¿Quieres saber más? ¡Sigue leyendo!
## ¿Cómo funcionan las series?
Una serie es simplemente una sección que tabi maneja de manera especial. Para más detalles sobre secciones, consulta la [documentación de Zola](https://www.getzola.org/documentation/content/section/).
Tomando el ejemplo del diagrama anterior, la estructura de directorios sería así:
```txt
content/
_index.md
blog/
_index.md
post1/
index.md
post2/
index.md
post4/
index.md
post6/
index.md
post7/
index.md
post9/
index.md
serie1/
_index.md
post3/
index.md
post5/
index.md
post8/
index.md
```
Para crear una serie, necesitas:
1. Usar la plantilla `series.html`
2. Establecer `series = true` en la configuración `[extra]` de la sección
3. Activar `transparent = true` para integrar las publicaciones de la serie con la sección del blog principal
La página principal de la serie muestra un resumen seguido de una lista de todas las publicaciones en la serie:
{{ dual_theme_image(light_src="blog/series/img/series_light.webp", dark_src="blog/series/img/series_dark.webp" alt="una serie", full_width=true) }}
## Saltar a las publicaciones
Si el contenido de una serie (el Markdown después del frontmatter en `_index.md`) supera los 2000 caracteres, aparece un enlace "Saltar a publicaciones" junto al título de la serie.
{{ dual_theme_image(light_src="blog/series/img/jump_to_series_posts_light.webp", dark_src="blog/series/img/jump_to_series_posts_dark.webp" alt="enlace para saltar a las publicaciones de la serie", full_width=true) }}
Para forzar la activación o desactivación de esta función, configura `show_jump_to_posts` en la sección `[extra]` de tu sección de series o en `config.toml`. Esta configuración sigue [la jerarquía](@/blog/mastering-tabi-settings/index.es.md#jerarquia-de-configuracion).
## Páginas de series y orden
Todas las páginas en la sección de series serán páginas de serie. Las páginas se ordenarán según el `sort_by` de la sección.
Aunque las series mantienen su propio orden interno, permanecen independientes del flujo cronológico de la sección principal (por ejemplo, `blog/`) gracias a la configuración `transparent`.
### Opciones de orden
Elige entre estos métodos de orden, cada uno con sus ventajas:
{% wide_container() %}
`sort_by` | ventajas | desventajas
---------|-------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
`slug` | El orden de las páginas es explícito en la ruta (por ejemplo, `example.com/blog/series1/01-series-post-uno`). | Cada página de la serie debe tener el prefijo correspondiente.
`weight` | El orden de las páginas es fácil de configurar de forma transparente.<br>La primera publicación tiene peso `1`, la segunda peso `2` y así sucesivamente. | Cada página de la serie debe tener su peso configurado.
`date` | El orden de las páginas se puede configurar una sola vez en la configuración de la sección. No hay que hacer nada en cada página. | El orden de las páginas debe invertirse porque la primera página suele ser la más antigua. Esto solo se puede lograr paginando la sección (`paginate_by = 9999`) e invirtiendo su orden (`paginate_reversed = true`).
{% end %}
{{ admonition(type="danger", title="Versión de Zola para ordenar por fecha", text="Para invertir correctamente las fechas, se requiere Zola v0.19.3+ (no publicada) para que la información de paginación esté disponible a través de la función `get_section`. De lo contrario, cualquier cosa que dependa del orden de las páginas de la serie no será correcta (por ejemplo, página anterior/siguiente, listas ordenadas y no ordenadas...) Ver [Zola PR #2653](https://github.com/getzola/zola/pull/2653).") }}
### Indexación de páginas
Las páginas en una serie se indexan empezando desde 1, siguiendo su orden `sort_by`. Para invertir la indexación (haciendo que la primera página tenga el índice más alto), añade esta configuración a `_index.md` o `config.toml`:
```toml
[extra]
post_listing_index_reversed = true # Por defecto es false si no se configura
```
{{ dual_theme_image(light_src="blog/series/img/series_reversed_light.webp", dark_src="blog/series/img/series_reversed_dark.webp" alt="una serie con índices invertidos", full_width=true) }}
Esta configuración sigue [la jerarquía](@/blog/mastering-tabi-settings/index.es.md#jerarquia-de-configuracion).
## Plantillas de introducción y conclusión
Los artículos de una serie pueden tener secciones automáticas de introducción y conclusión. Estas se configuran en el `_index.md` de tu serie. Un ejemplo básico:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra.series_intro_templates]
default = "Este artículo es parte de la serie $SERIES_HTML_LINK."
[extra.series_outro_templates]
default = "¡Gracias por leer la parte $SERIES_PAGE_INDEX de $SERIES_HTML_LINK!"
```
Las secciones de introducción y conclusión tienen sus propias clases CSS (`series-page-intro` y `series-page-outro`), lo que te permite personalizar su apariencia mediante [CSS personalizado](@/blog/mastering-tabi-settings/index.es.md#estilos-css-personalizados).
### Tipos de plantillas
El sistema de series usa diferentes plantillas según la posición del artículo en la serie:
- `next_only` - Usado para el primer artículo (tiene artículo siguiente pero no anterior)
- `middle` - Usado para artículos con artículos anterior y siguiente
- `prev_only` - Usado para el último artículo (tiene artículo anterior pero no siguiente)
- `default` - Plantilla por defecto usada cuando no existe una plantilla específica para la posición
El sistema determina automáticamente qué plantilla usar según la posición del artículo. Las plantillas se definen en la configuración de la serie (`_index.md`), como `extra.series_intro_templates` y `extra.series_outro_templates`:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra.series_intro_templates]
next_only = "¡Bienvenido a la parte 1! Siguiente: $NEXT_HTML_LINK"
middle = "Anterior: $PREV_HTML_LINK | Siguiente: $NEXT_HTML_LINK"
prev_only = "¡El capítulo final! Anteriormente: $PREV_HTML_LINK"
default = "Parte $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER"
```
Todas las plantillas son opcionales. La selección de plantillas sigue un sistema de prioridad:
1. Si existe una plantilla específica para la posición (`next_only`, `middle`, o `prev_only`), se usará esa
2. Si no, se usa la plantilla `default`
3. Si no se define ninguna plantilla, no se mostrará información de la serie
Mira el [ejemplo de plantilla](#ejemplo-de-plantilla) para ver un ejemplo más elaborado.
### Ubicación en el contenido
Por defecto:
- Las introducciones de serie aparecen al inicio de tu artículo
- La conclusión aparece al final (antes de las notas al pie, si las hay)
Puedes controlar exactamente dónde aparecen usando `<!-- series_intro -->` y `<!-- series_outro -->` en tu Markdown:
```markdown
Este párrafo aparece antes de la introducción de la serie.
<!-- series_intro -->
Contenido principal del artículo.
<!-- series_outro -->
## Recursos de aprendizaje
Contenido adicional...
[^1]: Las notas al pie siempre aparecerán al final.
```
## Variables
Las plantillas de series usan un sistema flexible de variables que te permite:
1. Hacer referencia a información de la serie (título, enlaces)
2. Añadir navegación entre artículos
3. Mostrar indicadores de progreso
4. Incluir información personalizada usando tus propias variables
Las variables son marcadores que comienzan con `$` y se reemplazan con contenido real cuando se construye tu sitio. Por ejemplo, `$SERIES_HTML_LINK` se convierte en un enlace clicable a la página índice de tu serie.
Hay tres tipos de variables:
- [Variables básicas de serie](#variables-basicas-de-serie): Información general sobre la serie
- [Variables de navegación](#variables-de-navegacion): Enlaces a artículos anterior/siguiente
- [Variables personalizadas](#variables-personalizadas): Tus propios marcadores para información adicional
### Variables básicas de serie
{% wide_container() %}
| Variable | Disponibilidad | Devuelve | Descripción | Ejemplo de uso | Ejemplo de salida |
|----------|---------------|-----------|-------------|----------------|-------------------|
| `$SERIES_TITLE` | Siempre | Texto | Título de la serie en texto plano | `Parte de $SERIES_TITLE` | Parte de Aprendiendo Rust |
| `$SERIES_PERMALINK` | Siempre | Texto | URL al índice de la serie | `[Ver todas las publicaciones]($SERIES_PERMALINK)` | [Ver todas las publicaciones](/series/learn-rust) |
| `$SERIES_HTML_LINK` | Siempre | HTML | Enlace listo para usar a la serie | `¡Bienvenido a $SERIES_HTML_LINK!` | ¡Bienvenido a <a href="/series/learn-rust">Aprendiendo Rust</a>! |
| `$SERIES_PAGES_NUMBER` | Siempre | Número | Total de artículos en la serie | `Una serie de $SERIES_PAGES_NUMBER partes` | Una serie de 5 partes |
| `$SERIES_PAGE_INDEX` | Siempre | Número | Posición del artículo actual | `Parte $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER` | Parte 3 de 5 |
| `$SERIES_PAGES_OLIST` | Siempre | HTML | Lista ordenada de todos los artículos | `Artículos en la serie: $SERIES_PAGES_OLIST` | Artículos en la serie: <ol><li>Artículo actual</li><li><a href="...">Otros artículos</a></li></ol> |
| `$SERIES_PAGES_ULIST` | Siempre | HTML | Lista desordenada de todos los artículos | `Artículos en la serie: $SERIES_PAGES_ULIST` | Artículos en la serie: <ul><li>Artículo actual</li><li><a href="...">Otros artículos</a></li></ul> |
{% end %}
### Variables de navegación
{% wide_container() %}
| Variable | Disponibilidad | Devuelve | Descripción | Ejemplo de uso | Ejemplo de salida |
|----------|---------------|-----------|-------------|----------------|-------------------|
| `$PREV_TITLE` | Existe anterior | Texto | Título del artículo anterior | `Anteriormente: $PREV_TITLE` | Anteriormente: Configurando tu entorno |
| `$PREV_PERMALINK` | Existe anterior | Texto | URL al artículo anterior | `[← Atrás]($PREV_PERMALINK)` | [← Atrás](/series/learn-rust/setup) |
| `$PREV_HTML_LINK` | Existe anterior | HTML | Enlace listo para usar al anterior | `Lee primero $PREV_HTML_LINK` | Lee primero <a href="/series/learn-rust/setup">Configurando tu entorno</a> |
| `$PREV_DESCRIPTION` | Existe anterior | Texto | Descripción del artículo anterior | `Resumen: $PREV_DESCRIPTION` | Resumen: Configurando Rust |
| `$NEXT_TITLE` | Existe siguiente | Texto | Título del siguiente artículo | `Siguiente: $NEXT_TITLE` | Siguiente: Patrones avanzados |
| `$NEXT_PERMALINK` | Existe siguiente | Texto | URL al siguiente artículo | `[Continuar →]($NEXT_PERMALINK)` | [Continuar →](/series/learn-rust/patterns) |
| `$NEXT_HTML_LINK` | Existe siguiente | HTML | Enlace listo para usar al siguiente | `Continúa con $NEXT_HTML_LINK` | Continúa con <a href="/series/learn-rust/patterns">Patrones avanzados</a> |
| `$NEXT_DESCRIPTION` | Existe siguiente | Texto | Descripción del siguiente artículo | `Próximamente: $NEXT_DESCRIPTION` | Próximamente: Aprende sobre las características avanzadas de pattern matching en Rust |
{% end %}
### Referencia al primer artículo
{% wide_container() %}
| Variable | Disponibilidad | Devuelve | Descripción | Ejemplo de uso | Ejemplo de salida |
|----------|---------------|-----------|-------------|----------------|-------------------|
| `$FIRST_TITLE` | Siempre | Texto | Título del primer artículo | `Comienza con $FIRST_TITLE` | Comienza con Introducción a Rust |
| `$FIRST_HTML_LINK` | Siempre | HTML | Enlace listo para usar al primer artículo | `Empieza en $FIRST_HTML_LINK` | Empieza en <a href="/series/learn-rust/intro">Introducción a Rust</a> |
{% end %}
### Ejemplo de plantilla
{{ admonition(type="tip", title="Variables HTML vs texto", text="Usa variables HTML (que terminan en `_HTML_LINK`) cuando quieras enlaces listos para usar. Usa variables de texto (que terminan en `_TITLE` o `_PERMALINK`) cuando quieras más control sobre el formato.") }}
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
# Introducción.
[extra.series_intro_templates]
next_only = """
¡Bienvenido a $SERIES_HTML_LINK! Esta serie de $SERIES_PAGES_NUMBER partes te enseñará Rust desde cero.
Siguiente: $NEXT_HTML_LINK - $NEXT_DESCRIPTION
"""
middle = """
📚 Parte $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER en $SERIES_HTML_LINK
Anterior: $PREV_HTML_LINK
Siguiente: $NEXT_HTML_LINK
"""
prev_only = """
¡Bienvenido a la última parte de $SERIES_HTML_LINK!
¿Eres nuevo? Comienza con $FIRST_HTML_LINK para construir una base sólida.
Anterior: $PREV_HTML_LINK
"""
# Plantilla de respaldo.
default = "Este artículo es parte de la serie $SERIES_HTML_LINK."
# Conclusión.
[extra.series_outro_templates]
next_only = """
¡Gracias por leer! 🙌
Continúa tu viaje con $NEXT_HTML_LINK, donde $NEXT_DESCRIPTION
O revisa el esquema completo de la serie [$SERIES_TITLE]($SERIES_PERMALINK).
"""
middle = """
---
📝 Navegación de la serie
- Anterior: $PREV_HTML_LINK
- Siguiente: $NEXT_HTML_LINK
- [Resumen de la serie]($SERIES_PERMALINK)
"""
prev_only = """
🎉 ¡Felicidades! Has completado $SERIES_HTML_LINK.
¿Quieres repasar? Aquí comenzamos: $FIRST_HTML_LINK
O revisa lo que acabamos de ver en $PREV_HTML_LINK.
"""
# Respaldo.
default = """
---
Este artículo es la parte $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER en $SERIES_HTML_LINK.
"""
```
### Variables personalizadas
Las plantillas de series admiten variables personalizadas para incluir información adicional en toda tu serie. El proceso tiene dos pasos:
1. Primero, define tus **marcadores** en la configuración de tu serie (`_index.md`):
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra]
series = true
series_template_placeholders = ["$POSITION", "$TOPIC", "$DIFFICULTY"]
```
2. Luego, en cada artículo de la serie, proporciona los valores para estos marcadores en `series_template_variables`:
{{ add_src_to_code_block(src="series/article.md") }}
```toml
[extra.series_template_variables]
position = "primero"
topic = "Variables y tipos"
difficulty = "Principiante"
```
### Uso de variables personalizadas
Puedes usar tus variables personalizadas en cualquier plantilla, junto con las variables integradas:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra.series_intro_templates]
default = """
Este es el artículo $POSITION en $SERIES_HTML_LINK.
Tema de hoy: $TOPIC
Nivel de dificultad: $DIFFICULTY
"""
```
{{ admonition(type="warning", text="Aunque los marcadores se definen en mayúsculas (`$POSITION`), los nombres de variables en `series_template_variables` deben estar en minúsculas (`position`).") }}
### Ejemplo con variables personalizadas
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
# En la configuración de la serie.
[extra]
series = true
series_template_placeholders = ["$LEARNING_TIME", "$KEY_CONCEPTS"]
series_intro_templates.default = """
📚 Parte $SERIES_PAGE_INDEX de $SERIES_PAGES_NUMBER
⏱️ Tiempo estimado: $LEARNING_TIME
🔑 Conceptos clave: $KEY_CONCEPTS
"""
```
{{ add_src_to_code_block(src="series/02-learning-rust/index.md") }}
```toml
# En un artículo de la serie.
[extra.series_template_variables]
learning_time = "30 minutos"
key_concepts = "Funciones, manejo de errores, coincidencia de patrones"
```
Esto generará:
```txt
📚 Parte 2 de 5
⏱️ Tiempo estimado: 30 minutos
🔑 Conceptos clave: Funciones, manejo de errores, coincidencia de patrones
```
{{ admonition(type="warning", title="Variables faltantes", text="Si usas un marcador en tus plantillas pero no proporcionas su valor en `series_template_variables`, la compilación fallará con un error que lista las variables faltantes.") }}

@ -0,0 +1,424 @@
+++
title = "A Complete Guide to Series"
date = 2024-11-08
description = "Learn how to organize your posts into sequential series, perfect for tutorials, courses, and multi-part stories."
[taxonomies]
tags = ["showcase", "tutorial", "FAQ", "series"]
[extra]
quick_navigation_buttons = true
toc = true
mermaid = true
social_media_card = "social_cards/es_blog_series.jpg"
+++
A series organizes related posts in a sequential order, similar to chapters in a book. Unlike tags, which simply group related content, series suggest a specific reading order from start to finish.
Posts within a series do not need to be published consecutively; the series feature brings together thematically linked posts in a coherent sequence.
The diagram below illustrates how series posts (3, 5, and 8) exist within the main blog flow while maintaining their own ordered sequence within Series 1.
{% mermaid(full_width=true) %}
flowchart
subgraph main[BLOG]
P1[Post 1]
P2[P2]
P3[P3]
P4[P4]
P5[P5]
P6[P6]
P7[P7]
P8[P8]
P9[P9]
end
subgraph series1[SERIES 1]
PS1["Series Post 1 (=P3)"]
PS2["Series Post 2 (=P5)"]
PS3["Series Post 3 (=P8)"]
end
P3 o-.-o PS1
P5 o-.-o PS2
P8 o-.-o PS3
{% end %}
## Quick Start
1. Create a directory for your series.
2. Create `_index.md` in the series directory.
3. Set up the `_index.md` front matter:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
title = "Learning Rust"
template = "series.html"
sort_by = "slug"
transparent = true
[extra]
series = true
```
4. Create your series articles in this directory.
Want more? Keep reading!
## How Do Series Work?
A series is just a section which is handled in a special way by tabi. For more details on sections, see the [Zola documentation](https://www.getzola.org/documentation/content/section/).
Taking the example from the diagram above, the directory structure would be as follow:
```txt
content/
_index.md
blog/
_index.md
post1/
index.md
post2/
index.md
post4/
index.md
post6/
index.md
post7/
index.md
post9/
index.md
series1/
_index.md
post3/
index.md
post5/
index.md
post8/
index.md
```
To create a series, you need to:
1. Use the `series.html` template
2. Set `series = true` in the section's `[extra]` configuration
3. Enable `transparent = true` to integrate series posts with the parent blog section
The series main page displays an overview followed by a list of all posts in the series:
{{ dual_theme_image(light_src="blog/series/img/series_light.webp", dark_src="blog/series/img/series_dark.webp" alt="a series", full_width=true) }}
## Jump to Posts
If the content of a series (the Markdown after the front matter in `_index.md`) is over 2000 characters, a "Jump to posts" link appears next to the series title.
{{ dual_theme_image(light_src="blog/series/img/jump_to_series_posts_light.webp", dark_src="blog/series/img/jump_to_series_posts_dark.webp" alt="jump to series posts link", full_width=true) }}
To force the feature on or off, set `show_jump_to_posts` in the `[extra]` section of your series section or in `config.toml`. This setting follows [the hierarchy](@/blog/mastering-tabi-settings/index.md#settings-hierarchy).
## Series Pages and Order
All pages in the series section will be a series page. The series pages will be ordered as per the series section `sort_by`.
While series maintain their own internal order, they remain independent from the main section's (e.g. `blog/`) chronological flow thanks to the `transparent` setting.
### Sorting Options
Choose from these sorting methods, each with its own advantages:
{% wide_container() %}
`sort_by` | pros | cons
---------|-------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
`slug` | The series pages order is made explicit in the path (e.g. `example.com/blog/series1/01-series-post-one`). | Each series page must be prefixed accordingly.
`weight` | The series pages order is easy to set up transparently.<br>First series post has weight `1`, second series post has weight `2` and so on. | Each series page must have its weight set accordingly.
`date` | The series pages order can be configured once in the series section configuration. No need to do anything on each series page. | The series pages order has to be reversed because the first page is usually the oldest. This can only be achieved by paginating the series section (`paginate_by = 9999`) and reversing its order (`paginate_reversed = true`).
{% end %}
{{ admonition(type="danger", title="Zola version to sort by date", text="In order to properly reverse dates, Zola v0.19.3+ (unreleased) is required so that pagination information is available through the `get_section` function. Anything relying on the series pages order won't be correct in a series page otherwise (e.g. previous/next series page, ordered and unordered list…) See [Zola PR #2653](https://github.com/getzola/zola/pull/2653).") }}
### Page Indexing
Pages in a series are indexed starting from 1, following their `sort_by` order. To reverse the indexing (making the first page have the highest index instead), add this setting to `_index.md` or `config.toml`:
```toml
[extra]
post_listing_index_reversed = true # Defaults to false if unset.
```
{{ dual_theme_image(light_src="blog/series/img/series_reversed_light.webp", dark_src="blog/series/img/series_reversed_dark.webp" alt="a series with indexes reversed", full_width=true) }}
This setting follows [the hierarchy](@/blog/mastering-tabi-settings/index.md#settings-hierarchy).
## Intro and Outro Templates
Series articles can have automatic introduction and conclusion sections. These are configured in your series' `_index.md`. A basic example:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra.series_intro_templates]
default = "This article is part of the $SERIES_HTML_LINK series."
[extra.series_outro_templates]
default = "Thanks for reading part $SERIES_PAGE_INDEX of $SERIES_HTML_LINK!"
```
The intro and outro sections each have their own CSS classes (`series-page-intro` and `series-page-outro`), allowing you to customize their appearance through [custom CSS](@/blog/mastering-tabi-settings/index.md#custom-css).
### Template Types
The series system uses different templates based on an article's position in the series:
- `next_only` - Used for the first article (has next article but no previous)
- `middle` - Used for articles with both previous and next articles
- `prev_only` - Used for the last article (has previous article but no next)
- `default` - Fallback template used when a specific position template isn't defined
The system automatically determines which template to use based on the article's position. The templates are defined in the series configuration (`_index.md`), as `extra.series_intro_templates` and `extra.series_outro_templates`.:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra.series_intro_templates]
next_only = "Welcome to part 1! Next up: $NEXT_HTML_LINK"
middle = "Previous: $PREV_HTML_LINK | Next: $NEXT_HTML_LINK"
prev_only = "The final chapter! Previously: $PREV_HTML_LINK"
default = "Part $SERIES_PAGE_INDEX of $SERIES_PAGES_NUMBER"
```
All templates are optional. Template selection follows a priority system:
1. If a position-specific template exists (`next_only`, `middle`, or `prev_only`), it will be used
2. Otherwise, the `default` template is used
3. If no templates are defined at all, no series information will be displayed
See the [template example](#template-example) for a more elaborate example.
### Placement in Content
By default:
- Series introductions appear at the start of your article
- Series outro appears at the end (before footnotes, if any)
You can control exactly where these appear using `<!-- series_intro -->` and `<!-- series_outro -->` in your Markdown:
```markdown
This paragraph appears before the series introduction.
<!-- series_intro -->
Main content of the article.
<!-- series_outro -->
## Learning Resources
Extra content…
[^1]: Footnotes will always appear at the end.
```
## Variables
Series templates use a flexible variable system that lets you:
1. Reference series information (title, links)
2. Add navigation between articles
3. Show progress indicators
4. Include custom information using your own variables
Variables are placeholders starting with `$` that get replaced with actual content when your site builds. For example, `$SERIES_HTML_LINK` becomes a clickable link to your series index page.
There are three types of variables:
- [**Basic Series Variables**](#basic-series-variables): General information about the series
- [**Navigation Variables**](#navigation-variables): Links to previous/next articles
- [**Custom Variables**](#custom-variables): Your own placeholders for additional information
### Basic Series Variables
{% wide_container() %}
| Variable | Availability | Returns | Description | Example Usage | Example Output |
|----------|-------------|---------|-------------|---------------|----------------|
| `$SERIES_TITLE` | Always | Text | Plain text title of the series | `Part of $SERIES_TITLE` | Part of Learn Rust |
| `$SERIES_PERMALINK` | Always | Text | URL to series index | `[See all posts]($SERIES_PERMALINK)` | [See all posts](/series/learn-rust) |
| `$SERIES_HTML_LINK` | Always | HTML | Ready-to-use link to series | `Welcome to $SERIES_HTML_LINK!` | Welcome to <a href="/series/learn-rust">Learn Rust</a>! |
| `$SERIES_PAGES_NUMBER` | Always | Number | Total articles in series | `A $SERIES_PAGES_NUMBER part series` | A 5 part series |
| `$SERIES_PAGE_INDEX` | Always | Number | Current article's position | `Part $SERIES_PAGE_INDEX of $SERIES_PAGES_NUMBER` | Part 3 of 5 |
| `$SERIES_PAGES_OLIST` | Always | HTML | Ordered list of all articles | `Articles in series: $SERIES_PAGES_OLIST` | Articles in series: <ol><li>Current article</li><li><a href="...">Other articles</a></li></ol> |
| `$SERIES_PAGES_ULIST` | Always | HTML | Unordered list of all articles | `Articles in series: $SERIES_PAGES_ULIST` | Articles in series: <ul><li>Current article</li><li><a href="...">Other articles</a></li></ul> |
{% end %}
### Navigation Variables
{% wide_container() %}
| Variable | Availability | Returns | Description | Example Usage | Example Output |
|----------|-------------|---------|-------------|---------------|----------------|
| `$PREV_TITLE` | Previous exists | Text | Previous article's title | `Previously: $PREV_TITLE` | Previously: Setting Up Your Environment |
| `$PREV_PERMALINK` | Previous exists | Text | URL to previous article | `[← Back]($PREV_PERMALINK)` | [← Back](/series/learn-rust/setup) |
| `$PREV_HTML_LINK` | Previous exists | HTML | Ready-to-use link to previous | `Read $PREV_HTML_LINK first` | Read <a href="/series/learn-rust/setup">Setting Up Your Environment</a> first |
| `$PREV_DESCRIPTION` | Previous exists | Text | Description of previous article | `Recap: $PREV_DESCRIPTION` | Recap: Setting up Rust |
| `$NEXT_TITLE` | Next exists | Text | Next article's title | `Next up: $NEXT_TITLE` | Next up: Advanced Patterns |
| `$NEXT_PERMALINK` | Next exists | Text | URL to next article | `[Continue →]($NEXT_PERMALINK)` | [Continue →](/series/learn-rust/patterns) |
| `$NEXT_HTML_LINK` | Next exists | HTML | Ready-to-use link to next | `Continue with $NEXT_HTML_LINK` | Continue with <a href="/series/learn-rust/patterns">Advanced Patterns</a> |
| `$NEXT_DESCRIPTION` | Next exists | Text | Description of next article | `Coming up: $NEXT_DESCRIPTION` | Coming up: Learn about Rust's advanced pattern matching features |
{% end %}
### First Article Reference
{% wide_container() %}
| Variable | Availability | Returns | Description | Example Usage | Example Output |
|----------|-------------|---------|-------------|---------------|----------------|
| `$FIRST_TITLE` | Always | Text | First article's title | `Start with $FIRST_TITLE` | Start with Introduction to Rust |
| `$FIRST_HTML_LINK` | Always | HTML | Ready-to-use link to first article | `Begin at $FIRST_HTML_LINK` | Begin at <a href="/series/learn-rust/intro">Introduction to Rust</a> |
{% end %}
### Template Example
{{ admonition(type="tip", title="HTML vs text variables", text="Use HTML variables (ending in `_HTML_LINK`) when you want ready-made links. Use text variables (ending in `_TITLE` or `_PERMALINK`) when you want more control over the formatting.") }}
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
# Introduction.
[extra.series_intro_templates]
next_only = """
Welcome to $SERIES_HTML_LINK! This $SERIES_PAGES_NUMBER-part series will teach you Rust from scratch.
Up next: $NEXT_HTML_LINK - $NEXT_DESCRIPTION
"""
middle = """
📚 Part $SERIES_PAGE_INDEX of $SERIES_PAGES_NUMBER in $SERIES_HTML_LINK
Previously: $PREV_HTML_LINK
Next up: $NEXT_HTML_LINK
"""
prev_only = """
Welcome to the final part of $SERIES_HTML_LINK!
New here? Start with $FIRST_HTML_LINK to build a strong foundation.
Previously: $PREV_HTML_LINK
"""
# Fallback template.
default = "This article is part of the $SERIES_HTML_LINK series."
# Outro.
[extra.series_outro_templates]
next_only = """
Thanks for reading! 🙌
Continue your journey with $NEXT_HTML_LINK, where $NEXT_DESCRIPTION
Or check out the complete [$SERIES_TITLE]($SERIES_PERMALINK) series outline.
"""
middle = """
---
📝 Series Navigation
- Previous: $PREV_HTML_LINK
- Next: $NEXT_HTML_LINK
- [Series Overview]($SERIES_PERMALINK)
"""
prev_only = """
🎉 Congratulations! You've completed $SERIES_HTML_LINK.
Want to review? Here's where we started: $FIRST_HTML_LINK
Or check what we just covered in $PREV_HTML_LINK.
"""
# Fallback.
default = """
---
This article is part $SERIES_PAGE_INDEX of $SERIES_PAGES_NUMBER in $SERIES_HTML_LINK.
"""
```
### Custom Variables
Series templates support custom variables for additional information you want to include across your series. The process takes two steps:
1. First, define your **placeholders** in your series configuration (`_index.md`):
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra]
series = true
series_template_placeholders = ["$POSITION", "$TOPIC", "$DIFFICULTY"]
```
2. Then, in each series article, provide the values for these placeholders in `series_template_variables`:
{{ add_src_to_code_block(src="series/article.md") }}
```toml
[extra.series_template_variables]
position = "first"
topic = "Variables and Types"
difficulty = "Beginner"
```
### Using Custom Variables
You can use your custom variables in any template, alongside the built-in variables:
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
[extra.series_intro_templates]
default = """
This is the $POSITION article in $SERIES_HTML_LINK.
Today's topic: $TOPIC
Difficulty level: $DIFFICULTY
"""
```
{{ admonition(type="warning", text="While placeholders are defined with uppercase (`$POSITION`), the variable names in `series_template_variables` must be lowercase (`position`).") }}
### Example with Custom Variables
{{ add_src_to_code_block(src="series/_index.md") }}
```toml
# In the series configuration.
[extra]
series = true
series_template_placeholders = ["$LEARNING_TIME", "$KEY_CONCEPTS"]
series_intro_templates.default = """
📚 Part $SERIES_PAGE_INDEX of $SERIES_PAGES_NUMBER
⏱️ Estimated time: $LEARNING_TIME
🔑 Key concepts: $KEY_CONCEPTS
"""
```
{{ add_src_to_code_block(src="series/02-learning-rust/index.md") }}
```toml
# In an article of the series.
[extra.series_template_variables]
learning_time = "30 minutes"
key_concepts = "Functions, Error Handling, Pattern Matching"
```
This will output:
```txt
📚 Part 2 of 5
⏱️ Estimated time: 30 minutes
🔑 Key concepts: Functions, Error Handling, Pattern Matching
```
{{ admonition(type="warning", title="Missing Variables", text="If you use a placeholder in your templates but don't provide its value in `series_template_variables`, the build will fail with an error listing the missing variables.") }}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

@ -19,8 +19,9 @@ social_media_card = "social_cards/ca_projects_tabi.jpg"
- [Estableix qualsevol idioma com a predeterminat](https://welpo.github.io/tabi/ca/blog/faq-languages/#com-estableixo-la-llengua-predeterminada-del-meu-lloc). Configura el teu lloc en xinès, espanyol, francès, hindi… o qualsevol [altre idioma compatible](https://welpo.github.io/tabi/ca/blog/faq-languages/#quines-llengues-admet-tabi). La interfície del tema es traduirà en conseqüència. - [Estableix qualsevol idioma com a predeterminat](https://welpo.github.io/tabi/ca/blog/faq-languages/#com-estableixo-la-llengua-predeterminada-del-meu-lloc). Configura el teu lloc en xinès, espanyol, francès, hindi… o qualsevol [altre idioma compatible](https://welpo.github.io/tabi/ca/blog/faq-languages/#quines-llengues-admet-tabi). La interfície del tema es traduirà en conseqüència.
- [Integració amb repositoris remots](https://welpo.github.io/tabi/ca/blog/mastering-tabi-settings#integracio-amb-repositoris-git) a GitHub, GitLab, Gitea i Codeberg per a l'historial de commits i mostrar el codi font del lloc. - [Integració amb repositoris remots](https://welpo.github.io/tabi/ca/blog/mastering-tabi-settings#integracio-amb-repositoris-git) a GitHub, GitLab, Gitea i Codeberg per a l'historial de commits i mostrar el codi font del lloc.
- Temes clars i foscos. S'adapta a la configuració del sistema operatiu, amb un interruptor a la barra de navegació. - Tema clar i fosc. S'adapta a la configuració del sistema operatiu, amb un interruptor a la barra de navegació.
- [Suport multilingüe complet](https://welpo.github.io/tabi/ca/blog/faq-languages/#com-gestiona-tabi-el-suport-multilingue). Afegeix tants idiomes com vulguis i deixa que els teus usuaris triin amb el selector d'idioma. - [Suport multilingüe complet](https://welpo.github.io/tabi/ca/blog/faq-languages/#com-gestiona-tabi-el-suport-multilingue). Afegeix tants idiomes com vulguis i deixa que els teus usuaris triin amb el selector d'idioma.
- [Suport per a sèries](https://welpo.github.io/tabi/ca/blog/series/) per crear contingut seqüencial com tutorials, cursos i històries multipart.
- Puntuació perfecta en Lighthouse (Rendiment, Accessibilitat, Millors Pràctiques i SEO). - Puntuació perfecta en Lighthouse (Rendiment, Accessibilitat, Millors Pràctiques i SEO).
- Suport per a [diagrames de Mermaid](https://welpo.github.io/tabi/ca/blog/shortcodes/#diagrames-de-mermaid) per a crear diagrames i gràfics amb text. - Suport per a [diagrames de Mermaid](https://welpo.github.io/tabi/ca/blog/shortcodes/#diagrames-de-mermaid) per a crear diagrames i gràfics amb text.
- Ressaltat de sintaxi de codi amb colors basats en [Catppuccin](https://github.com/catppuccin/catppuccin) Frappé. - Ressaltat de sintaxi de codi amb colors basats en [Catppuccin](https://github.com/catppuccin/catppuccin) Frappé.

@ -20,7 +20,8 @@ social_media_card = "social_cards/es_projects_tabi.jpg"
- [Establece cualquier idioma como predeterminado](https://welpo.github.io/tabi/es/blog/faq-languages/#como-establezco-el-idioma-predeterminado-de-mi-sitio). Configura tu sitio en chino, español, francés, hindi… o cualquier [otro idioma compatible](https://welpo.github.io/tabi/es/blog/faq-languages/#que-idiomas-admite-tabi). La interfaz del tema se traducirá en consecuencia. - [Establece cualquier idioma como predeterminado](https://welpo.github.io/tabi/es/blog/faq-languages/#como-establezco-el-idioma-predeterminado-de-mi-sitio). Configura tu sitio en chino, español, francés, hindi… o cualquier [otro idioma compatible](https://welpo.github.io/tabi/es/blog/faq-languages/#que-idiomas-admite-tabi). La interfaz del tema se traducirá en consecuencia.
- [Integración con repositorios remotos](https://welpo.github.io/tabi/es/blog/mastering-tabi-settings/#integracion-con-repositorios-git) en GitHub, GitLab, Gitea y Codeberg para el historial de commits y mostrar el código fuente del sitio. - [Integración con repositorios remotos](https://welpo.github.io/tabi/es/blog/mastering-tabi-settings/#integracion-con-repositorios-git) en GitHub, GitLab, Gitea y Codeberg para el historial de commits y mostrar el código fuente del sitio.
- [Soporte multilingüe completo](https://welpo.github.io/tabi/es/blog/faq-languages/#como-gestiona-tabi-el-soporte-multilingue). Añade tantos idiomas como desees y deja que tus usuarios elijan con un selector de idioma. - [Soporte multilingüe completo](https://welpo.github.io/tabi/es/blog/faq-languages/#como-gestiona-tabi-el-soporte-multilingue). Añade tantos idiomas como desees y deja que tus usuarios elijan con un selector de idioma.
- Temas claros y oscuros. Se adapta a la configuración del sistema operativo, con un interruptor en la barra de navegación. - Tema claro y oscuro. Se adapta a la configuración del sistema operativo, con un interruptor en la barra de navegación.
- [Soporte para series](https://welpo.github.io/tabi/es/blog/series/) para crear contenido secuencial como tutoriales, cursos e historias en varias partes.
- Puntuación perfecta en Lighthouse (Rendimiento, Accesibilidad, Mejores Prácticas y SEO). - Puntuación perfecta en Lighthouse (Rendimiento, Accesibilidad, Mejores Prácticas y SEO).
- Soporte para [diagramas de Mermaid](https://welpo.github.io/tabi/es/blog/shortcodes/#diagramas-de-mermaid) para crear diagramas y gráficos con texto. - Soporte para [diagramas de Mermaid](https://welpo.github.io/tabi/es/blog/shortcodes/#diagramas-de-mermaid) para crear diagramas y gráficos con texto.
- Resaltado de sintaxis de código con colores basados en [Catppuccin](https://github.com/catppuccin/catppuccin) Frappé. - Resaltado de sintaxis de código con colores basados en [Catppuccin](https://github.com/catppuccin/catppuccin) Frappé.

@ -18,6 +18,7 @@ social_media_card = "social_cards/projects_tabi.jpg"
- [Set any language as default](https://welpo.github.io/tabi/blog/faq-languages/#how-do-i-set-a-default-language-for-my-site). Set your base site to Chinese, Spanish, French, Hindi… or any [other supported language](https://welpo.github.io/tabi/blog/faq-languages/#what-languages-does-tabi-support). The theme's interface will be translated accordingly. - [Set any language as default](https://welpo.github.io/tabi/blog/faq-languages/#how-do-i-set-a-default-language-for-my-site). Set your base site to Chinese, Spanish, French, Hindi… or any [other supported language](https://welpo.github.io/tabi/blog/faq-languages/#what-languages-does-tabi-support). The theme's interface will be translated accordingly.
- [Integration with remote repositories](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#git-repository-integration) on GitHub, GitLab, Gitea & Codeberg for commit history and showing the site source. - [Integration with remote repositories](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#git-repository-integration) on GitHub, GitLab, Gitea & Codeberg for commit history and showing the site source.
- [Series support](https://welpo.github.io/tabi/blog/series/) for creating sequential content like tutorials, courses, and multi-part stories.
- Dark and light themes. Defaults to the OS setting, with a switcher in the navigation bar. - Dark and light themes. Defaults to the OS setting, with a switcher in the navigation bar.
- Thorough documentation. See [Mastering tabi Settings: A Comprehensive Guide](https://welpo.github.io/tabi/blog/mastering-tabi-settings/). - Thorough documentation. See [Mastering tabi Settings: A Comprehensive Guide](https://welpo.github.io/tabi/blog/mastering-tabi-settings/).
- Perfect Lighthouse score (Performance, Accessibility, Best Practices and SEO). - Perfect Lighthouse score (Performance, Accessibility, Best Practices and SEO).

@ -30,6 +30,7 @@ few_results = "تم العثور على $NUMBER نتائج" # for 3 to 10 searc
many_results = "تم العثور على $NUMBER نتيجة" # 11 or more search results. many_results = "تم العثور على $NUMBER نتيجة" # 11 or more search results.
# Navigation. # Navigation.
jump_to_posts = "الإنتقال إلى التدوينات"
read_more = "إقرأ المزيد" read_more = "إقرأ المزيد"
one_posts = "تدوينة واحدة" #One blog post. one_posts = "تدوينة واحدة" #One blog post.
two_posts = "تدوينتين" #Two blog posts. two_posts = "تدوينتين" #Two blog posts.

@ -23,6 +23,7 @@ one_results = "$NUMBER resultat" # "1 result"
many_results = "$NUMBER resultats" # "3 results" many_results = "$NUMBER resultats" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "Saltar als articles"
read_more = "Llegir més" read_more = "Llegir més"
one_posts = "$NUMBER entrada" one_posts = "$NUMBER entrada"
many_posts = "$NUMBER entrades" many_posts = "$NUMBER entrades"

@ -27,6 +27,7 @@ one_results = "$NUMBER Ergebnis" # "1 result"
many_results = "$NUMBER Ergebnisse" # "3 results" many_results = "$NUMBER Ergebnisse" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "Zu den Beiträgen springen"
read_more = "Weiterlesen" read_more = "Weiterlesen"
one_posts = "$NUMBER Beitrag" one_posts = "$NUMBER Beitrag"
many_posts = "$NUMBER Beiträge" many_posts = "$NUMBER Beiträge"

@ -23,6 +23,7 @@ one_results = "$NUMBER result" # "1 result"
many_results = "$NUMBER results" # "3 results" many_results = "$NUMBER results" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "Jump to posts"
read_more = "Read more" read_more = "Read more"
one_posts = "$NUMBER post" one_posts = "$NUMBER post"
many_posts = "$NUMBER posts" # "3 posts" many_posts = "$NUMBER posts" # "3 posts"

@ -23,6 +23,7 @@ one_results = "$NUMBER resultado"
many_results = "$NUMBER resultados" many_results = "$NUMBER resultados"
# Navigation. # Navigation.
jump_to_posts = "Saltar a las entradas"
read_more = "Leer más" read_more = "Leer más"
one_posts = "$NUMBER entrada" one_posts = "$NUMBER entrada"
many_posts = "$NUMBER entradas" many_posts = "$NUMBER entradas"

@ -23,6 +23,7 @@ one_results = "$NUMBER tulemus" # "1 result"
many_results = "$NUMBER tulemust" # "3 results" many_results = "$NUMBER tulemust" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "Hüppa postitusteni"
read_more = "Loe edasi" read_more = "Loe edasi"
one_posts = "$NUMBER postitus" one_posts = "$NUMBER postitus"
many_posts = "$NUMBER postitust" # "3 posts" many_posts = "$NUMBER postitust" # "3 posts"

@ -23,6 +23,7 @@ one_results = "$NUMBER نتیجه" # "1 result"
many_results = "$NUMBER نتیجه" # "3 results" many_results = "$NUMBER نتیجه" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "پرش به نوشته‌ها"
read_more = "ادامه مطلب" read_more = "ادامه مطلب"
one_posts = "$NUMBER مطلب" one_posts = "$NUMBER مطلب"
many_posts = "$NUMBER مطلب" # "3 posts" many_posts = "$NUMBER مطلب" # "3 posts"

@ -23,6 +23,7 @@ one_results = "$NUMBER résultat" # "1 result"
many_results = "$NUMBER résultats" # "3 results" many_results = "$NUMBER résultats" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "Aller aux articles"
read_more = "Lire plus" read_more = "Lire plus"
one_posts = "$NUMBER article" one_posts = "$NUMBER article"
many_posts = "$NUMBER articles" many_posts = "$NUMBER articles"

@ -25,6 +25,7 @@ one_results = "$NUMBER परिणाम" # "1 result"
many_results = "$NUMBER परिणाम" # "3 results" many_results = "$NUMBER परिणाम" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "पोस्ट पर जाएं"
read_more = "और पढ़ें" read_more = "और पढ़ें"
one_posts = "$NUMBER पोस्ट" one_posts = "$NUMBER पोस्ट"
many_posts = "$NUMBER पोस्ट्स" many_posts = "$NUMBER पोस्ट्स"

@ -23,6 +23,7 @@ one_results = "$NUMBER risultato"
many_results = "$NUMBER risultati" many_results = "$NUMBER risultati"
# Navigation. # Navigation.
jump_to_posts = "Vai ai post"
read_more = "Leggi di più" read_more = "Leggi di più"
one_posts = "$NUMBER post" one_posts = "$NUMBER post"
many_posts = "$NUMBER post" many_posts = "$NUMBER post"

@ -27,6 +27,7 @@ one_results = "$NUMBER 結果" # "1 result"
many_results = "$NUMBER 結果" # "3 results" many_results = "$NUMBER 結果" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "投稿へジャンプ"
read_more = "続きを読む" read_more = "続きを読む"
one_posts = "$NUMBER 投稿" one_posts = "$NUMBER 投稿"
many_posts = "$NUMBER 投稿" many_posts = "$NUMBER 投稿"

@ -27,6 +27,7 @@ one_results = "$NUMBER 결과" # "1 result"
many_results = "$NUMBER 결과" # "3 results" many_results = "$NUMBER 결과" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "게시물로 이동"
read_more = "더 읽기" read_more = "더 읽기"
one_posts = "$NUMBER 게시물" one_posts = "$NUMBER 게시물"
many_posts = "$NUMBER 게시물" many_posts = "$NUMBER 게시물"

@ -23,6 +23,7 @@ one_results = "$NUMBER resultaat" # "1 result"
many_results = "$NUMBER resultaten" # "3 results" many_results = "$NUMBER resultaten" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "Naar berichten springen"
read_more = "Lees meer" read_more = "Lees meer"
one_posts = "$NUMBER bericht" # "1 post" one_posts = "$NUMBER bericht" # "1 post"
many_posts = "$NUMBER berichten" # "3 posts" many_posts = "$NUMBER berichten" # "3 posts"

@ -23,6 +23,7 @@ one_results = "$NUMBER ପରିଣାମ" # "1 result"
many_results = "$NUMBER ପରିଣାମଗୁଡ଼ିକ" # "3 results" many_results = "$NUMBER ପରିଣାମଗୁଡ଼ିକ" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "ପୋଷ୍ଟକୁ ଯାଆନ୍ତୁ"
read_more = "ଆହୁରି ପଢ଼ନ୍ତୁ" read_more = "ଆହୁରି ପଢ଼ନ୍ତୁ"
one_posts = "$NUMBER ପୋଷ୍ଟ" one_posts = "$NUMBER ପୋଷ୍ଟ"
many_posts = "$NUMBER ପୋଷ୍ଟଗୁଡ଼ିକ" # "3 posts" many_posts = "$NUMBER ପୋଷ୍ଟଗୁଡ଼ିକ" # "3 posts"

@ -23,6 +23,7 @@ one_results = "$NUMBER resultado" # "1 result"
many_results = "$NUMBER resultados" # "3 results" many_results = "$NUMBER resultados" # "3 results"
# Navigation. # Navigation.
jump_to_posts = "Ir para as publicações"
read_more = "Ler mais" read_more = "Ler mais"
one_posts = "$NUMBER publicação" one_posts = "$NUMBER publicação"
many_posts = "$NUMBER publicações" many_posts = "$NUMBER publicações"

@ -28,6 +28,7 @@ few_results = "$NUMBER результата" # 2, 3, 4 but not 12-14
many_results = "$NUMBER результатов" # 5-9, 0, 11-14, and others many_results = "$NUMBER результатов" # 5-9, 0, 11-14, and others
# Navigation. # Navigation.
jump_to_posts = "Перейти к записям"
read_more = "Читать далее" read_more = "Читать далее"
post = "пост" post = "пост"
one_posts = "$NUMBER пост" one_posts = "$NUMBER пост"

@ -33,6 +33,7 @@ few_results = "$NUMBER результати"
many_results = "$NUMBER результатів" many_results = "$NUMBER результатів"
# Navigation. # Navigation.
jump_to_posts = "Перейти до дописів"
read_more = "Читати далі" read_more = "Читати далі"
one_posts = "$NUMBER пост" one_posts = "$NUMBER пост"
few_posts = "$NUMBER пости" # 2, 3, 4 but not 12-14 few_posts = "$NUMBER пости" # 2, 3, 4 but not 12-14

@ -23,6 +23,7 @@ one_results = "$NUMBER 个结果"
many_results = "$NUMBER 个结果" many_results = "$NUMBER 个结果"
# Navigation. # Navigation.
jump_to_posts = "跳转到文章"
read_more = "阅读全文" read_more = "阅读全文"
one_posts = "$NUMBER 篇文章" one_posts = "$NUMBER 篇文章"
many_posts = "$NUMBER 篇文章" many_posts = "$NUMBER 篇文章"

@ -23,6 +23,7 @@ one_results = "$NUMBER 個結果"
many_results = "$NUMBER 個結果" many_results = "$NUMBER 個結果"
# Navigation. # Navigation.
jump_to_posts = "跳轉到文章"
read_more = "閱讀全文" read_more = "閱讀全文"
one_posts = "$NUMBER 篇文章" one_posts = "$NUMBER 篇文章"
many_posts = "$NUMBER 篇文章" many_posts = "$NUMBER 篇文章"

@ -265,3 +265,24 @@ details summary {
[data-force-text-direction="rtl"] * { [data-force-text-direction="rtl"] * {
direction: inherit; direction: inherit;
} }
.title-with-jump {
display: flex;
justify-content: space-between;
align-items: center;
}
.title-with-jump h1 {
flex: 1;
}
.jump-link {
flex-shrink: 0;
font-size: 0.9rem;
}
@media (max-width: 500px) {
.title-with-jump {
flex-direction: column;
}
}

@ -1,24 +1,28 @@
.bloglist-container { .bloglist-container {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr 8fr;
} }
.bloglist-row { .bloglist-meta {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
background-color: var(--navbar-color); background-color: var(--navbar-color);
padding-block: 2.5rem; padding-block: 2.5rem;
.bloglist-meta { ul {
margin-inline-end: 0.7rem; margin-inline-end: 0.7rem;
padding: 0; padding: 0;
width: 13.5rem;
color: var(--meta-color); color: var(--meta-color);
font-weight: 300; font-weight: 300;
font-size: 0.9rem; font-size: 0.9rem;
li { li {
list-style-type: none; list-style-type: none;
white-space: nowrap;
}
li.date {
width: 13.5rem;
} }
li.draft-label { li.draft-label {
@ -26,23 +30,17 @@
line-height: 1.2rem; line-height: 1.2rem;
} }
} }
}
.bloglist-tags { .bloglist-content {
margin-top: 0.1rem; display: flex;
align-items: flex-start;
.tag { background-color: var(--navbar-color);
display: inline-block; padding: 2.5rem 0;
margin-inline-end: 0.7rem;
font-weight: 400;
font-size: 0.75rem;
text-transform: uppercase;
}
}
.bloglist-content { div {
flex: 1; flex: 1;
.bloglist-title { .bloglist-title {
margin: 0; margin: 0;
font-weight: bold; font-weight: bold;
@ -58,6 +56,18 @@
} }
} }
.bloglist-tags {
margin-top: 0.1rem;
.tag {
display: inline-block;
margin-inline-end: 0.7rem;
font-weight: 400;
font-size: 0.75rem;
text-transform: uppercase;
}
}
.description p { .description p {
margin: 0.5rem 0 1rem; margin: 0.5rem 0 1rem;
color: var(--text-color); color: var(--text-color);
@ -86,12 +96,15 @@
} }
@media only screen and (max-width: 1100px) { @media only screen and (max-width: 1100px) {
.bloglist-row { .bloglist-container {
flex-direction: column; grid-template-columns: 1fr;
align-items: flex-start; }
padding-block: 2rem;
.bloglist-meta { .bloglist-meta {
padding-block: 2rem;
border-bottom: 0;
ul {
margin-block-end: 0; margin-block-end: 0;
width: 100%; width: 100%;
@ -100,8 +113,15 @@
margin-inline-end: 0.3rem; margin-inline-end: 0.3rem;
} }
} }
}
.bloglist-content { .bloglist-content {
flex-direction: column;
align-items: flex-start;
padding: 0;
padding-bottom: 2rem;
div {
width: 100%; width: 100%;
} }
} }

@ -60,8 +60,8 @@
<xsl:variable name="post_listing_date" select="/atom:feed/tabi:metadata/tabi:post_listing_date"/> <xsl:variable name="post_listing_date" select="/atom:feed/tabi:metadata/tabi:post_listing_date"/>
<div class="bloglist-container"> <div class="bloglist-container">
<xsl:for-each select="/atom:feed/atom:entry"> <xsl:for-each select="/atom:feed/atom:entry">
<section class="bloglist-row bottom-divider"> <section class="bloglist-meta bottom-divider">
<ul class="bloglist-meta"> <ul>
<xsl:variable name="show_date" select="$post_listing_date = 'date' or $post_listing_date = 'both'"/> <xsl:variable name="show_date" select="$post_listing_date = 'date' or $post_listing_date = 'both'"/>
<xsl:variable name="show_updated" select="$post_listing_date = 'updated' or $post_listing_date = 'both'"/> <xsl:variable name="show_updated" select="$post_listing_date = 'updated' or $post_listing_date = 'both'"/>
@ -87,7 +87,9 @@
</li> </li>
</xsl:if> </xsl:if>
</ul> </ul>
<div class="bloglist-content"> </section>
<section class="bloglist-content bottom-divider">
<div>
<div class="bloglist-title"> <div class="bloglist-title">
<a> <a>
<xsl:attribute name="href"> <xsl:attribute name="href">

@ -5,6 +5,7 @@
{% import "macros/settings.html" as macros_settings %} {% import "macros/settings.html" as macros_settings %}
{% import "macros/table_of_contents.html" as macros_toc %} {% import "macros/table_of_contents.html" as macros_toc %}
{% import "macros/translate.html" as macros_translate %} {% import "macros/translate.html" as macros_translate %}
{% import "macros/series_page.html" as macros_series_page %}
{# Load the internationalisation data for the current language from {# Load the internationalisation data for the current language from
the .toml files in the user's '/i18n' folder, falling back to the theme's. the .toml files in the user's '/i18n' folder, falling back to the theme's.

@ -1,19 +1,41 @@
{% macro list_posts(posts, max, language_strings="", section_path="blog") %} {# `metadata` can be "dates", "indexes" or both (e.g. "dates indexes" or "indexes dates"). #}
{# If both, the order doesn't matter and indexes will always be displayed before dates. #}
{# It would also work with arrays (e.g. ["dates"] or ["indexes"] or even ["indexes","dates"]). #}
{# Nevertheless, arrays cannot be used as a default value for a macro parameter in Tera (see https://github.com/Keats/tera/issues/710). #}
{# `paginator` is only used to compute indexes metadata and can be let empty otherwise. #}
{% macro list_posts(posts, max, metadata="dates", language_strings="", section_path="blog", paginator="") %}
{%- set separator = config.extra.separator | default(value="•") -%} {%- set separator = config.extra.separator | default(value="•") -%}
<div class="bloglist-container"> <div class="bloglist-container">
{% for post in posts %} {% for post in posts %}
{% if loop.index <= max %} {% if loop.index <= max %}
{% if loop.index == max %} {% if loop.index == max or loop.last %}
<section class="bloglist-row"> {% set bottom_divider = false %}
{% elif loop.last %}
<section class="bloglist-row">
{% else %} {% else %}
<section class="bloglist-row bottom-divider"> {% set bottom_divider = true %}
{% endif %} {% endif %}
<ul class="bloglist-meta"> <section class="bloglist-meta {% if bottom_divider -%}bottom-divider{%- endif -%}">
<ul>
{%- if "indexes" in metadata -%}
{%- set post_index = loop.index -%}
{%- set nb_posts = posts | length -%}
{# in case we have a pager, the index has been computed for the current page. #}
{%- if paginator -%}
{%- set nb_posts = paginator.total_pages -%}
{%- set number_of_other_pages = paginator.current_index - 1 -%}
{%- set posts_per_page = paginator.paginate_by -%}
{%- set posts_in_other_pages = number_of_other_pages * posts_per_page -%}
{%- set post_index = posts_in_other_pages + post_index -%}
{%- endif -%}
{%- if macros_settings::evaluate_setting_priority(setting="post_listing_index_reversed", page=section, default_global_value=false) == "true" -%}
{# index starts at 1 instead of 0 #}
{%- set post_index = nb_posts + 1 - post_index -%}
{%- endif -%}
<li class="index-label">{{ post_index }}</li>
{%- endif -%}
{%- if "dates" in metadata -%}
{%- set allowed_post_listing_dates = ["date", "updated", "both"] -%} {%- set allowed_post_listing_dates = ["date", "updated", "both"] -%}
{%- set post_listing_date = config.extra.post_listing_date | default(value="date") -%} {%- set post_listing_date = config.extra.post_listing_date | default(value="date") -%}
{%- if post_listing_date not in allowed_post_listing_dates -%} {%- if post_listing_date not in allowed_post_listing_dates -%}
@ -37,12 +59,15 @@
<li class="date">{{ updated_str }}</li> <li class="date">{{ updated_str }}</li>
{%- endif -%} {%- endif -%}
{%- endif -%} {%- endif -%}
{%- 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 %}
</ul> </ul>
<div class="bloglist-content"> </section>
<section class="bloglist-content {% if bottom_divider -%}bottom-divider{%- endif -%}">
<div>
<h2 class="bloglist-title"> <h2 class="bloglist-title">
<a href="{{ post.permalink }}">{{ post.title }}</a> <a href="{{ post.permalink }}">{{ post.title }}</a>
</h2> </h2>

@ -0,0 +1,162 @@
{#
Those macros deal with introduction and navigation for series pages.
Using macros have been prefered over partial inclusion or inline code to make sure series_ordered_pages is forced to be used.
A section's pages natural order is invalid in case of reversed pagination which would lead to invalid series' pages order.
To prevent this, pages are ordered correctly in a separate variable which must be used instead of the series section pages.
#}
{#
Computes the introduction of a series's page.
Parameters:
- `page`: The page object being part of the series.
- `series_section`: The series' section the page belongs to.
- `series_ordered_pages`: The series' pages properly ordered (see at the top of this file for an explanation).
- `language_strings`: A dictionary containing the translation strings.
#}
{% macro process_series_template(template_type, page, series_section, series_ordered_pages, language_strings) %}
{%- if "series" in series_section.extra and series_section.extra.series -%}
{# Prepare variables for substitution #}
{%- set series_title = series_section.title -%}
{%- set series_permalink = series_section.permalink -%}
{%- set series_html_link = '<a href="' ~ series_section.permalink ~ '" aria_label="' ~ series_section.title ~ '">' ~ series_section.title ~ '</a>' -%}
{# Build series pages list #}
{%- set series_pages_list = [] -%}
{%- for series_page in series_ordered_pages -%}
{%- if series_page.relative_path == page.relative_path -%}
{%- set series_pages_list_item = '<li>' ~ series_page.title ~ '</li>' -%}
{%- else -%}
{%- set series_pages_list_item = '<li><a href="' ~ series_page.permalink ~ '" aria_label="' ~ series_page.title ~ '">' ~ series_page.title ~ '</a></li>' -%}
{%- endif -%}
{%- set_global series_pages_list = series_pages_list | concat(with=series_pages_list_item) -%}
{%- endfor -%}
{%- set series_pages_list = series_pages_list | join(sep="") -%}
{%- if macros_settings::evaluate_setting_priority(setting="post_listing_index_reversed", page=series_section, default_global_value=false) == "true" -%}
{%- set series_pages_ordered_list = '<ol reversed>' ~ series_pages_list ~ '</ol>' -%}
{%- else -%}
{%- set series_pages_ordered_list = '<ol>' ~ series_pages_list ~ '</ol>' -%}
{%- endif -%}
{%- set series_pages_unordered_list = '<ul>' ~ series_pages_list ~ '</ul>' -%}
{# Get page position and navigation info #}
{%- set series_pages_number = 0 -%}
{%- set series_page_index = 0 -%}
{%- set first_page = series_ordered_pages | first -%}
{%- set is_found = false -%}
{%- for series_page in series_ordered_pages -%}
{%- set_global series_pages_number = series_pages_number + 1 -%}
{%- if series_page.relative_path == page.relative_path -%}
{%- set_global series_page_index = series_pages_number -%}
{%- set_global is_found = true -%}
{%- else -%}
{%- if not is_found -%}
{%- set_global prev_page = series_page -%}
{%- elif not next_page is defined -%}
{%- set_global next_page = series_page -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{# Determine template to use based on available navigation #}
{%- set position = "middle" -%}
{%- if prev_page is defined and not next_page is defined -%}
{%- set_global position = "prev_only" -%}
{%- elif next_page is defined and not prev_page is defined -%}
{%- set_global position = "next_only" -%}
{%- endif -%}
{# Get template from config #}
{%- set templates_key = "series_" ~ template_type ~ "_templates" -%}
{%- set template = "" -%}
{%- if series_section.extra[templates_key] is defined -%}
{%- if series_section.extra[templates_key][position] is defined -%}
{%- set_global template = series_section.extra[templates_key][position] -%}
{%- elif series_section.extra[templates_key].default is defined -%}
{%- set_global template = series_section.extra[templates_key].default -%}
{%- endif -%}
{%- endif -%}
{# Prepare navigation variables #}
{%- if prev_page is defined -%}
{%- set prev_title = prev_page.title -%}
{%- set prev_permalink = prev_page.permalink -%}
{%- set prev_html_link = '<a href="' ~ prev_page.permalink ~ '" aria_label="' ~ prev_page.title ~ '">' ~ prev_page.title ~ '</a>' -%}
{%- set prev_description = prev_page.description | default(value="") -%}
{%- endif -%}
{%- if next_page is defined -%}
{%- set next_title = next_page.title -%}
{%- set next_permalink = next_page.permalink -%}
{%- set next_html_link = '<a href="' ~ next_page.permalink ~ '" aria_label="' ~ next_page.title ~ '">' ~ next_page.title ~ '</a>' -%}
{%- set next_description = next_page.description | default(value="") -%}
{%- endif -%}
{# Replace standard variables #}
{%- set template = template
| replace(from="$SERIES_TITLE", to=series_title)
| replace(from="$SERIES_PERMALINK", to=series_permalink)
| replace(from="$SERIES_HTML_LINK", to=series_html_link)
| replace(from="$FIRST_TITLE", to=first_page.title)
| replace(from="$FIRST_HTML_LINK", to='<a href="' ~ first_page.permalink ~ '">' ~ first_page.title ~ '</a>')
| replace(from="$SERIES_PAGES_NUMBER", to=series_pages_number | as_str)
| replace(from="$SERIES_PAGE_INDEX", to=series_page_index | as_str)
| replace(from="$SERIES_PAGES_OLIST", to=series_pages_ordered_list)
| replace(from="$SERIES_PAGES_ULIST", to=series_pages_unordered_list)
-%}
{# Replace navigation variables if they exist #}
{%- if prev_page is defined -%}
{%- set template = template
| replace(from="$PREV_TITLE", to=prev_title)
| replace(from="$PREV_PERMALINK", to=prev_permalink)
| replace(from="$PREV_HTML_LINK", to=prev_html_link)
| replace(from="$PREV_DESCRIPTION", to=prev_description)
-%}
{%- endif -%}
{%- if next_page is defined -%}
{%- set template = template
| replace(from="$NEXT_TITLE", to=next_title)
| replace(from="$NEXT_PERMALINK", to=next_permalink)
| replace(from="$NEXT_HTML_LINK", to=next_html_link)
| replace(from="$NEXT_DESCRIPTION", to=next_description)
-%}
{%- endif -%}
{# Custom placeholders #}
{%- if series_section.extra.series_template_placeholders is defined -%}
{%- set missing_vars = [] -%}
{%- for placeholder in series_section.extra.series_template_placeholders -%}
{%- if placeholder in template -%}
{%- set var_name = placeholder | replace(from="$", to="") | lower -%}
{%- if page.extra.series_template_variables is defined and page.extra.series_template_variables[var_name] is defined -%}
{%- set_global template = template | replace(from=placeholder, to=page.extra.series_template_variables[var_name]) -%}
{%- else -%}
{%- set_global missing_vars = missing_vars | concat(with=var_name) -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- if missing_vars | length > 0 -%}
{%- set missing_vars_str = missing_vars | join(sep=", ") -%}
{{ throw(message="ERROR: The following variables are included in this page's series templates (`series_template_placeholders`) but have not been set in the `series_template_variables` of this page: " ~ missing_vars_str) }}
{%- endif -%}
{%- endif -%}
{# Output the processed template if not empty #}
{%- if template | length > 0 -%}
<section class="series-page-{{ template_type }}">
{{ template | markdown | safe }}
</section>
{%- endif -%}
{%- endif -%}
{% endmacro %}
{# Macro for series introduction #}
{% macro get_introduction(page, series_section, series_ordered_pages, language_strings) %}
{{ macros_series_page::process_series_template(template_type="intro", page=page, series_section=series_section, series_ordered_pages=series_ordered_pages, language_strings=language_strings) }}
{% endmacro %}
{# Macro for series outro #}
{% macro get_outro(page, series_section, series_ordered_pages, language_strings) %}
{{ macros_series_page::process_series_template(template_type="outro", page=page, series_section=series_section, series_ordered_pages=series_ordered_pages, language_strings=language_strings) }}
{% endmacro %}

@ -180,6 +180,33 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
{% endif %} {% endif %}
</ul> </ul>
{#- A page is part of a series if one of the sections above (whether it is transparent or not) has the `extra.series` parameter set to true. -#}
{#- As a series might be a transparent section in order to mix up its articles with those of the section just above or the root, -#}
{#- there is no other way but to compute the potential path of each ancestor section related to the page and look for the first one being a series. -#}
{#- Using the `ancestors` field of a section is not possible because transparent sections are not present in this field. -#}
{%- set current_path = [] -%}
{%- set section_paths = [] -%}
{%- for path in page.relative_path | split(pat="/") | slice(end=-1) -%}
{%- set_global current_path = current_path | concat(with=path) -%}
{%- set section_path = current_path | concat(with="_index.md") | join(sep="/") -%}
{%- set_global section_paths = section_paths | concat(with=section_path) -%}
{%- endfor -%}
{#- The series the page is part of is the closest section flagged as a series, if any -#}
{%- for section_path in section_paths | reverse -%}
{%- set section_file_exists = load_data(path=section_path, required=false) -%}
{%- if section_file_exists -%}
{%- set loaded_section = get_section(path=section_path,lang=lang) -%}
{%- if "series" in loaded_section.extra and loaded_section.extra.series -%}
{%- set_global series_section = loaded_section -%}
{%- set_global series_ordered_pages = loaded_section.pages -%}
{%- if loaded_section.paginated | default(value=0) > 0 and loaded_section.paginate_reversed -%}
{%- set_global series_ordered_pages = loaded_section.pages | reverse -%}
{%- endif -%}
{%- break -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{% if page.extra.tldr %} {% if page.extra.tldr %}
<div class="admonition note"> <div class="admonition note">
<div class="admonition-icon admonition-icon-note"></div> <div class="admonition-icon admonition-icon-note"></div>
@ -192,18 +219,48 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
</div> </div>
{% endif %} {% endif %}
{# Optional table of contents below the header #} {#- Optional table of contents below the header -#}
{% if page.toc and macros_settings::evaluate_setting_priority(setting="toc", page=page, default_global_value=false) == "true" %} {% if page.toc and macros_settings::evaluate_setting_priority(setting="toc", page=page, default_global_value=false) == "true" %}
{{ macros_toc::toc(page=page, header=true, language_strings=language_strings) }} {{ macros_toc::toc(page=page, header=true, language_strings=language_strings) }}
{% endif %} {% endif %}
<section class="body"> <section class="body">
{# The replace pattern is used to enable arbitrary locations for the Table of Contents #} {#- Replace series_intro placeholder -#}
{# This is Philipp Oppermann's workaround: https://github.com/getzola/zola/issues/584#issuecomment-474329637 #} {%- set content_with_intro = page.content -%}
{{ page.content | replace(from="<!-- toc -->", to=macros_toc::toc(page=page, header=false, language_strings=language_strings)) | safe }} {%- if "<!-- series_intro -->" in page.content -%}
{%- set series_intro_html = macros_series_page::get_introduction(page=page, series_section=series_section, series_ordered_pages=series_ordered_pages, language_strings=language_strings) -%}
{%- set content_with_intro = content_with_intro | replace(from="<!-- series_intro -->", to=series_intro_html) -%}
{%- elif series_section -%}
{%- set series_intro_html = macros_series_page::get_introduction(page=page, series_section=series_section, series_ordered_pages=series_ordered_pages, language_strings=language_strings) -%}
{%- set content_with_intro = series_intro_html ~ content_with_intro -%}
{%- endif -%}
{#- Handle series_outro placeholder -#}
{%- set processed_content = content_with_intro -%}
{%- if "<!-- series_outro -->" in content_with_intro -%}
{%- set series_outro_html = macros_series_page::get_outro(page=page, series_section=series_section, series_ordered_pages=series_ordered_pages, language_strings=language_strings) -%}
{%- set processed_content = processed_content | replace(from="<!-- series_outro -->", to=series_outro_html) -%}
{%- elif series_section -%}
{%- set series_outro_html = macros_series_page::get_outro(page=page, series_section=series_section, series_ordered_pages=series_ordered_pages, language_strings=language_strings) -%}
{#- We want the outro at the end of the article, but before footnote definitions -#}
{%- set footnotes_marker = '<hr><ol class="footnotes-list">' -%}
{%- if footnotes_marker in content_with_intro -%}
{%- set content_sections = processed_content | split(pat=footnotes_marker) -%}
{%- set main_content = content_sections | first -%}
{%- set footnotes_content = content_sections | slice(start=1) | join(sep=footnotes_marker) -%}
{%- set processed_content = main_content ~ series_outro_html ~ footnotes_marker ~ footnotes_content -%}
{%- else -%}
{%- set processed_content = processed_content ~ series_outro_html -%}
{%- endif -%}
{%- endif -%}
{#- Replace TOC and render final content -#}
{#- The replace pattern is used to enable arbitrary locations for the Table of Contents -#}
{#- This is Philipp Oppermann's workaround: https://github.com/getzola/zola/issues/584#issuecomment-474329637 -#}
{{ processed_content | replace(from="<!-- toc -->", to=macros_toc::toc(page=page, header=false, language_strings=language_strings)) | safe }}
</section> </section>
{# Check if comments are enabled, checking that they are not disabled on the specific page #} {#- Check if comments are enabled, checking that they are not disabled on the specific page -#}
{% set systems = ["giscus", "utterances", "hyvortalk", "isso"] %} {% set systems = ["giscus", "utterances", "hyvortalk", "isso"] %}
{% set enabled_systems = 0 %} {% set enabled_systems = 0 %}
{% set comment_system = "" %} {% set comment_system = "" %}
@ -219,11 +276,6 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{# Ensure only one comment system is enabled #}
{% if enabled_systems > 1 %}
{{ throw(message="ERROR: Multiple comment systems have been enabled for the same page. Check your config.toml and individual page settings to ensure only one comment system is activated at a time.") }}
{% endif %}
{% if macros_settings::evaluate_setting_priority(setting="show_previous_next_article_links", page=page, default_global_value=true) == "true" %} {% if macros_settings::evaluate_setting_priority(setting="show_previous_next_article_links", page=page, default_global_value=true) == "true" %}
{%- if page.lower or page.higher -%} {%- if page.lower or page.higher -%}
{% set next_label = macros_translate::translate(key="next", default="Next", language_strings=language_strings) %} {% set next_label = macros_translate::translate(key="next", default="Next", language_strings=language_strings) %}
@ -271,7 +323,11 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
{%- endif -%} {%- endif -%}
{%- endif -%} {%- endif -%}
{# Comments #} {#- Comments -#}
{#- Ensure only one comment system is enabled -#}
{% if enabled_systems > 1 %}
{{ throw(message="ERROR: Multiple comment systems have been enabled for the same page. Check your config.toml and individual page settings to ensure only one comment system is activated at a time.") }}
{% endif %}
{% if comment_system %} {% if comment_system %}
{% include "partials/comments.html" %} {% include "partials/comments.html" %}
{% endif %} {% endif %}

@ -1,8 +1,9 @@
{%- set page_or_section = page | default(value=section) -%}
{# Quick navigation buttons #} {# Quick navigation buttons #}
{% if macros_settings::evaluate_setting_priority(setting="quick_navigation_buttons", page=page, default_global_value=false) == "true" %} {% if macros_settings::evaluate_setting_priority(setting="quick_navigation_buttons", page=page_or_section, default_global_value=false) == "true" %}
<div id="button-container"> <div id="button-container">
{# Button to go show a floating Table of Contents #} {# Button to go show a floating Table of Contents #}
{% if page.toc %} {% if page_or_section.toc %}
<div id="toc-floating-container"> <div id="toc-floating-container">
<input type="checkbox" id="toc-toggle" class="toggle"/> <input type="checkbox" id="toc-toggle" class="toggle"/>
<label for="toc-toggle" class="overlay"></label> <label for="toc-toggle" class="overlay"></label>
@ -10,7 +11,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M414.82-193.094q-18.044 0-30.497-12.32-12.453-12.319-12.453-30.036t12.453-30.086q12.453-12.37 30.497-12.37h392.767q17.237 0 29.927 12.487 12.69 12.486 12.69 30.203 0 17.716-12.69 29.919t-29.927 12.203H414.82Zm0-244.833q-18.044 0-30.497-12.487Q371.87-462.9 371.87-480.45t12.453-29.92q12.453-12.369 30.497-12.369h392.767q17.237 0 29.927 12.511 12.69 12.512 12.69 29.845 0 17.716-12.69 30.086-12.69 12.37-29.927 12.37H414.82Zm0-245.167q-18.044 0-30.497-12.32t-12.453-30.037q0-17.716 12.453-30.086 12.453-12.369 30.497-12.369h392.767q17.237 0 29.927 12.486 12.69 12.487 12.69 30.203 0 17.717-12.69 29.92-12.69 12.203-29.927 12.203H414.82ZM189.379-156.681q-32.652 0-55.878-22.829t-23.226-55.731q0-32.549 23.15-55.647 23.151-23.097 55.95-23.097 32.799 0 55.313 23.484 22.515 23.484 22.515 56.246 0 32.212-22.861 54.893-22.861 22.681-54.963 22.681Zm0-245.167q-32.652 0-55.878-23.134-23.226-23.135-23.226-55.623 0-32.487 23.467-55.517t56.12-23.03q32.102 0 54.721 23.288 22.62 23.288 22.62 55.775 0 32.488-22.861 55.364-22.861 22.877-54.963 22.877Zm-.82-244.833q-32.224 0-55.254-23.288-23.03-23.289-23.03-55.623 0-32.333 23.271-55.364 23.272-23.03 55.495-23.03 32.224 0 55.193 23.288 22.969 23.289 22.969 55.622 0 32.334-23.21 55.364-23.21 23.031-55.434 23.031Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M414.82-193.094q-18.044 0-30.497-12.32-12.453-12.319-12.453-30.036t12.453-30.086q12.453-12.37 30.497-12.37h392.767q17.237 0 29.927 12.487 12.69 12.486 12.69 30.203 0 17.716-12.69 29.919t-29.927 12.203H414.82Zm0-244.833q-18.044 0-30.497-12.487Q371.87-462.9 371.87-480.45t12.453-29.92q12.453-12.369 30.497-12.369h392.767q17.237 0 29.927 12.511 12.69 12.512 12.69 29.845 0 17.716-12.69 30.086-12.69 12.37-29.927 12.37H414.82Zm0-245.167q-18.044 0-30.497-12.32t-12.453-30.037q0-17.716 12.453-30.086 12.453-12.369 30.497-12.369h392.767q17.237 0 29.927 12.486 12.69 12.487 12.69 30.203 0 17.717-12.69 29.92-12.69 12.203-29.927 12.203H414.82ZM189.379-156.681q-32.652 0-55.878-22.829t-23.226-55.731q0-32.549 23.15-55.647 23.151-23.097 55.95-23.097 32.799 0 55.313 23.484 22.515 23.484 22.515 56.246 0 32.212-22.861 54.893-22.861 22.681-54.963 22.681Zm0-245.167q-32.652 0-55.878-23.134-23.226-23.135-23.226-55.623 0-32.487 23.467-55.517t56.12-23.03q32.102 0 54.721 23.288 22.62 23.288 22.62 55.775 0 32.488-22.861 55.364-22.861 22.877-54.963 22.877Zm-.82-244.833q-32.224 0-55.254-23.288-23.03-23.289-23.03-55.623 0-32.333 23.271-55.364 23.272-23.03 55.495-23.03 32.224 0 55.193 23.288 22.969 23.289 22.969 55.622 0 32.334-23.21 55.364-23.21 23.031-55.434 23.031Z"/></svg>
</label> </label>
<div class="toc-content"> <div class="toc-content">
{{ macros_toc::toc(page=page, header=false, language_strings=language_strings) }} {{ macros_toc::toc(page=page_or_section, header=false, language_strings=language_strings) }}
</div> </div>
</div> </div>
{% endif %} {% endif %}
@ -30,13 +31,13 @@
{% endif %} {% endif %}
{# Add KaTeX functionality #} {# Add KaTeX functionality #}
{%- if macros_settings::evaluate_setting_priority(setting="katex", page=page, default_global_value=false) == "true" -%} {%- if macros_settings::evaluate_setting_priority(setting="katex", page=page_or_section, default_global_value=false) == "true" -%}
<link rel="stylesheet" href="{{ get_url(path='katex.min.css', trailing_slash=false) | safe }}"> <link rel="stylesheet" href="{{ get_url(path='katex.min.css', trailing_slash=false) | safe }}">
<script defer src="{{ get_url(path='js/katex.min.js', trailing_slash=false) | safe }}"></script> <script defer src="{{ get_url(path='js/katex.min.js', trailing_slash=false) | safe }}"></script>
{%- endif -%} {%- endif -%}
{# Load mermaid.js #} {# Load mermaid.js #}
{%- if macros_settings::evaluate_setting_priority(setting="mermaid", page=page, default_global_value=false) == "true" -%} {%- if macros_settings::evaluate_setting_priority(setting="mermaid", page=page_or_section, default_global_value=false) == "true" -%}
{%- if config.extra.serve_local_mermaid | default(value=true) -%} {%- if config.extra.serve_local_mermaid | default(value=true) -%}
<script defer src="{{ get_url(path='js/mermaid.min.js', trailing_slash=false) | safe }}"></script> <script defer src="{{ get_url(path='js/mermaid.min.js', trailing_slash=false) | safe }}"></script>
{%- else -%} {%- else -%}
@ -45,7 +46,7 @@
{%- endif -%} {%- endif -%}
{# Add copy button to code blocks #} {# Add copy button to code blocks #}
{%- if macros_settings::evaluate_setting_priority(setting="copy_button", page=page, default_global_value=true) == "true" -%} {%- if macros_settings::evaluate_setting_priority(setting="copy_button", page=page_or_section, default_global_value=true) == "true" -%}
{#- Add hidden HTML elements with the translated strings for the button's interactions -#} {#- Add hidden HTML elements with the translated strings for the button's interactions -#}
<span id="copy-success" class="hidden"> <span id="copy-success" class="hidden">
{{ macros_translate::translate(key="copied", default="Copied!", language_strings=language_strings) }} {{ macros_translate::translate(key="copied", default="Copied!", language_strings=language_strings) }}
@ -57,11 +58,11 @@
{%- endif -%} {%- endif -%}
{# JavaScript to use the "Show source or path" on code blocks shortcode: https://welpo.github.io/tabi/blog/shortcodes/#show-source-or-path #} {# JavaScript to use the "Show source or path" on code blocks shortcode: https://welpo.github.io/tabi/blog/shortcodes/#show-source-or-path #}
{%- if macros_settings::evaluate_setting_priority(setting="add_src_to_code_block", page=page, default_global_value=false) == "true" -%} {%- if macros_settings::evaluate_setting_priority(setting="add_src_to_code_block", page=page_or_section, default_global_value=false) == "true" -%}
<script defer src="{{ get_url(path='js/addSrcToCodeBlock.min.js', trailing_slash=false) | safe }}"></script> <script defer src="{{ get_url(path='js/addSrcToCodeBlock.min.js', trailing_slash=false) | safe }}"></script>
{%- endif -%} {%- endif -%}
{# Add backlinks to footnotes #} {# Add backlinks to footnotes #}
{%- if macros_settings::evaluate_setting_priority(setting="footnote_backlinks", page=page, default_global_value=false) == "true" -%} {%- if macros_settings::evaluate_setting_priority(setting="footnote_backlinks", page=page_or_section, default_global_value=false) == "true" -%}
<script defer src="{{ get_url(path='js/footnoteBacklinks.min.js', trailing_slash=false | safe )}}"></script> <script defer src="{{ get_url(path='js/footnoteBacklinks.min.js', trailing_slash=false | safe )}}"></script>
{%- endif -%} {%- endif -%}

@ -1,4 +1,4 @@
{% if paginator %} {% if paginator and paginator.number_pagers > 1 %}
<ul class="pagination"> <ul class="pagination">
{% if paginator.previous %} {% if paginator.previous %}
<li class="page-item page-prev"> <li class="page-item page-prev">

@ -0,0 +1,62 @@
{% extends "base.html" %}
{% block main_content %}
{# Throw an error if the section is not flagged as a series. #}
{# This page would be displayed properly but it would become impossible for the series' child pages to reference their series. #}
{%- if "series" not in section.extra or not section.extra.series -%}
{{ throw(message="Section is not flagged as a series. Set `section.extra.series` to `true` if you want to use `series.html` template.") }}
{%- endif -%}
<main>
{%- if section.extra.header %}
{%- include "partials/home_banner.html" -%}
{% endif -%}
{%- set show_jump = false -%}
{%- set show_jump_hierarchy = macros_settings::evaluate_setting_priority(setting="show_jump_to_posts", page=section) -%}
{%- if show_jump_hierarchy == "true" -%}
{%- set show_jump = true -%}
{%- elif show_jump_hierarchy != "false" -%}
{#- Default to true if the content is long and var is unset #}
{%- if section.content | length > 2000 -%}
{%- set show_jump = true -%}
{%- endif -%}
{%- endif -%}
{%- if show_jump -%}
<div class="title-with-jump bottom-divider">
<h1 class="title-container section-title">{{ section.title }}</h1>
<a href="#posts-list" class="jump-link">{{ macros_translate::translate(key="jump_to_posts", default="Jump to posts", language_strings=language_strings) }} ↓</a>
</div>
{%- else -%}
{{ macros_page_header::page_header(title=section.title) }}
{%- endif -%}
<section class="body">
{{ section.content | safe }}
</section>
<div id="posts-list">
<h2 class="bottom-divider">
{{ macros_translate::translate(key="all_posts", default="All posts", language_strings=language_strings) }}
</h2>
{%- if paginator %}
{%- set pages = paginator.pages -%}
{% else %}
{%- set pages = section.pages -%}
{% endif -%}
{% set max_posts = section.extra.max_posts | default(value=999999) %}
{{ macros_list_posts::list_posts(posts=pages, max=max_posts, metadata="indexes", language_strings=language_strings, section_path=section.path, paginator=paginator | default(value="")) }}
</div>
{% if paginator %}
{%- include "partials/paginate.html" -%}
{% endif %}
</main>
{%- include "partials/extra_features.html" -%}
{% endblock main_content %}

@ -114,6 +114,16 @@ show_date = true
# "both" - Show both the original date and the last updated date. # "both" - Show both the original date and the last updated date.
post_listing_date = "date" post_listing_date = "date"
# Show "Jump to posts" link next to series' title.
# By default, the link appears automatically when a series description exceeds 2000 characters.
# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
# show_jump_to_posts = true
# Determines if indexes should be increasing (false) or decreasing (true) in series' posts list.
# It has only effect if the section uses indexes metadata (which is only the case for series as of now).
# Can be set at section levels, following the hierarchy: section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
post_listing_index_reversed = false # Defaults to false.
# DEPRECATED! # DEPRECATED!
# Use Zola's built-in `bottom_footnotes = true` in the [markdown] section instead. (Available since v0.19.0) # Use Zola's built-in `bottom_footnotes = true` in the [markdown] section instead. (Available since v0.19.0)
# Adds backlinks to footnotes (loads ~500 bytes of JavaScripts). # Adds backlinks to footnotes (loads ~500 bytes of JavaScripts).

Loading…
Cancel
Save