added favicon, improve layout
This commit is contained in:
+73
-1
@@ -38,6 +38,28 @@ fn image_src_is_resolvable(element: &scraper::ElementRef) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_html_attr(value: &str) -> String {
|
||||
value
|
||||
.replace('&', "&")
|
||||
.replace('"', """)
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
}
|
||||
|
||||
// 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&b="x"">"#.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
@@ -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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user