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) {
|
fn create_feed_item(item: Item, feed: &Feed, connection: &mut PgConnection) {
|
||||||
let item_title = item.title.clone().unwrap();
|
let item_title = item.title.clone().unwrap();
|
||||||
log::info!("Create feed item: {}", item_title);
|
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 mut content = "".to_string();
|
||||||
|
|
||||||
let selector_img = Selector::parse("img").unwrap();
|
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(&image.html());
|
||||||
content.push_str("<br>");
|
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() {
|
for node in frag.tree.nodes() {
|
||||||
if let scraper::node::Node::Text(text) = node.value() {
|
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());
|
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]
|
#[actix_web::test]
|
||||||
async fn create_feed_item_does_not_duplicate_existing_items() {
|
async fn create_feed_item_does_not_duplicate_existing_items() {
|
||||||
let mut connection = establish_connection();
|
let mut connection = establish_connection();
|
||||||
|
|||||||
+2
-1
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<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">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>RSS-Reader</title>
|
<title>RSS-Reader</title>
|
||||||
</head>
|
</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 {
|
.feed-content {
|
||||||
font-family: Georgia, 'Times New Roman', Times, serif;
|
font-family: Georgia, 'Times New Roman', Times, serif;
|
||||||
font-size: clamp(1rem, 3.5vw, 1.25rem);
|
font-size: clamp(1rem, 3.5vw, 1.25rem);
|
||||||
padding: 1em;
|
padding: 0 1em 1em;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,11 +105,11 @@ a,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.feed-content p {
|
.feed-content p {
|
||||||
padding: 1em;
|
padding: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feed-content h3 {
|
.feed-content h3 {
|
||||||
padding: 1em;
|
padding: 0.5em 0;
|
||||||
font-size: clamp(1rem, 3vw, 1.3rem);
|
font-size: clamp(1rem, 3vw, 1.3rem);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user