Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions assets/xsl/rss.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
{{- $logo := resources.Get "images/logo.png" -}}
{{- $logo = partial "image_resize.html" (dict "img" $logo "width" 400) -}}
<?xml version="1.0" encoding="utf-8"?>
<!--
Renders an RSS feed as a readable page when a person opens it in a browser.
A feed reader ignores this stylesheet and reads the XML directly.
XSLT 1.0 only: that is what browsers implement.
Pairs with sitemap.xsl: same heading shape, same intro, same CSS, same date form.
Serves /index.xml, the only feed of the site. A section feed, if one is ever
built again, gets its section name in the heading.
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="html" encoding="utf-8" indent="yes" doctype-system="about:legacy-compat"/>

<!--
Hugo titles a section feed "Events on PyBCN - The Barcelona Python
Association" and the home feed "Welcome to PyBCN on PyBCN - ...". The
heading keeps the part before " on " for a section and nothing for the
home feed, which is the one whose link has no path after the host.
-->
<xsl:variable name="section" select="substring-before(rss/channel/title, ' on ')"/>
<xsl:variable name="path" select="substring-after(substring-after(rss/channel/link, '//'), '/')"/>
<xsl:variable name="heading">
<xsl:text>PyBCN feed</xsl:text>
<xsl:if test="$path != '' and $section != ''">
<xsl:value-of select="concat(': ', $section)"/>
</xsl:if>
</xsl:variable>
<xsl:variable name="count" select="count(rss/channel/item)"/>

<!-- RFC 822, Sun, 04 Oct 2026 10:09:00 +0200, shown as 2026-10-04 10:09 +02:00. -->
<xsl:template name="when">
<xsl:param name="stamp"/>
<xsl:if test="string-length($stamp) >= 31">
<xsl:value-of select="substring($stamp, 13, 4)"/>
<xsl:text>-</xsl:text>
<xsl:choose>
<xsl:when test="substring($stamp, 9, 3) = 'Jan'">01</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Feb'">02</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Mar'">03</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Apr'">04</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'May'">05</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Jun'">06</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Jul'">07</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Aug'">08</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Sep'">09</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Oct'">10</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Nov'">11</xsl:when>
<xsl:when test="substring($stamp, 9, 3) = 'Dec'">12</xsl:when>
<xsl:otherwise>??</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="concat('-', substring($stamp, 6, 2), ' ', substring($stamp, 18, 5), ' ', substring($stamp, 27, 3), ':', substring($stamp, 30, 2))"/>
</xsl:if>
</xsl:template>

