diff --git a/README.md b/README.md index 60e7e7fc..ee2b2801 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ GTK 4 / Libadwaita client written in Rust * [ ] History * [ ] Browser window * [x] Page navigation - * [ ] Recently visited - * [x] Recently closed tabs + * [x] Recently visited + * [x] Recently closed * [ ] Proxy * [ ] Session * [ ] Window diff --git a/src/app/browser/window/header/bar/menu.rs b/src/app/browser/window/header/bar/menu.rs index 52e53008..d09ffe6f 100644 --- a/src/app/browser/window/header/bar/menu.rs +++ b/src/app/browser/window/header/bar/menu.rs @@ -11,6 +11,7 @@ use std::rc::Rc; const LABEL_MAX_LENGTH: usize = 32; const RECENT_BOOKMARKS: usize = 50; const RECENTLY_CLOSED: usize = 50; +const RECENT_REQUESTS: usize = 50; pub struct Menu { pub menu_button: MenuButton, @@ -137,8 +138,13 @@ impl Menu { // Main > History > Recently closed // * menu items dynamically generated using profile memory pool and `set_create_popup_func` - let main_history_closed = gio::Menu::new(); - main_history.append_submenu(Some("Closed tabs"), &main_history_closed); + let main_history_tab = gio::Menu::new(); + main_history.append_submenu(Some("Recently closed"), &main_history_tab); + + // Main > History > Recent requests + // * menu items dynamically generated using profile memory pool and `set_create_popup_func` + let main_history_request = gio::Menu::new(); + main_history.append_section(None, &main_history_request); main.append_submenu(Some("History"), &main_history); @@ -204,8 +210,8 @@ impl Menu { // @TODO // } - // History - main_history_closed.remove_all(); + // Recently closed history + main_history_tab.remove_all(); for item in profile.history.memory.tab.recent(RECENTLY_CLOSED) { let item_request = item.page.navigation.request.widget.entry.text(); // @TODO restore entire `Item` let menu_item = gio::MenuItem::new(Some(&label(&item_request, LABEL_MAX_LENGTH)), None); @@ -215,7 +221,20 @@ impl Menu { window_action.open.simple_action.name() )), Some(&item_request.to_variant())); - main_history_closed.append_item(&menu_item); + main_history_tab.append_item(&menu_item); + } + + // Recently visited history + main_history_request.remove_all(); + for request in profile.history.memory.request.recent(RECENT_REQUESTS) { + let menu_item = gio::MenuItem::new(Some(&label(&request, LABEL_MAX_LENGTH)), None); + menu_item.set_action_and_target_value(Some(&format!( + "{}.{}", + window_action.id, + window_action.open.simple_action.name() + )), Some(&request.to_variant())); + + main_history_request.append_item(&menu_item); } } }); diff --git a/src/app/browser/window/tab/item/page.rs b/src/app/browser/window/tab/item/page.rs index 516c9d87..2564cbed 100644 --- a/src/app/browser/window/tab/item/page.rs +++ b/src/app/browser/window/tab/item/page.rs @@ -28,7 +28,7 @@ use gtk::{ gdk::Texture, gdk_pixbuf::Pixbuf, gio::SocketClientEvent, - glib::{gformat, GString, Priority, Uri, UriFlags, UriHideFlags}, + glib::{gformat, DateTime, GString, Priority, Uri, UriFlags, UriHideFlags}, prelude::{EditableExt, FileExt, SocketClientExt}, }; use sqlite::Transaction; @@ -234,7 +234,7 @@ impl Page { scheme => { // Add history record if is_history { - snap_history(self.navigation.clone()); + snap_history(&self.profile, &self.navigation); } // Update widget @@ -322,7 +322,7 @@ impl Page { self.navigation.restore(transaction, &record.id)?; // Make initial page history snap using `navigation` values restored // * just to have back/forward navigation ability - snap_history(self.navigation.clone()); + snap_history(&self.profile, &self.navigation); } } Err(e) => return Err(e.to_string()), @@ -391,11 +391,12 @@ impl Page { let browser_action = self.browser_action.clone(); let cancellable = self.client.cancellable(); let content = self.content.clone(); - let search = self.search.clone(); let id = self.id.clone(); let input = self.input.clone(); let meta = self.meta.clone(); let navigation = self.navigation.clone(); + let profile = self.profile.clone(); + let search = self.search.clone(); let tab_action = self.tab_action.clone(); let window_action = self.window_action.clone(); @@ -477,7 +478,7 @@ impl Page { // https://geminiprotocol.net/docs/protocol-specification.gmi#status-20 response::meta::Status::Success => { if is_history { - snap_history(navigation.clone()); + snap_history(&profile, &navigation); } if is_download { // Init download widget @@ -805,7 +806,7 @@ impl Page { response::meta::Status::CertificateInvalid => { // Add history record if is_history { - snap_history(navigation.clone()); + snap_history(&profile, &navigation); } // Update widget @@ -830,7 +831,7 @@ impl Page { _ => { // Add history record if is_history { - snap_history(navigation.clone()); + snap_history(&profile, &navigation); } // Update widget @@ -853,7 +854,7 @@ impl Page { Err(e) => { // Add history record if is_history { - snap_history(navigation.clone()); + snap_history(&profile, &navigation); } // Update widget @@ -920,15 +921,21 @@ fn is_external_uri(subject: &Uri, base: &Uri) -> bool { /// Make new history record for given `navigation` object /// * applies on shared conditions match only -fn snap_history(navigation: Rc) { +fn snap_history(profile: &Profile, navigation: &Navigation) { let request = navigation.request.widget.entry.text(); - // Apply additional filters + // Add new record into the global memory index (used in global menu) + profile + .history + .memory + .request + .set(request.clone(), DateTime::now_local().unwrap().to_unix()); + + // Add new record into the page navigation history if match navigation.history.current() { - Some(current) => current != request, + Some(current) => current != request, // apply additional filters None => true, } { - // Add new record match conditions navigation.history.add(request, true) } } diff --git a/src/profile/history/memory.rs b/src/profile/history/memory.rs index cb6fe608..934c6019 100644 --- a/src/profile/history/memory.rs +++ b/src/profile/history/memory.rs @@ -1,8 +1,12 @@ +mod request; mod tab; + +use request::Request; use tab::Tab; /// Reduce disk usage by cache Bookmarks index in memory pub struct Memory { + pub request: Request, pub tab: Tab, } @@ -17,6 +21,9 @@ impl Memory { /// Create new `Self` pub fn new() -> Self { - Self { tab: Tab::new() } + Self { + request: Request::new(), + tab: Tab::new(), + } } } diff --git a/src/profile/history/memory/request.rs b/src/profile/history/memory/request.rs new file mode 100644 index 00000000..138957f1 --- /dev/null +++ b/src/profile/history/memory/request.rs @@ -0,0 +1,60 @@ +use gtk::glib::GString; +use itertools::Itertools; +use std::{cell::RefCell, collections::HashMap}; + +pub struct Value { + pub unix_timestamp: i64, +} + +/// Recent request history +pub struct Request { + index: RefCell>, +} + +impl Default for Request { + fn default() -> Self { + Self::new() + } +} + +impl Request { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self { + index: RefCell::new(HashMap::new()), + } + } + + // Actions + + /// Add new record with `request` as key and `unix_timestamp` as value + /// * replace with new value if `request` already exists + pub fn set(&self, request: GString, unix_timestamp: i64) { + self.index + .borrow_mut() + .insert(request, Value { unix_timestamp }); + } + + /// Get recent requests vector + /// * sorted by `unix_timestamp` DESC + pub fn recent(&self, limit: usize) -> Vec { + let mut recent: Vec = Vec::new(); + for (request, _) in self + .index + .borrow() + .iter() + .sorted_by(|a, b| Ord::cmp(&b.1.unix_timestamp, &a.1.unix_timestamp)) + .take(limit) + { + recent.push(request.clone()) + } + recent + } + + /// Get records total + pub fn total(&self) -> usize { + self.index.borrow().len() + } +}