From 8fd52dd8a04d613f12c8b5da88f26f052a2560c8 Mon Sep 17 00:00:00 2001 From: Jeena Date: Sat, 21 Mar 2026 01:13:01 +0000 Subject: [PATCH] ui: overhaul sidebar, add content filters and state improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidebar layout: - Replace AdwNavigationSplitView with GtkPaned for a resizable sidebar with a persistent width stored in GSettings. - Apply navigation-sidebar CSS class to the content Stack only (not the ToolbarView) so both header bars share the same colour and height. - Override Adwaita's automatic paned-first-child header tint and gap via application-level CSS. - Remove the gap between the sidebar header and the first list item. - Add toggle-sidebar button and F9 shortcut; sidebar visibility and width are persisted across restarts. Loading indicator: - Replace the large AdwSpinner status page + header Stack with a small Gtk.Spinner (16×16) in the header Stack so the header height never changes during loading. Article row: - Add hexpand to title and excerpt labels so text reflows when the sidebar is resized. Content: - Inline CSS into the HTML template at load time (/*INJECT_CSS*/ placeholder) so WebKit does not need a custom URI scheme handler. - Fix max-width centering and padding for article body and header. - Fix embedded video/iframe auto-opening in browser by checking NavigationType::LinkClicked instead of is_user_gesture(). Content filters: - Add Preferences dialog with a TextView for content-rewrite rules stored in GSettings (content-filters key). - Rule format: "domain find replace [find replace …]" one per line. - Rules are applied to article HTML before display and reloaded on every refresh. Shortcuts: - Add Ctrl+W to close, Ctrl+Q to quit, F1 for keyboard shortcuts overlay, j/k and arrow-key navigation via a capture-phase controller so keys work regardless of which widget has focus. Misc: - Set window title to "FeedTheMonkey" (fixes Hyprland title bar). - Update About dialog website URL. --- build.rs | 8 + data/net.jeena.FeedTheMonkey.gschema.xml | 4 + data/resources.gresource.xml | 1 + data/ui/article_row.blp | 2 + data/ui/article_row.ui | 2 + data/ui/preferences_dialog.blp | 29 +++ data/ui/preferences_dialog.ui | 44 ++++ data/ui/shortcuts.blp | 5 + data/ui/shortcuts.ui | 6 + data/ui/window.blp | 218 +++++++++-------- data/ui/window.ui | 290 ++++++++++++----------- html/content.css | 158 +++++++----- html/content.html | 54 +++-- src/app.rs | 36 ++- src/filters.rs | 68 ++++++ src/preferences_dialog.rs | 75 ++++++ 16 files changed, 680 insertions(+), 320 deletions(-) create mode 100644 data/ui/preferences_dialog.blp create mode 100644 data/ui/preferences_dialog.ui create mode 100644 src/filters.rs create mode 100644 src/preferences_dialog.rs diff --git a/build.rs b/build.rs index 0035e54..47065f3 100644 --- a/build.rs +++ b/build.rs @@ -44,6 +44,14 @@ fn main() { let gresource_xml = data_dir.join("resources.gresource.xml"); println!("cargo:rerun-if-changed={}", gresource_xml.display()); + // Watch HTML/CSS so changes trigger a resource rebuild + let html_dir = manifest_dir.join("html"); + if let Ok(entries) = std::fs::read_dir(&html_dir) { + for entry in entries.filter_map(|e| e.ok()) { + println!("cargo:rerun-if-changed={}", entry.path().display()); + } + } + let gresource_out = out_dir.join("feedthemonkey.gresource"); let status = Command::new("glib-compile-resources") .arg(format!("--sourcedir={}", data_dir.display())) diff --git a/data/net.jeena.FeedTheMonkey.gschema.xml b/data/net.jeena.FeedTheMonkey.gschema.xml index e0790c4..16ae75d 100644 --- a/data/net.jeena.FeedTheMonkey.gschema.xml +++ b/data/net.jeena.FeedTheMonkey.gschema.xml @@ -21,5 +21,9 @@ 1.0 WebView zoom level + + '' + Content rewrite rules, one per line: domain from to [from to …] + diff --git a/data/resources.gresource.xml b/data/resources.gresource.xml index e5d363a..ef69e9b 100644 --- a/data/resources.gresource.xml +++ b/data/resources.gresource.xml @@ -5,6 +5,7 @@ ui/login_dialog.ui ui/article_row.ui ui/shortcuts.ui + ui/preferences_dialog.ui html/content.html html/content.css diff --git a/data/ui/article_row.blp b/data/ui/article_row.blp index 052b813..ea4c08d 100644 --- a/data/ui/article_row.blp +++ b/data/ui/article_row.blp @@ -27,6 +27,7 @@ template $ArticleRow : Gtk.Box { } Label title_label { + hexpand: true; xalign: 0; wrap: true; lines: 2; @@ -34,6 +35,7 @@ template $ArticleRow : Gtk.Box { } Label excerpt_label { + hexpand: true; xalign: 0; ellipsize: end; lines: 1; diff --git a/data/ui/article_row.ui b/data/ui/article_row.ui index 1218020..e159ff1 100644 --- a/data/ui/article_row.ui +++ b/data/ui/article_row.ui @@ -41,6 +41,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler. + true 0 true 2 @@ -49,6 +50,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler. + true 0 3 1 diff --git a/data/ui/preferences_dialog.blp b/data/ui/preferences_dialog.blp new file mode 100644 index 0000000..94fe343 --- /dev/null +++ b/data/ui/preferences_dialog.blp @@ -0,0 +1,29 @@ +using Gtk 4.0; +using Adw 1; + +template $PreferencesDialog : Adw.Dialog { + title: _("Preferences"); + content-width: 500; + content-height: 400; + + Adw.ToolbarView { + [top] + Adw.HeaderBar {} + + Adw.PreferencesPage { + Adw.PreferencesGroup { + title: _("Content Filters"); + description: _("One rule per line: domain find replace [find replace …]\n\nExample:\n www.imycomic.com -150x150.jpg .jpg"); + + TextView filters_text_view { + monospace: true; + wrap-mode: word; + top-margin: 8; + bottom-margin: 8; + left-margin: 8; + right-margin: 8; + } + } + } + } +} diff --git a/data/ui/preferences_dialog.ui b/data/ui/preferences_dialog.ui new file mode 100644 index 0000000..6fefaad --- /dev/null +++ b/data/ui/preferences_dialog.ui @@ -0,0 +1,44 @@ + + + + + + \ No newline at end of file diff --git a/data/ui/shortcuts.blp b/data/ui/shortcuts.blp index 94c72e8..7d76ec3 100644 --- a/data/ui/shortcuts.blp +++ b/data/ui/shortcuts.blp @@ -79,6 +79,11 @@ ShortcutsWindow help_overlay { accelerator: "0"; } + ShortcutsShortcut { + title: _("Toggle sidebar"); + accelerator: "F9"; + } + ShortcutsShortcut { title: _("Toggle fullscreen"); accelerator: "F11"; diff --git a/data/ui/shortcuts.ui b/data/ui/shortcuts.ui index 5392972..e51f173 100644 --- a/data/ui/shortcuts.ui +++ b/data/ui/shortcuts.ui @@ -97,6 +97,12 @@ corresponding .blp file and regenerate this file with blueprint-compiler. <Control>0 + + + Toggle sidebar + F9 + + Toggle fullscreen diff --git a/data/ui/window.blp b/data/ui/window.blp index 9ded4f7..6c0192d 100644 --- a/data/ui/window.blp +++ b/data/ui/window.blp @@ -7,121 +7,140 @@ template $FeedTheMonkeyWindow : Adw.ApplicationWindow { default-height: 600; Adw.ToastOverlay toast_overlay { - Adw.NavigationSplitView split_view { - sidebar: Adw.NavigationPage { - title: _("FeedTheMonkey"); + Paned paned { + focusable: false; + shrink-start-child: false; + resize-start-child: false; - Adw.ToolbarView { - [top] - Adw.HeaderBar { - [start] - Stack refresh_stack { - StackPage { - name: "button"; - child: Button refresh_button { - icon-name: "view-refresh-symbolic"; - tooltip-text: _("Refresh"); - action-name: "win.reload"; - }; - } + start-child: Adw.ToolbarView sidebar_toolbar { + top-bar-style: raised; - StackPage { - name: "spinner"; - child: Adw.Spinner {}; - } + [top] + Adw.HeaderBar { + show-start-title-buttons: false; + show-end-title-buttons: false; + + title-widget: Box {}; + + [start] + Stack refresh_stack { + StackPage { + name: "button"; + child: Button refresh_button { + icon-name: "view-refresh-symbolic"; + tooltip-text: _("Refresh"); + action-name: "win.reload"; + }; } - - [end] - MenuButton menu_button { - icon-name: "open-menu-symbolic"; - primary: true; - menu-model: primary_menu; + StackPage { + name: "spinner"; + child: Spinner { + spinning: true; + width-request: 16; + height-request: 16; + }; } } - Stack sidebar_content { - StackPage { - name: "placeholder"; - child: Adw.StatusPage { - icon-name: "rss-symbolic"; - title: _("FeedTheMonkey"); - description: _("Log in to load your articles"); - }; - } + [end] + MenuButton menu_button { + icon-name: "open-menu-symbolic"; + primary: true; + menu-model: primary_menu; + } + } - StackPage { - name: "loading"; - child: Adw.StatusPage { - paintable: Adw.SpinnerPaintable {}; - title: _("Loading…"); - }; - } + Stack sidebar_content { + styles ["sidebar-content"] - StackPage { - name: "empty"; - child: Adw.StatusPage { - icon-name: "rss-symbolic"; - title: _("No Unread Articles"); - }; - } + StackPage { + name: "placeholder"; + child: Adw.StatusPage { + icon-name: "rss-symbolic"; + title: _("FeedTheMonkey"); + description: _("Log in to load your articles"); + }; + } - StackPage { - name: "error"; - child: Adw.StatusPage error_status { - icon-name: "network-error-symbolic"; - title: _("Could Not Load Articles"); + StackPage { + name: "loading"; + child: Adw.StatusPage { + title: _("Loading…"); + }; + } - Button { - label: _("Try Again"); - halign: center; - action-name: "win.reload"; - styles ["pill", "suggested-action"] - } - }; - } + StackPage { + name: "empty"; + child: Adw.StatusPage { + icon-name: "rss-symbolic"; + title: _("No Unread Articles"); + }; + } - StackPage { - name: "list"; - child: ScrolledWindow { - hscrollbar-policy: never; - ListView article_list_view { - single-click-activate: true; - } - }; - } + StackPage { + name: "error"; + child: Adw.StatusPage error_status { + icon-name: "network-error-symbolic"; + title: _("Could Not Load Articles"); + + Button { + label: _("Try Again"); + halign: center; + action-name: "win.reload"; + styles ["pill", "suggested-action"] + } + }; + } + + StackPage { + name: "list"; + child: ScrolledWindow { + hscrollbar-policy: never; + ListView article_list_view { + single-click-activate: false; + show-separators: true; + } + }; } } }; - content: Adw.NavigationPage content_page { - title: _("FeedTheMonkey"); + end-child: Adw.ToolbarView { + top-bar-style: raised; - Adw.ToolbarView { - top-bar-style: raised; - - [top] - Adw.HeaderBar { - [end] - MenuButton article_menu_button { - icon-name: "view-more-symbolic"; - menu-model: article_menu; - visible: false; - } + [top] + Adw.HeaderBar { + [start] + Button toggle_sidebar_button { + icon-name: "sidebar-show-symbolic"; + tooltip-text: _("Toggle Sidebar"); + action-name: "win.toggle-sidebar"; } - Stack content_stack { - StackPage { - name: "empty"; - child: Adw.StatusPage { - icon-name: "document-open-symbolic"; - title: _("No Article Selected"); - }; - } + title-widget: Adw.WindowTitle { + title: _("FeedTheMonkey"); + }; - StackPage { - name: "webview"; - child: WebKit.WebView web_view {}; - } + [end] + MenuButton article_menu_button { + icon-name: "view-more-symbolic"; + menu-model: article_menu; + visible: false; + } + } + + Stack content_stack { + StackPage { + name: "empty"; + child: Adw.StatusPage { + icon-name: "document-open-symbolic"; + title: _("No Article Selected"); + }; + } + + StackPage { + name: "webview"; + child: WebKit.WebView web_view {}; } } }; @@ -137,6 +156,13 @@ menu primary_menu { } } + section { + item { + label: _("Preferences"); + action: "win.preferences"; + } + } + section { item { label: _("Keyboard Shortcuts"); diff --git a/data/ui/window.ui b/data/ui/window.ui index 5ee9da1..e9cd9cc 100644 --- a/data/ui/window.ui +++ b/data/ui/window.ui @@ -12,166 +12,182 @@ corresponding .blp file and regenerate this file with blueprint-compiler. - - - - FeedTheMonkey - - - - - - - - - button - - - view-refresh-symbolic - Refresh - win.reload - - + + false + false + false + + + 1 + + + false + false + + + + + + + + button + + + view-refresh-symbolic + Refresh + win.reload - - - - spinner - - - - - + - - - open-menu-symbolic - true - primary_menu + + + spinner + + + true + 16 + 16 + + + + + open-menu-symbolic + true + primary_menu + + + + + + + - - - - placeholder - - - rss-symbolic - FeedTheMonkey - Log in to load your articles - - + + placeholder + + + rss-symbolic + FeedTheMonkey + Log in to load your articles - - - - loading - - - - - - Loading… - - + + + + + + loading + + + Loading… - - - - empty - - - rss-symbolic - No Unread Articles - - + + + + + + empty + + + rss-symbolic + No Unread Articles - - - - error - - - network-error-symbolic - Could Not Load Articles - - - Try Again - 3 - win.reload - - - + + + + + + error + + + network-error-symbolic + Could Not Load Articles + + + Try Again + 3 + win.reload + - + - - - - list - - - 2 - - - true - - + + + + + + list + + + 2 + + + false + true - + - + - - - FeedTheMonkey + + + 1 + + + + + sidebar-show-symbolic + Toggle Sidebar + win.toggle-sidebar + + + + + FeedTheMonkey + + + + + view-more-symbolic + article_menu + false + + + + - - 1 - - - - - view-more-symbolic - article_menu - false + + + + empty + + + document-open-symbolic + No Article Selected - + - - - - empty - - - document-open-symbolic - No Article Selected - - - - - - - webview - - - - - + + webview + + + @@ -190,6 +206,12 @@ corresponding .blp file and regenerate this file with blueprint-compiler. win.logout +
+ + Preferences + win.preferences + +
Keyboard Shortcuts diff --git a/html/content.css b/html/content.css index c3f979b..4fc396b 100644 --- a/html/content.css +++ b/html/content.css @@ -1,101 +1,131 @@ -* { - box-sizing: border-box; +/* CSS custom properties are set from Rust via AdwStyleManager. + The :root defaults below act as a light-mode fallback only. */ + +:root { + --bg: #ffffff; + --fg: #1a1a1a; + --fg-dim: rgba(0,0,0,0.55); + --border: rgba(0,0,0,0.12); + --header-bg: #f6f5f4; + --link: #1c71d8; + --code-bg: rgba(0,0,0,0.06); + --blockquote-border: rgba(0,0,0,0.2); + --font: sans-serif; + --font-size: 15px; } -body { - font-family: sans-serif; +:root[data-dark="1"] { + --bg: #1e1e1e; + --fg: rgba(255,255,255,0.87); + --fg-dim: rgba(255,255,255,0.5); + --border: rgba(255,255,255,0.12); + --header-bg: #242424; + --link: #78aeed; + --code-bg: rgba(255,255,255,0.06); + --blockquote-border: rgba(255,255,255,0.2); +} + +* { box-sizing: border-box; } + +html, body { margin: 0; padding: 0; - line-height: 1.6; - color: #222; - background: #fff; + background: var(--bg); + color: var(--fg); + font-family: var(--font); + font-size: var(--font-size); + word-wrap: break-word; } -@media (prefers-color-scheme: dark) { - body { - color: #ddd; - background: #1e1e1e; - } - a { - color: #78aeed; - } - img { - opacity: 0.85; - } +a { + color: var(--link); + text-decoration: none; } -#header { +article a { + text-decoration: underline; +} + +header { padding: 1.5em 2em 1em; - border-bottom: 1px solid rgba(0,0,0,0.1); - margin-bottom: 1em; + background: var(--header-bg); + border-bottom: 1px solid var(--border); } -@media (prefers-color-scheme: dark) { - #header { - border-bottom-color: rgba(255,255,255,0.1); - } +header > .inner, +article { + max-width: 720px; + margin-left: auto; + margin-right: auto; } -#feed-title { - font-size: 0.8em; - opacity: 0.6; - margin-bottom: 0.25em; - text-transform: uppercase; - letter-spacing: 0.05em; +header > .inner { + padding: 0 2em; } -#title { - font-size: 1.5em; - margin: 0 0 0.5em; +header h1 { + font-size: 1.3em; + margin: 0.2em 0 0.4em; + padding: 0; line-height: 1.3; } -#meta { - font-size: 0.85em; - opacity: 0.6; - margin-bottom: 0.5em; +header h1 a { + color: var(--fg); } -#meta span + span::before { - content: ' · '; -} - -#link { +header p { + color: var(--fg-dim); + margin: 0; + padding: 0; font-size: 0.85em; } -#content { - padding: 0 2em 2em; - max-width: 800px; +article { + line-height: 1.6; + padding: 1.5em 2em 2em; } -#content img { +img { max-width: 100%; height: auto; } -#content pre { - overflow-x: auto; - background: rgba(0,0,0,0.05); +div > a:only-child img, +figure > a:only-child img, +p > a:only-child img, +figure > img:only-child, +div > img:only-child, +p > img:only-child { + display: block; + margin: 1em auto; + float: none !important; +} + +pre { + overflow: auto; + background: var(--code-bg); padding: 1em; - border-radius: 4px; + border-radius: 6px; + font-size: 0.9em; } -@media (prefers-color-scheme: dark) { - #content pre { - background: rgba(255,255,255,0.05); - } +code { + background: var(--code-bg); + padding: 0.15em 0.35em; + border-radius: 3px; + font-size: 0.9em; } -#content blockquote { - border-left: 3px solid rgba(0,0,0,0.2); +pre code { + background: none; + padding: 0; +} + +blockquote { + border-left: 3px solid var(--blockquote-border); margin-left: 0; padding-left: 1em; - opacity: 0.8; -} - -@media (prefers-color-scheme: dark) { - #content blockquote { - border-left-color: rgba(255,255,255,0.2); - } + color: var(--fg-dim); + font-style: italic; } diff --git a/html/content.html b/html/content.html index f817038..82610c9 100644 --- a/html/content.html +++ b/html/content.html @@ -2,35 +2,47 @@ + FeedTheMonkey - + -