added favicon, improve layout

This commit is contained in:
2026-06-07 20:18:31 +02:00
parent b865fe982e
commit 39f08c7218
5 changed files with 86 additions and 7 deletions
+73 -1
View File
@@ -38,6 +38,28 @@ fn image_src_is_resolvable(element: &scraper::ElementRef) -> bool {
}
}
fn escape_html_attr(value: &str) -> String {
value
.replace('&', "&")
.replace('"', """)
.replace('<', "&lt;")
.replace('>', "&gt;")
}
// Some feeds (e.g. Deutsche Welle) don't embed an <img> in the item content at
// all — they carry the article image as an RSS <enclosure> instead. Build an
// <img> tag from it so those feeds get a preview image too.
fn enclosure_image_html(item: &Item) -> Option<String> {
let enclosure = item.enclosure()?;
if !enclosure.mime_type().to_lowercase().starts_with("image/") {
return None;
}
Some(format!(
r#"<img src="{}">"#,
escape_html_attr(enclosure.url())
))
}
fn create_feed_item(item: Item, feed: &Feed, connection: &mut PgConnection) {
let item_title = item.title.clone().unwrap();
log::info!("Create feed item: {}", item_title);
@@ -48,10 +70,18 @@ fn create_feed_item(item: Item, feed: &Feed, connection: &mut PgConnection) {
let mut content = "".to_string();
let selector_img = Selector::parse("img").unwrap();
if let Some(image) = frag.select(&selector_img).find(image_src_is_resolvable) {
match frag.select(&selector_img).find(image_src_is_resolvable) {
Some(image) => {
content.push_str(&image.html());
content.push_str("<br>");
}
None => {
if let Some(image_html) = enclosure_image_html(&item) {
content.push_str(&image_html);
content.push_str("<br>");
}
}
}
for node in frag.tree.nodes() {
if let scraper::node::Node::Text(text) = node.value() {
@@ -176,6 +206,48 @@ mod tests {
assert!(html.select(&selector).find(image_src_is_resolvable).is_none());
}
#[test]
fn enclosure_image_html_builds_img_for_image_enclosures() {
let mut item = Item::default();
item.set_enclosure(rss::Enclosure {
url: "https://static.dw.com/image/73880499_302.jpg".to_string(),
length: "2000".to_string(),
mime_type: "image/jpeg".to_string(),
});
assert_eq!(
Some(r#"<img src="https://static.dw.com/image/73880499_302.jpg">"#.to_string()),
enclosure_image_html(&item)
);
}
#[test]
fn enclosure_image_html_ignores_non_image_enclosures() {
let mut item = Item::default();
item.set_enclosure(rss::Enclosure {
url: "https://example.test/episode.mp3".to_string(),
length: "2000".to_string(),
mime_type: "audio/mpeg".to_string(),
});
assert_eq!(None, enclosure_image_html(&item));
}
#[test]
fn enclosure_image_html_escapes_url_attribute() {
let mut item = Item::default();
item.set_enclosure(rss::Enclosure {
url: "https://example.test/img.jpg?a=1&b=\"x\"".to_string(),
length: "2000".to_string(),
mime_type: "image/jpeg".to_string(),
});
assert_eq!(
Some(r#"<img src="https://example.test/img.jpg?a=1&amp;b=&quot;x&quot;">"#.to_string()),
enclosure_image_html(&item)
);
}
#[actix_web::test]
async fn create_feed_item_does_not_duplicate_existing_items() {
let mut connection = establish_connection();
+2 -1
View File
@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="alternate icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RSS-Reader</title>
</head>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="14" fill="#1a8f5e"/>
<circle cx="20" cy="44" r="6" fill="#ffffff"/>
<path d="M14 28a22 22 0 0 1 22 22h-8a14 14 0 0 0-14-14z" fill="#ffffff"/>
<path d="M14 14a36 36 0 0 1 36 36h-8a28 28 0 0 0-28-28z" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 325 B

+3 -3
View File
@@ -95,7 +95,7 @@ a,
.feed-content {
font-family: Georgia, 'Times New Roman', Times, serif;
font-size: clamp(1rem, 3.5vw, 1.25rem);
padding: 1em;
padding: 0 1em 1em;
overflow-wrap: break-word;
}
@@ -105,11 +105,11 @@ a,
}
.feed-content p {
padding: 1em;
padding: 0.5em 0;
}
.feed-content h3 {
padding: 1em;
padding: 0.5em 0;
font-size: clamp(1rem, 3vw, 1.3rem);
font-weight: bold;
}