Skip to content

Commit 18bc7a9

Browse files
authored
feature(rss): add option to set a stylesheet (#432)
It comes with a special value to use the XSL shipped with the plugin
2 parents 46d2f0f + ac4fb39 commit 18bc7a9

9 files changed

Lines changed: 262 additions & 0 deletions

File tree

docs/configuration.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,38 @@ Default: `False`.
550550

551551
----
552552

553+
### :material-brush-variant: `stylesheet`: define a XSL stylesheet { #stylesheet }
554+
555+
Use a XSL stylesheet to customize how the RSS feed looks like. `auto` is a special value to use the stylesheet shipped with the plugin.
556+
557+
```yaml
558+
plugins:
559+
- rss:
560+
stylesheet: auto
561+
```
562+
563+
If you're willing to work on your own stylesheet, set the URL or relative path:
564+
565+
```yaml
566+
plugins:
567+
- rss:
568+
stylesheet: https://docs.example.org/rss_stylesheet.xsl
569+
```
570+
571+
Note that it's recommended to host the stylesheet on the same website / domain to make CORS happy.
572+
573+
If you prefer to disable the stylesheet, you set it to `""`:
574+
575+
```yaml
576+
plugins:
577+
- rss:
578+
stylesheet: ""
579+
```
580+
581+
Default: `auto`.
582+
583+
----
584+
553585
### :material-track-light: `url_parameters`: additional URL parameters { #url_parameters }
554586

555587
This option allows you to add parameters to the URLs of the RSS feed items. It works as a dictionary of keys/values that is passed to [Python *urllib.parse.urlencode*](https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode).

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ plugins:
121121
match_path: ".*"
122122
pretty_print: true
123123
rss_feed_enabled: true
124+
stylesheet: auto
124125
url_parameters:
125126
utm_source: "documentation"
126127
utm_medium: "RSS"

mkdocs_rss_plugin/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class RssPluginConfig(Config):
5858
match_path = config_options.Type(str, default=".*")
5959
pretty_print = config_options.Type(bool, default=False)
6060
rss_feed_enabled = config_options.Type(bool, default=True)
61+
stylesheet = config_options.Type(str, default="auto")
6162
url_parameters = config_options.Optional(config_options.Type(dict))
6263
use_git = config_options.Type(bool, default=True)
6364
use_material_blog = config_options.Type(bool, default=True)

mkdocs_rss_plugin/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,5 +90,6 @@ class RssFeedBase:
9090
pubDate: str | None = None
9191
repo_url: str | None = None
9292
rss_url: str | None = None
93+
stylesheet: str | None = None
9394
title: str | None = None
9495
ttl: int | None = None

mkdocs_rss_plugin/plugin.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from email.utils import format_datetime, formatdate
1313
from pathlib import Path
1414
from re import compile as re_compile
15+
from shutil import copyfile
1516
from typing import Literal
1617

1718
# 3rd party
@@ -175,6 +176,23 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig:
175176
if self.config.image:
176177
base_feed.logo_url = self.config.image
177178

179+
# feed stylesheet (XSL)
180+
print(f"Config stylesheet: {self.config.stylesheet}")
181+
if self.config.stylesheet:
182+
if self.config.stylesheet == "auto":
183+
base_feed.stylesheet = "rss.xsl"
184+
logger.debug(
185+
f"Shipped stylesheet will be referenced in RSS feeds: {self.config.stylesheet}"
186+
)
187+
else:
188+
189+
base_feed.stylesheet = self.config.stylesheet
190+
logger.debug(
191+
f"Stylesheet will be referenced in RSS feeds: {self.config.stylesheet}"
192+
)
193+
else:
194+
logger.debug("No stylesheet will be referenced in RSS feeds.")
195+
178196
# pattern to match pages included in output
179197
self.match_path_pattern = re_compile(self.config.match_path)
180198

@@ -378,6 +396,12 @@ def on_post_build(self, config: config_options.Config) -> None:
378396
self.config.feeds_filenames.json_updated
379397
)
380398

