group request history by host names

This commit is contained in:
yggverse 2025-01-13 02:46:53 +02:00
parent c403039abc
commit d10987ff4e
3 changed files with 65 additions and 39 deletions

View File

@ -1,14 +1,16 @@
use super::{BrowserAction, Profile, WindowAction};
use gtk::{
gio::{self},
glib::{GString, Uri},
prelude::{ActionExt, EditableExt, ToVariant},
Align, MenuButton,
};
use indexmap::IndexMap;
use std::rc::Rc;
// Config options
const LABEL_MAX_LENGTH: usize = 32;
const LABEL_MAX_LENGTH: usize = 28;
pub struct Menu {
pub menu_button: MenuButton,
}
@ -192,7 +194,7 @@ impl Menu {
// Bookmarks
main_bookmarks.remove_all();
for request in profile.bookmark.memory.recent() {
let menu_item = gio::MenuItem::new(Some(&label(&request, LABEL_MAX_LENGTH)), None);
let menu_item = gio::MenuItem::new(Some(&ellipsize(&request, LABEL_MAX_LENGTH)), None);
menu_item.set_action_and_target_value(Some(&format!(
"{}.{}",
window_action.id,
@ -210,7 +212,7 @@ impl Menu {
main_history_tab.remove_all();
for item in profile.history.memory.tab.recent() {
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);
let menu_item = gio::MenuItem::new(Some(&ellipsize(&item_request, LABEL_MAX_LENGTH)), None);
menu_item.set_action_and_target_value(Some(&format!(
"{}.{}",
window_action.id,
@ -221,16 +223,33 @@ impl Menu {
}
// Recently visited history
// * in first iteration, group records by it hostname
// * in second iteration, collect uri path as the menu sub-item label
main_history_request.remove_all();
for request in profile.history.memory.request.recent() {
let menu_item = gio::MenuItem::new(Some(&label(&request, LABEL_MAX_LENGTH)), None);
menu_item.set_action_and_target_value(Some(&format!(
let mut list: IndexMap<GString, Vec<Uri>> = IndexMap::new();
for uri in profile.history.memory.request.recent() {
list.entry(match uri.host() {
Some(host) => host,
None => uri.to_str(),
}).or_default().push(uri);
}
for (group, items) in list {
let list = gio::Menu::new();
for uri in items {
let item = gio::MenuItem::new(Some(&ellipsize(
&uri_to_label(&uri),
LABEL_MAX_LENGTH
)), None);
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);
)), Some(&uri.to_string().to_variant()));
list.append_item(&item);
}
main_history_request.append_submenu(Some(&group), &list);
}
}
});
@ -242,14 +261,9 @@ impl Menu {
}
}
/// Format dynamically generated strings for menu item labels
/// * trim gemini scheme prefix
/// * trim slash postfix
/// Format dynamically generated strings for menu item label
/// * crop resulting string at the middle position on new `value` longer than `limit`
fn label(value: &str, limit: usize) -> String {
let value = value.trim_start_matches("gemini://");
let value = value.trim_end_matches('/');
fn ellipsize(value: &str, limit: usize) -> String {
if value.len() <= limit {
return value.to_string();
}
@ -258,3 +272,12 @@ fn label(value: &str, limit: usize) -> String {
format!("{}..{}", &value[..length], &value[value.len() - length..])
}
fn uri_to_label(uri: &Uri) -> GString {
let path = uri.path();
if path == "/" {
uri.host().unwrap_or(uri.to_str())
} else {
path
}
}

View File

@ -28,7 +28,7 @@ use gtk::{
gdk::Texture,
gdk_pixbuf::Pixbuf,
gio::SocketClientEvent,
glib::{gformat, DateTime, GString, Priority, Uri, UriFlags, UriHideFlags},
glib::{gformat, 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.profile, &self.navigation);
snap_history(&self.profile, &self.navigation, Some(uri));
}
// 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.profile, &self.navigation);
snap_history(&self.profile, &self.navigation, None);
}
}
Err(e) => return Err(e.to_string()),
@ -478,7 +478,7 @@ impl Page {
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
response::meta::Status::Success => {
if is_history {
snap_history(&profile, &navigation);
snap_history(&profile, &navigation, Some(&uri));
}
if is_download {
// Init download widget
@ -806,7 +806,7 @@ impl Page {
response::meta::Status::CertificateInvalid => {
// Add history record
if is_history {
snap_history(&profile, &navigation);
snap_history(&profile, &navigation, Some(&uri));
}
// Update widget
@ -831,7 +831,7 @@ impl Page {
_ => {
// Add history record
if is_history {
snap_history(&profile, &navigation);
snap_history(&profile, &navigation, Some(&uri));
}
// Update widget
@ -854,7 +854,7 @@ impl Page {
Err(e) => {
// Add history record
if is_history {
snap_history(&profile, &navigation);
snap_history(&profile, &navigation, Some(&uri));
}
// Update widget
@ -921,15 +921,13 @@ 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(profile: &Profile, navigation: &Navigation) {
fn snap_history(profile: &Profile, navigation: &Navigation, uri: Option<&Uri>) {
let request = navigation.request.widget.entry.text();
// 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());
if let Some(uri) = uri {
profile.history.memory.request.set(uri);
}
// Add new record into the page navigation history
if match navigation.history.current() {

View File

@ -1,9 +1,10 @@
use gtk::glib::GString;
use gtk::glib::{DateTime, GString, Uri};
use itertools::Itertools;
use std::{cell::RefCell, collections::HashMap};
pub struct Value {
pub unix_timestamp: i64,
pub uri: Uri,
}
/// Recent request history
@ -31,23 +32,27 @@ impl Request {
/// 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 });
pub fn set(&self, uri: &Uri) {
self.index.borrow_mut().insert(
uri.to_str(),
Value {
unix_timestamp: DateTime::now_local().unwrap().to_unix(),
uri: uri.clone(),
},
);
}
/// Get recent requests vector
/// Get recent records vector
/// * sorted by `unix_timestamp` DESC
pub fn recent(&self) -> Vec<GString> {
let mut recent: Vec<GString> = Vec::new();
for (request, _) in self
pub fn recent(&self) -> Vec<Uri> {
let mut recent: Vec<Uri> = Vec::new();
for (_, value) in self
.index
.borrow()
.iter()
.sorted_by(|a, b| Ord::cmp(&b.1.unix_timestamp, &a.1.unix_timestamp))
{
recent.push(request.clone())
recent.push(value.uri.clone())
}
recent
}