mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-04-02 01:25:27 +00:00
group request history by host names
This commit is contained in:
parent
c403039abc
commit
d10987ff4e
3 changed files with 65 additions and 39 deletions
|
|
@ -1,14 +1,16 @@
|
||||||
use super::{BrowserAction, Profile, WindowAction};
|
use super::{BrowserAction, Profile, WindowAction};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
gio::{self},
|
gio::{self},
|
||||||
|
glib::{GString, Uri},
|
||||||
prelude::{ActionExt, EditableExt, ToVariant},
|
prelude::{ActionExt, EditableExt, ToVariant},
|
||||||
Align, MenuButton,
|
Align, MenuButton,
|
||||||
};
|
};
|
||||||
|
use indexmap::IndexMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
// Config options
|
// Config options
|
||||||
|
|
||||||
const LABEL_MAX_LENGTH: usize = 32;
|
const LABEL_MAX_LENGTH: usize = 28;
|
||||||
pub struct Menu {
|
pub struct Menu {
|
||||||
pub menu_button: MenuButton,
|
pub menu_button: MenuButton,
|
||||||
}
|
}
|
||||||
|
|
@ -192,7 +194,7 @@ impl Menu {
|
||||||
// Bookmarks
|
// Bookmarks
|
||||||
main_bookmarks.remove_all();
|
main_bookmarks.remove_all();
|
||||||
for request in profile.bookmark.memory.recent() {
|
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!(
|
menu_item.set_action_and_target_value(Some(&format!(
|
||||||
"{}.{}",
|
"{}.{}",
|
||||||
window_action.id,
|
window_action.id,
|
||||||
|
|
@ -210,7 +212,7 @@ impl Menu {
|
||||||
main_history_tab.remove_all();
|
main_history_tab.remove_all();
|
||||||
for item in profile.history.memory.tab.recent() {
|
for item in profile.history.memory.tab.recent() {
|
||||||
let item_request = item.page.navigation.request.widget.entry.text(); // @TODO restore entire `Item`
|
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!(
|
menu_item.set_action_and_target_value(Some(&format!(
|
||||||
"{}.{}",
|
"{}.{}",
|
||||||
window_action.id,
|
window_action.id,
|
||||||
|
|
@ -221,16 +223,33 @@ impl Menu {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recently visited history
|
// 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();
|
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);
|
let mut list: IndexMap<GString, Vec<Uri>> = IndexMap::new();
|
||||||
menu_item.set_action_and_target_value(Some(&format!(
|
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.id,
|
||||||
window_action.open.simple_action.name()
|
window_action.open.simple_action.name()
|
||||||
)), Some(&request.to_variant()));
|
)), Some(&uri.to_string().to_variant()));
|
||||||
|
list.append_item(&item);
|
||||||
main_history_request.append_item(&menu_item);
|
}
|
||||||
|
main_history_request.append_submenu(Some(&group), &list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -242,14 +261,9 @@ impl Menu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format dynamically generated strings for menu item labels
|
/// Format dynamically generated strings for menu item label
|
||||||
/// * trim gemini scheme prefix
|
|
||||||
/// * trim slash postfix
|
|
||||||
/// * crop resulting string at the middle position on new `value` longer than `limit`
|
/// * crop resulting string at the middle position on new `value` longer than `limit`
|
||||||
fn label(value: &str, limit: usize) -> String {
|
fn ellipsize(value: &str, limit: usize) -> String {
|
||||||
let value = value.trim_start_matches("gemini://");
|
|
||||||
let value = value.trim_end_matches('/');
|
|
||||||
|
|
||||||
if value.len() <= limit {
|
if value.len() <= limit {
|
||||||
return value.to_string();
|
return value.to_string();
|
||||||
}
|
}
|
||||||
|
|
@ -258,3 +272,12 @@ fn label(value: &str, limit: usize) -> String {
|
||||||
|
|
||||||
format!("{}..{}", &value[..length], &value[value.len() - length..])
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ use gtk::{
|
||||||
gdk::Texture,
|
gdk::Texture,
|
||||||
gdk_pixbuf::Pixbuf,
|
gdk_pixbuf::Pixbuf,
|
||||||
gio::SocketClientEvent,
|
gio::SocketClientEvent,
|
||||||
glib::{gformat, DateTime, GString, Priority, Uri, UriFlags, UriHideFlags},
|
glib::{gformat, GString, Priority, Uri, UriFlags, UriHideFlags},
|
||||||
prelude::{EditableExt, FileExt, SocketClientExt},
|
prelude::{EditableExt, FileExt, SocketClientExt},
|
||||||
};
|
};
|
||||||
use sqlite::Transaction;
|
use sqlite::Transaction;
|
||||||
|
|
@ -234,7 +234,7 @@ impl Page {
|
||||||
scheme => {
|
scheme => {
|
||||||
// Add history record
|
// Add history record
|
||||||
if is_history {
|
if is_history {
|
||||||
snap_history(&self.profile, &self.navigation);
|
snap_history(&self.profile, &self.navigation, Some(uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update widget
|
// Update widget
|
||||||
|
|
@ -322,7 +322,7 @@ impl Page {
|
||||||
self.navigation.restore(transaction, &record.id)?;
|
self.navigation.restore(transaction, &record.id)?;
|
||||||
// Make initial page history snap using `navigation` values restored
|
// Make initial page history snap using `navigation` values restored
|
||||||
// * just to have back/forward navigation ability
|
// * 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()),
|
Err(e) => return Err(e.to_string()),
|
||||||
|
|
@ -478,7 +478,7 @@ impl Page {
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
|
||||||
response::meta::Status::Success => {
|
response::meta::Status::Success => {
|
||||||
if is_history {
|
if is_history {
|
||||||
snap_history(&profile, &navigation);
|
snap_history(&profile, &navigation, Some(&uri));
|
||||||
}
|
}
|
||||||
if is_download {
|
if is_download {
|
||||||
// Init download widget
|
// Init download widget
|
||||||
|
|
@ -806,7 +806,7 @@ impl Page {
|
||||||
response::meta::Status::CertificateInvalid => {
|
response::meta::Status::CertificateInvalid => {
|
||||||
// Add history record
|
// Add history record
|
||||||
if is_history {
|
if is_history {
|
||||||
snap_history(&profile, &navigation);
|
snap_history(&profile, &navigation, Some(&uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update widget
|
// Update widget
|
||||||
|
|
@ -831,7 +831,7 @@ impl Page {
|
||||||
_ => {
|
_ => {
|
||||||
// Add history record
|
// Add history record
|
||||||
if is_history {
|
if is_history {
|
||||||
snap_history(&profile, &navigation);
|
snap_history(&profile, &navigation, Some(&uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update widget
|
// Update widget
|
||||||
|
|
@ -854,7 +854,7 @@ impl Page {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Add history record
|
// Add history record
|
||||||
if is_history {
|
if is_history {
|
||||||
snap_history(&profile, &navigation);
|
snap_history(&profile, &navigation, Some(&uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update widget
|
// Update widget
|
||||||
|
|
@ -921,15 +921,13 @@ fn is_external_uri(subject: &Uri, base: &Uri) -> bool {
|
||||||
|
|
||||||
/// Make new history record for given `navigation` object
|
/// Make new history record for given `navigation` object
|
||||||
/// * applies on shared conditions match only
|
/// * 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();
|
let request = navigation.request.widget.entry.text();
|
||||||
|
|
||||||
// Add new record into the global memory index (used in global menu)
|
// Add new record into the global memory index (used in global menu)
|
||||||
profile
|
if let Some(uri) = uri {
|
||||||
.history
|
profile.history.memory.request.set(uri);
|
||||||
.memory
|
}
|
||||||
.request
|
|
||||||
.set(request.clone(), DateTime::now_local().unwrap().to_unix());
|
|
||||||
|
|
||||||
// Add new record into the page navigation history
|
// Add new record into the page navigation history
|
||||||
if match navigation.history.current() {
|
if match navigation.history.current() {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use gtk::glib::GString;
|
use gtk::glib::{DateTime, GString, Uri};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::{cell::RefCell, collections::HashMap};
|
use std::{cell::RefCell, collections::HashMap};
|
||||||
|
|
||||||
pub struct Value {
|
pub struct Value {
|
||||||
pub unix_timestamp: i64,
|
pub unix_timestamp: i64,
|
||||||
|
pub uri: Uri,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recent request history
|
/// Recent request history
|
||||||
|
|
@ -31,23 +32,27 @@ impl Request {
|
||||||
|
|
||||||
/// Add new record with `request` as key and `unix_timestamp` as value
|
/// Add new record with `request` as key and `unix_timestamp` as value
|
||||||
/// * replace with new value if `request` already exists
|
/// * replace with new value if `request` already exists
|
||||||
pub fn set(&self, request: GString, unix_timestamp: i64) {
|
pub fn set(&self, uri: &Uri) {
|
||||||
self.index
|
self.index.borrow_mut().insert(
|
||||||
.borrow_mut()
|
uri.to_str(),
|
||||||
.insert(request, Value { unix_timestamp });
|
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
|
/// * sorted by `unix_timestamp` DESC
|
||||||
pub fn recent(&self) -> Vec<GString> {
|
pub fn recent(&self) -> Vec<Uri> {
|
||||||
let mut recent: Vec<GString> = Vec::new();
|
let mut recent: Vec<Uri> = Vec::new();
|
||||||
for (request, _) in self
|
for (_, value) in self
|
||||||
.index
|
.index
|
||||||
.borrow()
|
.borrow()
|
||||||
.iter()
|
.iter()
|
||||||
.sorted_by(|a, b| Ord::cmp(&b.1.unix_timestamp, &a.1.unix_timestamp))
|
.sorted_by(|a, b| Ord::cmp(&b.1.unix_timestamp, &a.1.unix_timestamp))
|
||||||
{
|
{
|
||||||
recent.push(request.clone())
|
recent.push(value.uri.clone())
|
||||||
}
|
}
|
||||||
recent
|
recent
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue