From 0820ce6ef72d72701864e4a23f1f963cbbca14f6 Mon Sep 17 00:00:00 2001 From: mace Date: Fri, 12 Jun 2026 10:57:48 +0200 Subject: [PATCH] fix some frontend issues --- src/reader/sync.rs | 77 +++++++++ vue/src/assets/base.css | 4 + vue/src/components/AppNav.vue | 20 ++- vue/src/components/RssFeeds.vue | 274 ++++++++++++++++++++++++++++++-- vue/src/composables/useFeeds.js | 31 ++-- 5 files changed, 383 insertions(+), 23 deletions(-) diff --git a/src/reader/sync.rs b/src/reader/sync.rs index 9a8bac8..56b79c7 100644 --- a/src/reader/sync.rs +++ b/src/reader/sync.rs @@ -93,6 +93,17 @@ fn create_feed_item(item: Item, feed: &Feed, connection: &mut PgConnection) -> a let frag = Html::parse_fragment(base_content); let mut content = "".to_string(); + // Some feeds (e.g. Stuttgarter Nachrichten) embed a social-sharing widget + // (WhatsApp/Email/Facebook/... links plus a "Link kopiert" tooltip) in the + // article content. It's not part of the article and isn't present in the + // scraped/readable edition either, so skip its text when flattening below. + let selector_social_bar = + Selector::parse("#article-social-bar").expect("\"#article-social-bar\" is a valid CSS selector"); + let excluded_node_ids: std::collections::HashSet<_> = frag + .select(&selector_social_bar) + .flat_map(|el| el.descendants().map(|node| node.id())) + .collect(); + let selector_img = Selector::parse("img").expect("\"img\" is a valid CSS selector"); match frag.select(&selector_img).find(image_src_is_resolvable) { Some(image) => { @@ -108,6 +119,9 @@ fn create_feed_item(item: Item, feed: &Feed, connection: &mut PgConnection) -> a } for node in frag.tree.nodes() { + if excluded_node_ids.contains(&node.id()) { + continue; + } if let scraper::node::Node::Text(text) = node.value() { content.push_str(&text.text); } @@ -382,4 +396,67 @@ mod tests { .execute(&mut connection) .ok(); } + + #[actix_web::test] + async fn create_feed_item_strips_social_sharing_widget() { + let mut connection = establish_connection(); + let suffix = unique_suffix(); + + let new_user = NewUser::new( + format!("social_bar_test_{suffix}"), + format!("social_bar_{suffix}@example.test"), + "secret".to_string(), + ) + .unwrap(); + let user: User = diesel::insert_into(users::table) + .values(&new_user) + .get_result(&mut connection) + .unwrap(); + + let new_feed = NewFeed::new( + format!("Social bar test feed {suffix}"), + format!("https://example.test/feed/{suffix}"), + user.id, + ); + let feed: Feed = diesel::insert_into(feed::table) + .values(&new_feed) + .get_result(&mut connection) + .unwrap(); + + let mut item = Item::default(); + item.set_title(Some(format!("Social bar article {suffix}"))); + item.set_link(Some(format!("https://example.test/article/{suffix}"))); + item.set_content(Some( + r#"

Article text

+
+ +
"# + .to_string(), + )); + + create_feed_item(item, &feed, &mut connection).unwrap(); + + let items: Vec = feed_item::table + .filter(feed_id.eq(feed.id)) + .load(&mut connection) + .unwrap(); + assert_eq!(1, items.len()); + assert!(items[0].content.contains("Article text")); + assert!(!items[0].content.contains("Link kopiert")); + + diesel::delete(feed_item::table.filter(feed_id.eq(feed.id))) + .execute(&mut connection) + .ok(); + diesel::delete(feed::table.filter(feed::id.eq(feed.id))) + .execute(&mut connection) + .ok(); + diesel::delete(users::table.filter(users::id.eq(user.id))) + .execute(&mut connection) + .ok(); + } } diff --git a/vue/src/assets/base.css b/vue/src/assets/base.css index d79ce9b..90ce73d 100644 --- a/vue/src/assets/base.css +++ b/vue/src/assets/base.css @@ -70,6 +70,10 @@ body { min-height: 100vh; + /* Full-bleed article images use a `100vw`-based breakout, which can be + wider than the visible content area (scrollbar) and would otherwise + introduce a horizontal scrollbar. */ + overflow-x: hidden; color: var(--color-text); background: var(--color-background); transition: color 0.5s, background-color 0.5s; diff --git a/vue/src/components/AppNav.vue b/vue/src/components/AppNav.vue index 27ff530..5be2aa9 100644 --- a/vue/src/components/AppNav.vue +++ b/vue/src/components/AppNav.vue @@ -1,12 +1,26 @@