diff --git a/src/app/browser/window/tab/item/page/content/text/markdown.rs b/src/app/browser/window/tab/item/page/content/text/markdown.rs index ebcaf16e..dd3f0cbc 100644 --- a/src/app/browser/window/tab/item/page/content/text/markdown.rs +++ b/src/app/browser/window/tab/item/page/content/text/markdown.rs @@ -4,11 +4,11 @@ mod tags; use super::{ItemAction, WindowAction}; use crate::app::browser::window::action::Position; use gtk::{ - EventControllerMotion, GestureClick, TextBuffer, TextTag, TextTagTable, TextView, - TextWindowType, UriLauncher, Window, WrapMode, + EventControllerMotion, GestureClick, TextBuffer, TextSearchFlags, TextTag, TextTagTable, + TextView, TextWindowType, UriLauncher, Window, WrapMode, gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY, BUTTON_SECONDARY, RGBA}, gio::{Cancellable, SimpleAction, SimpleActionGroup}, - glib::{Uri, uuid_string_random}, + glib::{ControlFlow, Uri, idle_add_local, uri_unescape_string, uuid_string_random}, prelude::{PopoverExt, TextBufferExt, TextTagExt, TextViewExt, WidgetExt}, }; use gutter::Gutter; @@ -308,6 +308,34 @@ impl Markdown { } }); // @TODO may be expensive for CPU, add timeout? + // Anchor auto-scroll behavior (@TODO navigate without page reload) + idle_add_local({ + let base = base.clone(); + let text_view = text_view.clone(); + move || { + if let Some(fragment) = base.fragment() { + let query = uri_unescape_string(&fragment, None::<&str>) + .unwrap_or(fragment) + .replace("-", " "); + let mut cursor = text_view.buffer().start_iter(); + while let Some((mut match_start, match_end)) = + cursor.forward_search(&query, TextSearchFlags::CASE_INSENSITIVE, None) + { + if match_start + .tags() + .iter() + .any(|t| t.name().is_some_and(|n| n.starts_with("h"))) + { + text_view.scroll_to_iter(&mut match_start, 0.0, true, 0.0, 0.0); + break; + } + cursor = match_end; + } + } + ControlFlow::Break + } + }); + Self { text_view, title } } } diff --git a/src/app/browser/window/tab/item/page/content/text/markdown/tags/header.rs b/src/app/browser/window/tab/item/page/content/text/markdown/tags/header.rs index 9de1b20c..5d558eb4 100644 --- a/src/app/browser/window/tab/item/page/content/text/markdown/tags/header.rs +++ b/src/app/browser/window/tab/item/page/content/text/markdown/tags/header.rs @@ -17,9 +17,11 @@ pub struct Header { impl Header { pub fn new() -> Self { + // * important to give the tag name here as used in the fragment search Self { h1: TextTag::builder() .foreground("#2190a4") // @TODO optional + .name("h1") .scale(1.6) .sentence(true) .weight(500) @@ -27,6 +29,7 @@ impl Header { .build(), h2: TextTag::builder() .foreground("#d56199") // @TODO optional + .name("h2") .scale(1.4) .sentence(true) .weight(400) @@ -34,6 +37,7 @@ impl Header { .build(), h3: TextTag::builder() .foreground("#c88800") // @TODO optional + .name("h3") .scale(1.2) .sentence(true) .weight(400) @@ -41,6 +45,7 @@ impl Header { .build(), h4: TextTag::builder() .foreground("#c88800") // @TODO optional + .name("h4") .scale(1.1) .sentence(true) .weight(400) @@ -48,6 +53,7 @@ impl Header { .build(), h5: TextTag::builder() .foreground("#c88800") // @TODO optional + .name("h5") .scale(1.0) .sentence(true) .weight(400) @@ -55,6 +61,7 @@ impl Header { .build(), h6: TextTag::builder() .foreground("#c88800") // @TODO optional + .name("h6") .scale(1.0) .sentence(true) .weight(300)