<xsl:template match="/">
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="robots" content="noindex"/>
<title><xsl:value-of select="$heading"/></title>
<link rel="icon" href="/favicon.ico" sizes="any"/>
<style>
body { margin: 0; color: #343a40; background: #fff;
font-family: Helvetica, Roboto, "Segoe UI", Calibri, sans-serif; }
h1 { margin: 0; padding: 0; font-weight: normal; font-size: 2rem; }
header { padding: 28px 0 24px; text-align: center; background: #fff;
border-bottom: 1px solid #dee2e6; margin-bottom: 1.5rem; }
header a { display: inline-block; color: #006c9b; text-decoration: none; }
header img { display: block; height: 58px; width: auto; margin: 0 auto 14px; }
header a:hover h1, header a:focus h1 { text-decoration: underline; }
main { max-width: 72rem; margin: 0 auto; padding: 0 1rem 2rem; }
p { margin: 0 0 1rem; }
a { color: #006c9b; }
table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
th, td { padding: 0.4rem 0.6rem; text-align: left; vertical-align: top;
border-bottom: 1px solid #dee2e6; }
th { background: #f1f5f8; font-weight: 600; }
.n { width: 2.5rem; color: #6c757d; text-align: right; white-space: nowrap; }
.when { width: 12rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
td.loc { word-break: break-all; }
tbody tr:hover { background: #f1f5f8; }
</style>
</head>
<body>
<header>
<a href="{{ site.Home.Permalink }}">
<img src="{{ $logo.RelPermalink }}" width="{{ $logo.Width }}" height="{{ $logo.Height }}" alt="Python Barcelona"/>
<h1><xsl:value-of select="$heading"/></h1>
</a>
</header>
<main>
<p>
<xsl:value-of select="$count"/>
<xsl:choose>
<xsl:when test="$count = 1"> entry</xsl:when>
<xsl:otherwise> entries</xsl:otherwise>
</xsl:choose>, most recently changed first.
Feed readers read the XML behind this page; a browser shows it as a table.
The feed covers <a href="{rss/channel/link}"><xsl:value-of select="rss/channel/link"/></a>.
</p>
<table>
<thead>
<tr>
<th class="n">#</th>
<th>Title</th>
<th class="when">Published</th>
</tr>
</thead>
<tbody>
<xsl:for-each select="rss/channel/item">
<tr>
<td class="n"><xsl:value-of select="position()"/></td>
<td>
<!-- An item with no title shows its link, so the row has text to read and click. -->
<a href="{link}">
<xsl:choose>
<xsl:when test="normalize-space(title) != ''"><xsl:value-of select="title"/></xsl:when>
<xsl:otherwise><xsl:value-of select="link"/></xsl:otherwise>
</xsl:choose>
</a>
</td>
<td class="when">
<xsl:call-template name="when">
<xsl:with-param name="stamp" select="pubDate"/>
</xsl:call-template>
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</main>
</body>
</html>
</xsl:template>

</xsl:stylesheet>
104 changes: 104 additions & 0 deletions assets/xsl/sitemap.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{{- $logo := resources.Get "images/logo.png" -}}
{{- $logo = partial "image_resize.html" (dict "img" $logo "width" 400) -}}
<?xml version="1.0" encoding="utf-8"?>
<!--
Renders /sitemap.xml as a readable page when a person opens it in a browser.
Search engines ignore this stylesheet and read the XML directly.
XSLT 1.0 only: that is what browsers implement.
Pairs with rss.xsl: same heading shape, same intro, same CSS, same date form.
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:s="http://www.sitemaps.org/schemas/sitemap/0.9"
exclude-result-prefixes="s">

<xsl:output method="html" encoding="utf-8" indent="yes" doctype-system="about:legacy-compat"/>

<xsl:variable name="heading">PyBCN sitemap</xsl:variable>
<xsl:variable name="count" select="count(s:urlset/s:url)"/>

<!-- W3C datetime, 2026-10-05T18:27:40+02:00, shown as 2026-10-05 18:27 +02:00. -->
<xsl:template name="when">
<xsl:param name="stamp"/>
<xsl:if test="string-length($stamp) >= 25">
<xsl:value-of select="concat(substring($stamp, 1, 10), ' ', substring($stamp, 12, 5), ' ', substring($stamp, 20, 6))"/>
</xsl:if>
</xsl:template>

<xsl:template match="/">
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="robots" content="noindex"/>
<title><xsl:value-of select="$heading"/></title>
<link rel="icon" href="/favicon.ico" sizes="any"/>
<style>
body { margin: 0; color: #343a40; background: #fff;
font-family: Helvetica, Roboto, "Segoe UI", Calibri, sans-serif; }
h1 { margin: 0; padding: 0; font-weight: normal; font-size: 2rem; }
header { padding: 28px 0 24px; text-align: center; background: #fff;
border-bottom: 1px solid #dee2e6; margin-bottom: 1.5rem; }
header a { display: inline-block; color: #006c9b; text-decoration: none; }
header img { display: block; height: 58px; width: auto; margin: 0 auto 14px; }
header a:hover h1, header a:focus h1 { text-decoration: underline; }
main { max-width: 72rem; margin: 0 auto; padding: 0 1rem 2rem; }
p { margin: 0 0 1rem; }
a { color: #006c9b; }
table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
th, td { padding: 0.4rem 0.6rem; text-align: left; vertical-align: top;
border-bottom: 1px solid #dee2e6; }
th { background: #f1f5f8; font-weight: 600; }
.n { width: 2.5rem; color: #6c757d; text-align: right; white-space: nowrap; }
.when { width: 12rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
td.loc { word-break: break-all; }
tbody tr:hover { background: #f1f5f8; }
</style>
</head>
<body>
<header>
<a href="{{ site.Home.Permalink }}">
<img src="{{ $logo.RelPermalink }}" width="{{ $logo.Width }}" height="{{ $logo.Height }}" alt="Python Barcelona"/>
<h1><xsl:value-of select="$heading"/></h1>
</a>
</header>
<main>
<p>
<xsl:value-of select="$count"/>
<xsl:choose>
<xsl:when test="$count = 1"> URL</xsl:when>
<xsl:otherwise> URLs</xsl:otherwise>
</xsl:choose>, most recently changed first.
Search engines read the XML behind this page; a browser shows it as a table.
</p>
<table>
<thead>
<tr>
<th class="n">#</th>
<th>URL</th>
<th class="when">Last modified</th>
</tr>
</thead>
<tbody>
<xsl:for-each select="s:urlset/s:url">
<xsl:sort select="s:lastmod" order="descending"/>
<tr>
<td class="n"><xsl:value-of select="position()"/></td>
<td class="loc">
<a href="{s:loc}"><xsl:value-of select="s:loc"/></a>
</td>
<td class="when">
<xsl:call-template name="when">
<xsl:with-param name="stamp" select="s:lastmod"/>
</xsl:call-template>
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</main>
</body>
</html>
</xsl:template>

</xsl:stylesheet>
36 changes: 36 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,44 @@ timeout = "300s"
isPlainText = true
notAlternative = true

# One feed for the whole site, /index.xml, and none per section. Hugo built
# one for every section by default: ten feeds, nine of them a by-product,
# from /contact/index.xml with zero items to /people/index.xml with 138
# items and no titles. The home feed carries the same pages, so a section
# feed adds nothing a reader can use (#201).
[outputs]
home = ["HTML", "RSS", "llms"]
section = ["HTML"]

# Every file under content/people/ and content/sponsors/ feeds the grids and
# the modals through its front matter. Hugo also built a page from each one,
# with no inbound link anywhere on the site, and they were 206 of the 247
# entries in the sitemap (#201). The sponsor pages are empty; 73 of the 138
# person files carry a bio that did render. They all stop, bio or not:
# publishing only the people who happen to have a paragraph written about
# them would sort the organizers and speakers of this association into two
# classes. The pages come back with the Archive, when every person has one
# worth linking to.
#
# render = "never" stops the page and keeps the default list = "always", so
# the file stays in site.Pages, where people-grid.html and sponsors-grid.html
# look it up by id. list = "local" would take it out of site.Pages and empty
# every grid. A page that is not rendered has no permalink, which is how the
# feed template leaves it out (layouts/_default/rss.xml).
#
# /people/ had nothing but a heading and goes with them. /sponsors/ is a real
# page and stays.
[[cascade]]
[cascade._build]
render = "never"
[cascade._target]
path = "{/people,/people/**}"

[[cascade]]
[cascade._build]
render = "never"
[cascade._target]
path = "/sponsors/*"


[markup.goldmark.renderer]
Expand Down
2 changes: 0 additions & 2 deletions content/people/christian-adell.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
---
id: christian-adell
aliases:
- /people/christian-adell-querol/
name: Christian Adell
photo: christian-adell.jpg

Expand Down
2 changes: 0 additions & 2 deletions content/people/kemalcan-bora.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
---
id: kemalcan-bora
aliases:
- /people/kemalkan-bora/
name: "Kemalcan Bora"
photo: "kemalcan-bora.jpg"
linkedin: "https://www.linkedin.com/in/kemalcanbora/"
Expand Down
70 changes: 70 additions & 0 deletions layouts/_default/rss.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{{- $authorEmail := "" }}
{{- with site.Params.author }}
{{- if reflect.IsMap . }}
{{- with .email }}
{{- $authorEmail = . }}
{{- end }}
{{- end }}
{{- end }}

{{- $authorName := "" }}
{{- with site.Params.author }}
{{- if reflect.IsMap . }}
{{- with .name }}
{{- $authorName = . }}
{{- end }}
{{- else }}
{{- $authorName = . }}
{{- end }}
{{- end }}

{{- $pctx := . }}
{{- if .IsHome }}{{ $pctx = .Site }}{{ end }}
{{- $pages := slice }}
{{- if or $.IsHome $.IsSection }}
{{- $pages = $pctx.RegularPages }}
{{- else }}
{{- $pages = $pctx.Pages }}
{{- end }}
{{- /* No content file carries a date, so .Date and .PublishDate are zero and the
default order is by title. .Lastmod comes from git (enableGitInfo), the same
source as the sitemap's lastmod: last change first, and the item date is the
date of the last commit that touched the file. */ -}}
{{- /* A page with _build.render = "never" stays in the collections (the people
and sponsor files feed the grids that way) but has no permalink. The feed
lists only pages a person can visit. */ -}}
{{- $pages = where $pages "Permalink" "ne" "" }}
{{- $pages = $pages.ByLastmod.Reverse }}
{{- $limit := .Site.Config.Services.RSS.Limit }}
{{- if ge $limit 1 }}
{{- $pages = $pages | first $limit }}
{{- end }}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
{{- $xsl := resources.Get "xsl/rss.xsl" | resources.ExecuteAsTemplate "xsl/rss.xsl" . | resources.Fingerprint -}}
{{- printf "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>" $xsl.RelPermalink | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} on {{ end }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{ . }} {{ end }}{{ end }}on {{ .Site.Title }}</description>
<generator>Hugo</generator>
<language>{{ site.Language.LanguageCode }}</language>{{ with $authorEmail }}
<managingEditor>{{.}}{{ with $authorName }} ({{ . }}){{ end }}</managingEditor>{{ end }}{{ with $authorEmail }}
<webMaster>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</webMaster>{{ end }}{{ with .Site.Copyright }}
<copyright>{{ . }}</copyright>{{ end }}{{ if not .Date.IsZero }}
<lastBuildDate>{{ (index $pages.ByLastmod.Reverse 0).Lastmod.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{- with .OutputFormats.Get "RSS" }}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end }}
{{- range $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Lastmod.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
<guid>{{ .Permalink }}</guid>
<description>{{ .Summary | transform.XMLEscape | safeHTML }}</description>
</item>
{{- end }}
</channel>
</rss>
Loading
Loading