399+
# stylesheet for RSS feed
400+
if self.config.stylesheet == "auto":
401+
xsl_source = self.tpl_folder.joinpath("default.xsl")
402+
xsl_dest = Path(config.site_dir).joinpath("rss.xsl")
403+
copyfile(xsl_source, xsl_dest)
404+
381405
# created items
382406
self.feed_created.entries.extend(
383407
self.util.filter_pages(
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xsl:stylesheet version="1.0"
3+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
4+
xmlns:atom="http://www.w3.org/2005/Atom"
5+
xmlns:dc="http://purl.org/dc/elements/1.1/">
6+
7+
<xsl:output method="html" encoding="UTF-8" indent="yes"/>
8+
9+
<xsl:template match="/">
10+
<html>
11+
<head>
12+
<meta charset="UTF-8"/>
13+
<title>
14+
<xsl:value-of select="rss/channel/title"/>
15+
</title>
16+
<style>
17+
body {
18+
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
19+
max-width: 900px;
20+
margin: 40px auto;
21+
padding: 0 20px;
22+
background: #f9fafb;
23+
color: #111827;
24+
}
25+
26+
header {
27+
border-bottom: 2px solid #e5e7eb;
28+
padding-bottom: 20px;
29+
margin-bottom: 30px;
30+
}
31+
32+
header img {
33+
vertical-align: middle;
34+
margin-right: 15px;
35+
}
36+
header .meta {
37+
margin-top: 15px;
38+
font-size: 0.9em;
39+
color: #6b7280;
40+
}
41+
42+
h1 {
43+
margin: 0;
44+
}
45+
46+
.item {
47+
background: white;
48+
padding: 20px;
49+
margin-bottom: 25px;
50+
border-radius: 10px;
51+
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
52+
}
53+
54+
.item h2 {
55+
margin-top: 0;
56+
}
57+
58+
.meta {
59+
font-size: 0.9em;
60+
color: #6b7280;
61+
margin-bottom: 10px;
62+
}
63+
64+
.categories span {
65+
background: #eef2ff;
66+
color: #3730a3;
67+
padding: 3px 8px;
68+
margin-right: 5px;
69+
border-radius: 6px;
70+
font-size: 0.75em;
71+
}
72+
73+
.item img {
74+
max-width: 100%;
75+
margin: 15px 0;
76+
border-radius: 8px;
77+
}
78+
79+
a {
80+
color: #2563eb;
81+
text-decoration: none;
82+
}
83+
84+
a:hover {
85+
text-decoration: underline;
86+
}
87+
</style>
88+
</head>
89+
90+
<body>
91+
92+
<header>
93+
<xsl:if test="rss/channel/image/url">
94+
<img>
95+
<xsl:attribute name="src">
96+
<xsl:value-of select="rss/channel/image/url"/>
97+
</xsl:attribute>
98+
<xsl:attribute name="width">64</xsl:attribute>
99+
</img>
100+
</xsl:if>
101+
102+
<h1>
103+
<xsl:value-of select="rss/channel/title"/>
104+
</h1>
105+
106+
<p>
107+
<xsl:value-of select="rss/channel/description"/>
108+
</p>
109+
110+
<p>
111+
<a>
112+
<xsl:attribute name="href">
113+
<xsl:value-of select="rss/channel/link"/>
114+
</xsl:attribute>
115+
Visit website
116+
</a>
117+
</p>
118+
<div class="meta">
119+
120+
<xsl:if test="author">
121+
By <xsl:value-of select="author"/>
122+
</xsl:if>
123+
124+
<xsl:if test="pubDate">
125+
— <strong>Published:</strong>
126+
<xsl:value-of select="pubDate"/>
127+
</xsl:if>
128+
129+
<xsl:if test="updated">
130+
— <strong>Updated:</strong>
131+
<xsl:value-of select="updated"/>
132+
</xsl:if>
133+
134+
</div>
135+
</header>
136+
137+
<xsl:for-each select="rss/channel/item">
138+
139+
<div class="item">
140+
141+
<h2>
142+
<a>
143+
<xsl:attribute name="href">
144+
<xsl:value-of select="link"/>
145+
</xsl:attribute>
146+
<xsl:value-of select="title"/>
147+
</a>
148+
</h2>
149+
150+
<div class="meta">
151+
<xsl:if test="author">
152+
Par <xsl:value-of select="author"/>
153+
</xsl:if>
154+
<xsl:if test="pubDate">
155+
— <xsl:value-of select="pubDate"/>
156+
</xsl:if>
157+
</div>
158+
159+
<!-- Image si enclosure image/* -->
160+
<xsl:if test="enclosure[starts-with(@type,'image/')]">
161+
<img>
162+
<xsl:attribute name="src">
163+
<xsl:value-of select="enclosure/@url"/>
164+
</xsl:attribute>
165+
</img>
166+
</xsl:if>
167+
168+
<p>
169+
<xsl:value-of select="description"/>
170+
</p>
171+
172+
<div class="categories">
173+
<xsl:for-each select="category">
174+
<span>
175+
<xsl:value-of select="."/>
176+
</span>
177+
</xsl:for-each>
178+
</div>
179+
180+
</div>
181+
182+
</xsl:for-each>
183+
184+
</body>
185+
</html>
186+
</xsl:template>
187+
188+
</xsl:stylesheet>

mkdocs_rss_plugin/templates/rss.xml.jinja2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
2+
{% if feed.stylesheet is not none %}<?xml-stylesheet type="text/xsl" href="{{ feed.stylesheet }}"?>{% endif -%}
23
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
34
<channel>
45
{# Mandatory elements #}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
site_name: Test RSS Plugin - Disable stylesheet
2+
# site_description: Test RSS Plugin
3+
site_url: https://guts.github.io/mkdocs-rss-plugin
4+
5+
use_directory_urls: true
6+
7+
plugins:
8+
- rss:
9+
stylesheet: ""
10+
11+
theme:
12+
name: mkdocs

tests/test_config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def test_plugin_config_defaults(self):
8888
"rss_updated": "feed_rss_updated.xml",
8989
},
9090
"pretty_print": False,
91+
"stylesheet": "auto",
9192
"rss_feed_enabled": True,
9293
"url_parameters": None,
9394
"use_git": True,
@@ -134,6 +135,7 @@ def test_plugin_config_image(self):
134135
"rss_updated": "feed_rss_updated.xml",
135136
},
136137
"pretty_print": False,
138+
"stylesheet": "auto",
137139
"rss_feed_enabled": True,
138140
"url_parameters": None,
139141
"use_git": True,

0 commit comments

Comments
 (0)