diff --git a/data/ui/article_row.blp b/data/ui/article_row.blp index 07c4cbe..ea4c08d 100644 --- a/data/ui/article_row.blp +++ b/data/ui/article_row.blp @@ -32,7 +32,6 @@ template $ArticleRow : Gtk.Box { wrap: true; lines: 2; ellipsize: end; - styles ["article-title"] } Label excerpt_label { diff --git a/data/ui/article_row.ui b/data/ui/article_row.ui index d5eeab9..e159ff1 100644 --- a/data/ui/article_row.ui +++ b/data/ui/article_row.ui @@ -46,9 +46,6 @@ corresponding .blp file and regenerate this file with blueprint-compiler. true 2 3 - diff --git a/src/api.rs b/src/api.rs index c3db913..d683e8f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -148,7 +148,7 @@ impl Api { Ok(stream.items.into_iter().map(|item| { let unread = !item.categories.as_deref().unwrap_or_default() .iter() - .any(|c| c == "user/-/state/com.google/read"); + .any(|c| c.contains("user/-/state/com.google/read")); let content = item.summary .as_ref() .and_then(|s| s.content.clone()) @@ -249,20 +249,9 @@ fn plain_text_excerpt(html: &str, max_chars: usize) -> String { } } let collapsed: String = out.split_whitespace().collect::>().join(" "); - let decoded = decode_html_entities(&collapsed); - if decoded.chars().count() <= max_chars { - decoded + if collapsed.chars().count() <= max_chars { + collapsed } else { - decoded.chars().take(max_chars).collect::() + "…" + collapsed.chars().take(max_chars).collect::() + "…" } } - -fn decode_html_entities(s: &str) -> String { - s.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace(""", "\"") - .replace("'", "'") - .replace("'", "'") - .replace(" ", " ") -} diff --git a/src/app.rs b/src/app.rs index 8e966e9..6e287f0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -71,8 +71,8 @@ mod imp { .sidebar-content row:selected { background-color: alpha(@window_fg_color, 0.22); } - .article-title { - font-size: 1.05em; + .unread-title { + font-weight: bold; }" ); gtk4::style_context_add_provider_for_display( diff --git a/src/article_row.rs b/src/article_row.rs index bb239b2..d8877ce 100644 --- a/src/article_row.rs +++ b/src/article_row.rs @@ -44,7 +44,6 @@ mod imp { pub bindings: RefCell>, pub unread_handler: RefCell>, - pub context_menu: std::cell::OnceCell, } #[glib::object_subclass] @@ -62,98 +61,32 @@ mod imp { } } - impl ObjectImpl for ArticleRow { - fn constructed(&self) { - self.parent_constructed(); - self.setup_context_menu(); - } - - fn dispose(&self) { - if let Some(popover) = self.context_menu.get() { - popover.unparent(); - } - } - } + impl ObjectImpl for ArticleRow {} impl WidgetImpl for ArticleRow {} impl BoxImpl for ArticleRow {} impl ArticleRow { - fn setup_context_menu(&self) { - let button = gtk4::Button::with_label("Mark as Unread"); - button.set_has_frame(false); - - let popover = gtk4::Popover::new(); - popover.set_child(Some(&button)); - popover.set_parent(&*self.obj()); - self.context_menu.set(popover.clone()).ok(); - - // Close popover and activate action when button is clicked. - let imp_weak = self.downgrade(); - let popover_weak = popover.downgrade(); - button.connect_clicked(move |_| { - if let Some(popover) = popover_weak.upgrade() { - popover.popdown(); - } - let Some(imp) = imp_weak.upgrade() else { return }; - let handler = imp.unread_handler.borrow(); - let Some((obj, _)) = handler.as_ref() else { return }; - let article_id = obj.article().id.clone(); - drop(handler); - imp.obj() - .activate_action( - "win.mark-article-unread", - Some(&article_id.to_variant()), - ) - .ok(); - }); - - // Right-click gesture to show the popover at cursor position. - let gesture = gtk4::GestureClick::new(); - gesture.set_button(3); - let popover_weak2 = popover.downgrade(); - gesture.connect_pressed(move |gesture, _, x, y| { - gesture.set_state(gtk4::EventSequenceState::Claimed); - let Some(popover) = popover_weak2.upgrade() else { return }; - popover.set_pointing_to(Some(>k4::gdk::Rectangle::new( - x as i32, - y as i32, - 1, - 1, - ))); - popover.popup(); - }); - self.obj().add_controller(gesture); - } - pub fn bind(&self, obj: &ArticleObject) { let article = obj.article(); + self.feed_title_label.set_text(&article.feed_title); + self.title_label.set_text(&article.title); self.excerpt_label.set_text(&article.excerpt); self.date_label.set_text(&relative_time(article.published)); - // Set initial bold state directly (using Pango markup to avoid - // CSS specificity issues with the zoom font-size provider). - let escaped = glib::markup_escape_text(&article.title); - if article.unread { - self.title_label.remove_css_class("dim-label"); - self.title_label.set_markup(&format!("{escaped}")); - } else { - self.title_label.add_css_class("dim-label"); - self.title_label.set_markup(&escaped); - } + self.update_read_style(article.unread); drop(article); - // Connect handler for future unread state changes. + // Watch for unread state changes let title_label = self.title_label.clone(); let id = obj.connect_notify_local(Some("unread"), move |obj, _| { - let article = obj.article(); - let escaped = glib::markup_escape_text(&article.title); - if article.unread { + let unread = obj.article().unread; + if unread { title_label.remove_css_class("dim-label"); - title_label.set_markup(&format!("{escaped}")); + title_label.add_css_class("unread-title"); } else { title_label.add_css_class("dim-label"); - title_label.set_markup(&escaped); + title_label.remove_css_class("unread-title"); } }); *self.unread_handler.borrow_mut() = Some((obj.clone(), id)); @@ -167,6 +100,16 @@ mod imp { b.unbind(); } } + + fn update_read_style(&self, unread: bool) { + if unread { + self.title_label.remove_css_class("dim-label"); + self.title_label.add_css_class("unread-title"); + } else { + self.title_label.add_css_class("dim-label"); + self.title_label.remove_css_class("unread-title"); + } + } } } diff --git a/src/window.rs b/src/window.rs index c3c0699..91181c8 100644 --- a/src/window.rs +++ b/src/window.rs @@ -77,11 +77,6 @@ pub mod imp { klass.install_action("win.reload", None, |win, _, _| win.imp().do_reload()); klass.install_action("win.logout", None, |win, _, _| win.imp().do_logout()); klass.install_action("win.mark-unread", None, |win, _, _| win.imp().do_mark_unread()); - klass.install_action("win.mark-article-unread", Some(glib::VariantTy::STRING), |win, _, param| { - if let Some(id) = param.and_then(|p| p.get::()) { - win.imp().do_mark_article_unread(id); - } - }); klass.install_action("win.open-in-browser", None, |win, _, _| { win.imp().do_open_in_browser() }); @@ -106,14 +101,6 @@ pub mod imp { }); klass.install_action("win.preferences", None, |win, _, _| { let dialog = crate::preferences_dialog::PreferencesDialog::new(); - let win_weak = win.downgrade(); - dialog.connect_closed(move |_| { - if let Some(win) = win_weak.upgrade() { - let imp = win.imp(); - *imp.filter_rules.borrow_mut() = crate::filters::load_rules(); - imp.reload_current_article(); - } - }); dialog.present(Some(win.upcast_ref::())); }); } @@ -247,21 +234,6 @@ pub mod imp { obj.set_unread(false); } - fn reload_current_article(&self) { - let id = self.current_article_id.borrow().clone(); - let Some(id) = id else { return }; - if let Some(store) = self.article_store.borrow().as_ref() { - for i in 0..store.n_items() { - if let Some(obj) = store.item(i).and_downcast::() { - if obj.article().id == id { - self.load_article_in_webview(&obj.article().clone()); - break; - } - } - } - } - } - fn load_article_in_webview(&self, article: &crate::model::Article) { let rules = self.filter_rules.borrow(); let content = crate::filters::apply(&rules, &article.id, &article.link, &article.content); @@ -755,11 +727,8 @@ pub mod imp { fn do_mark_unread(&self) { let id = self.current_article_id.borrow().clone(); let Some(id) = id else { return }; - self.do_mark_article_unread(id); - } - fn do_mark_article_unread(&self, id: String) { - // Find the ArticleObject in the store and set unread=true. + // Find the ArticleObject in the store and set unread=true if let Some(store) = self.article_store.borrow().as_ref() { for i in 0..store.n_items() { if let Some(obj) = store.item(i).and_downcast::() { @@ -770,11 +739,7 @@ pub mod imp { } } - // If this is the currently displayed article, guard against it - // being immediately re-marked read when the selection fires. - if self.current_article_id.borrow().as_deref() == Some(&*id) { - *self.mark_unread_guard.borrow_mut() = true; - } + *self.mark_unread_guard.borrow_mut() = true; let api = self.api.borrow().clone(); let wt = self.write_token.borrow().clone(); @@ -843,7 +808,7 @@ pub mod imp { fn update_sidebar_zoom(&self, level: f64) { if let Some(css) = self.sidebar_zoom_css.get() { css.load_from_string(&format!( - ".sidebar-content {{ font-size: {level}em; }}" + ".sidebar-content label {{ font-size: {level}em; }}" )); } }